@databricks/appkit 0.6.0 → 0.7.1

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 (169) hide show
  1. package/CLAUDE.md +11 -0
  2. package/NOTICE.md +1 -1
  3. package/dist/_virtual/_rolldown/runtime.js +1 -33
  4. package/dist/appkit/package.js +1 -1
  5. package/dist/cache/index.d.ts.map +1 -1
  6. package/dist/cache/index.js +7 -7
  7. package/dist/cache/index.js.map +1 -1
  8. package/dist/cache/storage/persistent.js +23 -21
  9. package/dist/cache/storage/persistent.js.map +1 -1
  10. package/dist/connectors/index.js +1 -1
  11. package/dist/connectors/lakebase/index.d.ts +16 -0
  12. package/dist/connectors/lakebase/index.d.ts.map +1 -0
  13. package/dist/connectors/lakebase/index.js +22 -0
  14. package/dist/connectors/lakebase/index.js.map +1 -0
  15. package/dist/connectors/lakebase-v1/client.js +4 -367
  16. package/dist/connectors/lakebase-v1/client.js.map +1 -1
  17. package/dist/connectors/lakebase-v1/index.js +1 -1
  18. package/dist/connectors/sql-warehouse/client.js +2 -2
  19. package/dist/context/execution-context.js +1 -7
  20. package/dist/context/execution-context.js.map +1 -1
  21. package/dist/context/index.js +4 -16
  22. package/dist/context/index.js.map +1 -1
  23. package/dist/core/appkit.js +1 -1
  24. package/dist/index.d.ts +2 -1
  25. package/dist/index.js +4 -3
  26. package/dist/index.js.map +1 -1
  27. package/dist/logging/logger.js +1 -1
  28. package/dist/plugin/plugin.js +4 -4
  29. package/dist/plugins/analytics/analytics.js +2 -2
  30. package/dist/plugins/server/index.js +2 -2
  31. package/docs/docs/api/appkit/Class.AppKitError/index.html +3 -3
  32. package/docs/docs/api/appkit/Class.AuthenticationError/index.html +3 -3
  33. package/docs/docs/api/appkit/Class.ConfigurationError/index.html +3 -3
  34. package/docs/docs/api/appkit/Class.ConnectionError/index.html +3 -3
  35. package/docs/docs/api/appkit/Class.ExecutionError/index.html +3 -3
  36. package/docs/docs/api/appkit/Class.InitializationError/index.html +3 -3
  37. package/docs/docs/api/appkit/Class.Plugin/index.html +3 -3
  38. package/docs/docs/api/appkit/Class.ResourceRegistry/index.html +3 -3
  39. package/docs/docs/api/appkit/Class.ServerError/index.html +3 -3
  40. package/docs/docs/api/appkit/Class.TunnelError/index.html +3 -3
  41. package/docs/docs/api/appkit/Class.ValidationError/index.html +3 -3
  42. package/docs/docs/api/appkit/Enumeration.RequestedClaimsPermissionSet/index.html +21 -0
  43. package/docs/docs/api/appkit/Enumeration.RequestedClaimsPermissionSet.md +14 -0
  44. package/docs/docs/api/appkit/Enumeration.ResourceType/index.html +4 -4
  45. package/docs/docs/api/appkit/Function.appKitTypesPlugin/index.html +3 -3
  46. package/docs/docs/api/appkit/Function.createApp/index.html +4 -4
  47. package/docs/docs/api/appkit/Function.createLakebasePool/index.html +24 -0
  48. package/docs/docs/api/appkit/Function.createLakebasePool.md +20 -0
  49. package/docs/docs/api/appkit/Function.generateDatabaseCredential/index.html +30 -0
  50. package/docs/docs/api/appkit/Function.generateDatabaseCredential.md +57 -0
  51. package/docs/docs/api/appkit/Function.getExecutionContext/index.html +4 -4
  52. package/docs/docs/api/appkit/Function.getLakebaseOrmConfig/index.html +39 -0
  53. package/docs/docs/api/appkit/Function.getLakebaseOrmConfig.md +90 -0
  54. package/docs/docs/api/appkit/Function.getLakebasePgConfig/index.html +27 -0
  55. package/docs/docs/api/appkit/Function.getLakebasePgConfig.md +31 -0
  56. package/docs/docs/api/appkit/Function.getPluginManifest/index.html +4 -4
  57. package/docs/docs/api/appkit/Function.getResourceRequirements/index.html +4 -4
  58. package/docs/docs/api/appkit/Function.getWorkspaceClient/index.html +22 -0
  59. package/docs/docs/api/appkit/Function.getWorkspaceClient.md +18 -0
  60. package/docs/docs/api/appkit/Function.isSQLTypeMarker/index.html +4 -4
  61. package/docs/docs/api/appkit/Interface.BasePluginConfig/index.html +3 -3
  62. package/docs/docs/api/appkit/Interface.CacheConfig/index.html +4 -4
  63. package/docs/docs/api/appkit/Interface.DatabaseCredential/index.html +28 -0
  64. package/docs/docs/api/appkit/Interface.DatabaseCredential.md +32 -0
  65. package/docs/docs/api/appkit/Interface.GenerateDatabaseCredentialRequest/index.html +38 -0
  66. package/docs/docs/api/appkit/Interface.GenerateDatabaseCredentialRequest.md +54 -0
  67. package/docs/docs/api/appkit/Interface.ITelemetry/index.html +4 -4
  68. package/docs/docs/api/appkit/Interface.LakebasePoolConfig/index.html +79 -0
  69. package/docs/docs/api/appkit/Interface.LakebasePoolConfig.md +126 -0
  70. package/docs/docs/api/appkit/Interface.PluginManifest/index.html +4 -4
  71. package/docs/docs/api/appkit/Interface.RequestedClaims/index.html +26 -0
  72. package/docs/docs/api/appkit/Interface.RequestedClaims.md +25 -0
  73. package/docs/docs/api/appkit/Interface.RequestedResource/index.html +27 -0
  74. package/docs/docs/api/appkit/Interface.RequestedResource.md +32 -0
  75. package/docs/docs/api/appkit/Interface.ResourceEntry/index.html +4 -4
  76. package/docs/docs/api/appkit/Interface.ResourceFieldEntry/index.html +3 -3
  77. package/docs/docs/api/appkit/Interface.ResourceRequirement/index.html +3 -3
  78. package/docs/docs/api/appkit/Interface.StreamExecutionSettings/index.html +3 -3
  79. package/docs/docs/api/appkit/Interface.TelemetryConfig/index.html +3 -3
  80. package/docs/docs/api/appkit/Interface.ValidationResult/index.html +3 -3
  81. package/docs/docs/api/appkit/TypeAlias.ConfigSchema/index.html +3 -3
  82. package/docs/docs/api/appkit/TypeAlias.IAppRouter/index.html +3 -3
  83. package/docs/docs/api/appkit/TypeAlias.ResourcePermission/index.html +3 -3
  84. package/docs/docs/api/appkit/Variable.sql/index.html +3 -3
  85. package/docs/docs/api/appkit/index.html +6 -6
  86. package/docs/docs/api/appkit-ui/data/AreaChart/index.html +2 -2
  87. package/docs/docs/api/appkit-ui/data/BarChart/index.html +2 -2
  88. package/docs/docs/api/appkit-ui/data/DataTable/index.html +2 -2
  89. package/docs/docs/api/appkit-ui/data/DonutChart/index.html +2 -2
  90. package/docs/docs/api/appkit-ui/data/HeatmapChart/index.html +2 -2
  91. package/docs/docs/api/appkit-ui/data/LineChart/index.html +2 -2
  92. package/docs/docs/api/appkit-ui/data/PieChart/index.html +2 -2
  93. package/docs/docs/api/appkit-ui/data/RadarChart/index.html +2 -2
  94. package/docs/docs/api/appkit-ui/data/ScatterChart/index.html +2 -2
  95. package/docs/docs/api/appkit-ui/index.html +2 -2
  96. package/docs/docs/api/appkit-ui/styling/index.html +2 -2
  97. package/docs/docs/api/appkit-ui/ui/Accordion/index.html +2 -2
  98. package/docs/docs/api/appkit-ui/ui/Alert/index.html +2 -2
  99. package/docs/docs/api/appkit-ui/ui/AlertDialog/index.html +2 -2
  100. package/docs/docs/api/appkit-ui/ui/AspectRatio/index.html +2 -2
  101. package/docs/docs/api/appkit-ui/ui/Avatar/index.html +2 -2
  102. package/docs/docs/api/appkit-ui/ui/Badge/index.html +2 -2
  103. package/docs/docs/api/appkit-ui/ui/Breadcrumb/index.html +2 -2
  104. package/docs/docs/api/appkit-ui/ui/Button/index.html +2 -2
  105. package/docs/docs/api/appkit-ui/ui/ButtonGroup/index.html +2 -2
  106. package/docs/docs/api/appkit-ui/ui/Calendar/index.html +2 -2
  107. package/docs/docs/api/appkit-ui/ui/Card/index.html +2 -2
  108. package/docs/docs/api/appkit-ui/ui/Carousel/index.html +2 -2
  109. package/docs/docs/api/appkit-ui/ui/ChartContainer/index.html +2 -2
  110. package/docs/docs/api/appkit-ui/ui/Checkbox/index.html +2 -2
  111. package/docs/docs/api/appkit-ui/ui/Collapsible/index.html +2 -2
  112. package/docs/docs/api/appkit-ui/ui/Command/index.html +2 -2
  113. package/docs/docs/api/appkit-ui/ui/ContextMenu/index.html +2 -2
  114. package/docs/docs/api/appkit-ui/ui/Dialog/index.html +2 -2
  115. package/docs/docs/api/appkit-ui/ui/Drawer/index.html +2 -2
  116. package/docs/docs/api/appkit-ui/ui/DropdownMenu/index.html +2 -2
  117. package/docs/docs/api/appkit-ui/ui/Empty/index.html +2 -2
  118. package/docs/docs/api/appkit-ui/ui/Field/index.html +2 -2
  119. package/docs/docs/api/appkit-ui/ui/FormControl/index.html +2 -2
  120. package/docs/docs/api/appkit-ui/ui/HoverCard/index.html +2 -2
  121. package/docs/docs/api/appkit-ui/ui/Input/index.html +2 -2
  122. package/docs/docs/api/appkit-ui/ui/InputGroup/index.html +2 -2
  123. package/docs/docs/api/appkit-ui/ui/InputOTP/index.html +2 -2
  124. package/docs/docs/api/appkit-ui/ui/Item/index.html +2 -2
  125. package/docs/docs/api/appkit-ui/ui/Kbd/index.html +2 -2
  126. package/docs/docs/api/appkit-ui/ui/Label/index.html +2 -2
  127. package/docs/docs/api/appkit-ui/ui/Menubar/index.html +2 -2
  128. package/docs/docs/api/appkit-ui/ui/NavigationMenu/index.html +2 -2
  129. package/docs/docs/api/appkit-ui/ui/Pagination/index.html +2 -2
  130. package/docs/docs/api/appkit-ui/ui/Popover/index.html +2 -2
  131. package/docs/docs/api/appkit-ui/ui/Progress/index.html +2 -2
  132. package/docs/docs/api/appkit-ui/ui/RadioGroup/index.html +2 -2
  133. package/docs/docs/api/appkit-ui/ui/ResizableHandle/index.html +2 -2
  134. package/docs/docs/api/appkit-ui/ui/ScrollArea/index.html +2 -2
  135. package/docs/docs/api/appkit-ui/ui/Select/index.html +2 -2
  136. package/docs/docs/api/appkit-ui/ui/Separator/index.html +2 -2
  137. package/docs/docs/api/appkit-ui/ui/Sheet/index.html +2 -2
  138. package/docs/docs/api/appkit-ui/ui/Sidebar/index.html +2 -2
  139. package/docs/docs/api/appkit-ui/ui/Skeleton/index.html +2 -2
  140. package/docs/docs/api/appkit-ui/ui/Slider/index.html +2 -2
  141. package/docs/docs/api/appkit-ui/ui/Spinner/index.html +2 -2
  142. package/docs/docs/api/appkit-ui/ui/Switch/index.html +2 -2
  143. package/docs/docs/api/appkit-ui/ui/Table/index.html +2 -2
  144. package/docs/docs/api/appkit-ui/ui/Tabs/index.html +2 -2
  145. package/docs/docs/api/appkit-ui/ui/Textarea/index.html +2 -2
  146. package/docs/docs/api/appkit-ui/ui/Toaster/index.html +2 -2
  147. package/docs/docs/api/appkit-ui/ui/Toggle/index.html +2 -2
  148. package/docs/docs/api/appkit-ui/ui/ToggleGroup/index.html +2 -2
  149. package/docs/docs/api/appkit-ui/ui/Tooltip/index.html +2 -2
  150. package/docs/docs/api/appkit.md +34 -23
  151. package/docs/docs/api/index.html +2 -2
  152. package/docs/docs/app-management/index.html +2 -2
  153. package/docs/docs/architecture/index.html +2 -2
  154. package/docs/docs/category/development/index.html +2 -2
  155. package/docs/docs/configuration/index.html +2 -2
  156. package/docs/docs/core-principles/index.html +2 -2
  157. package/docs/docs/development/ai-assisted-development/index.html +2 -2
  158. package/docs/docs/development/index.html +2 -2
  159. package/docs/docs/development/llm-guide/index.html +2 -2
  160. package/docs/docs/development/local-development/index.html +2 -2
  161. package/docs/docs/development/project-setup/index.html +2 -2
  162. package/docs/docs/development/remote-bridge/index.html +2 -2
  163. package/docs/docs/development/type-generation/index.html +2 -2
  164. package/docs/docs/index.html +2 -2
  165. package/docs/docs/plugins/index.html +2 -2
  166. package/llms.txt +11 -0
  167. package/package.json +5 -4
  168. package/dist/connectors/lakebase-v1/defaults.js +0 -18
  169. package/dist/connectors/lakebase-v1/defaults.js.map +0 -1
@@ -1,376 +1,13 @@
1
- import { __toCommonJS } from "../../_virtual/_rolldown/runtime.js";
2
1
  import { createLogger } from "../../logging/logger.js";
3
- import { TelemetryManager } from "../../telemetry/telemetry-manager.js";
4
- import { SpanStatusCode } from "../../telemetry/index.js";
5
- import { AppKitError } from "../../errors/base.js";
6
- import { AuthenticationError } from "../../errors/authentication.js";
7
- import { ConfigurationError } from "../../errors/configuration.js";
8
- import { ConnectionError } from "../../errors/connection.js";
9
- import { ValidationError } from "../../errors/validation.js";
10
- import { init_errors } from "../../errors/index.js";
11
- import { deepMerge } from "../../utils/merge.js";
12
- import { lakebaseV1Defaults } from "./defaults.js";
13
- import { context_exports, init_context } from "../../context/index.js";
2
+ import "../../telemetry/index.js";
3
+ import "../../context/index.js";
14
4
  import { randomUUID } from "node:crypto";
15
- import { ApiClient, Config } from "@databricks/sdk-experimental";
5
+ import "@databricks/sdk-experimental";
16
6
  import pg from "pg";
17
7
 
18
8
  //#region src/connectors/lakebase-v1/client.ts
19
- init_errors();
20
9
  const logger = createLogger("connectors:lakebase-v1");
21
- /**
22
- * Enterprise-grade connector for Databricks Lakebase Provisioned
23
- *
24
- * @deprecated This connector is for Lakebase Provisioned only.
25
- * For new projects, use Lakebase Autoscaling instead: https://docs.databricks.com/aws/en/oltp/projects/
26
- *
27
- * This connector is compatible with Lakebase Provisioned: https://docs.databricks.com/aws/en/oltp/instances/
28
- *
29
- * Lakebase Autoscaling offers:
30
- * - Automatic compute scaling
31
- * - Scale-to-zero for cost optimization
32
- * - Database branching for development
33
- * - Instant restore capabilities
34
- *
35
- * Use the new LakebaseConnector (coming in a future release) for Lakebase Autoscaling support.
36
- *
37
- * @example Simplest - everything from env/context
38
- * ```typescript
39
- * const connector = new LakebaseV1Connector();
40
- * await connector.query('SELECT * FROM users');
41
- * ```
42
- *
43
- * @example With explicit connection string
44
- * ```typescript
45
- * const connector = new LakebaseV1Connector({
46
- * connectionString: 'postgresql://...'
47
- * });
48
- * ```
49
- */
50
- var LakebaseV1Connector = class {
51
- name = "lakebase-v1";
52
- CACHE_BUFFER_MS = 120 * 1e3;
53
- config;
54
- connectionConfig;
55
- pool = null;
56
- credentials = null;
57
- telemetry;
58
- telemetryMetrics;
59
- constructor(userConfig) {
60
- this.config = deepMerge(lakebaseV1Defaults, userConfig);
61
- this.connectionConfig = this.parseConnectionConfig();
62
- this.telemetry = TelemetryManager.getProvider(this.name, this.config.telemetry);
63
- this.telemetryMetrics = {
64
- queryCount: this.telemetry.getMeter().createCounter("lakebase.v1.query.count", {
65
- description: "Total number of queries executed",
66
- unit: "1"
67
- }),
68
- queryDuration: this.telemetry.getMeter().createHistogram("lakebase.v1.query.duration", {
69
- description: "Duration of queries executed",
70
- unit: "ms"
71
- })
72
- };
73
- if (this.config.maxPoolSize < 1) throw ValidationError.invalidValue("maxPoolSize", this.config.maxPoolSize, "at least 1");
74
- }
75
- /**
76
- * Execute a SQL query
77
- *
78
- * @example
79
- * ```typescript
80
- * const users = await connector.query('SELECT * FROM users');
81
- * const user = await connector.query('SELECT * FROM users WHERE id = $1', [123]);
82
- * ```
83
- */
84
- async query(sql, params, retryCount = 0) {
85
- const startTime = Date.now();
86
- return this.telemetry.startActiveSpan("lakebase.v1.query", { attributes: {
87
- "db.system": "lakebase-v1",
88
- "db.statement": sql.substring(0, 500),
89
- "db.retry_count": retryCount
90
- } }, async (span) => {
91
- try {
92
- const result = await (await this.getPool()).query(sql, params);
93
- span.setAttribute("db.rows_affected", result.rowCount ?? 0);
94
- span.setStatus({ code: SpanStatusCode.OK });
95
- return result;
96
- } catch (error) {
97
- if (this.isAuthError(error)) {
98
- span.addEvent("auth_error_retry");
99
- await this.rotateCredentials();
100
- const result = await (await this.getPool()).query(sql, params);
101
- span.setAttribute("db.rows_affected", result.rowCount ?? 0);
102
- span.setStatus({ code: SpanStatusCode.OK });
103
- return result;
104
- }
105
- if (this.isTransientError(error) && retryCount < 1) {
106
- span.addEvent("transient_error_retry");
107
- await new Promise((resolve) => setTimeout(resolve, 100));
108
- return await this.query(sql, params, retryCount + 1);
109
- }
110
- span.recordException(error);
111
- span.setStatus({ code: SpanStatusCode.ERROR });
112
- if (error instanceof AppKitError) throw error;
113
- throw ConnectionError.queryFailed(error);
114
- } finally {
115
- const duration = Date.now() - startTime;
116
- this.telemetryMetrics.queryCount.add(1);
117
- this.telemetryMetrics.queryDuration.record(duration);
118
- span.end();
119
- }
120
- });
121
- }
122
- /**
123
- * Execute a transaction
124
- *
125
- * COMMIT and ROLLBACK are automatically managed by the transaction function.
126
- *
127
- * @param callback - Callback function to execute within the transaction context
128
- * @example
129
- * ```typescript
130
- * await connector.transaction(async (client) => {
131
- * await client.query('INSERT INTO accounts (name) VALUES ($1)', ['Alice']);
132
- * await client.query('INSERT INTO logs (action) VALUES ($1)', ['Created Alice']);
133
- * });
134
- * ```
135
- */
136
- async transaction(callback, retryCount = 0) {
137
- const startTime = Date.now();
138
- return this.telemetry.startActiveSpan("lakebase.v1.transaction", { attributes: {
139
- "db.system": "lakebase-v1",
140
- "db.retry_count": retryCount
141
- } }, async (span) => {
142
- const client = await (await this.getPool()).connect();
143
- try {
144
- await client.query("BEGIN");
145
- const result = await callback(client);
146
- await client.query("COMMIT");
147
- span.setStatus({ code: SpanStatusCode.OK });
148
- return result;
149
- } catch (error) {
150
- try {
151
- await client.query("ROLLBACK");
152
- } catch {}
153
- if (this.isAuthError(error)) {
154
- span.addEvent("auth_error_retry");
155
- client.release();
156
- await this.rotateCredentials();
157
- const retryClient = await (await this.getPool()).connect();
158
- try {
159
- await client.query("BEGIN");
160
- const result = await callback(retryClient);
161
- await client.query("COMMIT");
162
- span.setStatus({ code: SpanStatusCode.OK });
163
- return result;
164
- } catch (retryError) {
165
- try {
166
- await retryClient.query("ROLLBACK");
167
- } catch {}
168
- throw retryError;
169
- } finally {
170
- retryClient.release();
171
- }
172
- }
173
- if (this.isTransientError(error) && retryCount < 1) {
174
- span.addEvent("transaction_error_retry");
175
- client.release();
176
- await new Promise((resolve) => setTimeout(resolve, 100));
177
- return await this.transaction(callback, retryCount + 1);
178
- }
179
- span.recordException(error);
180
- span.setStatus({ code: SpanStatusCode.ERROR });
181
- if (error instanceof AppKitError) throw error;
182
- throw ConnectionError.transactionFailed(error);
183
- } finally {
184
- client.release();
185
- const duration = Date.now() - startTime;
186
- this.telemetryMetrics.queryCount.add(1);
187
- this.telemetryMetrics.queryDuration.record(duration);
188
- span.end();
189
- }
190
- });
191
- }
192
- /** Check if database connection is healthy */
193
- async healthCheck() {
194
- return this.telemetry.startActiveSpan("lakebase.v1.healthCheck", {}, async (span) => {
195
- try {
196
- const healthy = (await this.query("SELECT 1 as result")).rows[0]?.result === 1;
197
- span.setAttribute("db.healthy", healthy);
198
- span.setStatus({ code: SpanStatusCode.OK });
199
- return healthy;
200
- } catch {
201
- span.setAttribute("db.healthy", false);
202
- span.setStatus({ code: SpanStatusCode.ERROR });
203
- return false;
204
- } finally {
205
- span.end();
206
- }
207
- });
208
- }
209
- /** Close connection pool (call on shutdown) */
210
- async close() {
211
- if (this.pool) {
212
- await this.pool.end().catch((error) => {
213
- logger.error("Error closing connection pool: %O", error);
214
- });
215
- this.pool = null;
216
- }
217
- this.credentials = null;
218
- }
219
- /** Setup graceful shutdown to close connection pools */
220
- shutdown() {
221
- process.on("SIGTERM", () => this.close());
222
- process.on("SIGINT", () => this.close());
223
- this.close();
224
- }
225
- /** Get Databricks workspace client - from config or execution context */
226
- getWorkspaceClient() {
227
- if (this.config.workspaceClient) return this.config.workspaceClient;
228
- try {
229
- const { getWorkspaceClient: getClient } = (init_context(), __toCommonJS(context_exports));
230
- const client = getClient();
231
- this.config.workspaceClient = client;
232
- return client;
233
- } catch (_error) {
234
- throw ConnectionError.clientUnavailable("Databricks workspace client", "Either pass it in config or ensure ServiceContext is initialized");
235
- }
236
- }
237
- /** Get or create connection pool */
238
- async getPool() {
239
- if (!this.connectionConfig) throw ConfigurationError.invalidConnection("Lakebase", "Set PGHOST, PGDATABASE, PGAPPNAME env vars, provide a connectionString, or pass explicit config");
240
- if (!this.pool) {
241
- const creds = await this.getCredentials();
242
- this.pool = this.createPool(creds);
243
- }
244
- return this.pool;
245
- }
246
- /** Create PostgreSQL pool */
247
- createPool(credentials) {
248
- const { host, database, port, sslMode } = this.connectionConfig;
249
- const pool = new pg.Pool({
250
- host,
251
- port,
252
- database,
253
- user: credentials.username,
254
- password: credentials.password,
255
- max: this.config.maxPoolSize,
256
- idleTimeoutMillis: this.config.idleTimeoutMs,
257
- connectionTimeoutMillis: this.config.connectionTimeoutMs,
258
- ssl: sslMode === "require" ? { rejectUnauthorized: true } : false
259
- });
260
- pool.on("error", (error) => {
261
- logger.error("Connection pool error: %s (code: %s)", error.message, error.code);
262
- });
263
- return pool;
264
- }
265
- /** Get or fetch credentials with caching */
266
- async getCredentials() {
267
- const now = Date.now();
268
- if (this.credentials && now < this.credentials.expiresAt - this.CACHE_BUFFER_MS) return this.credentials;
269
- const username = await this.fetchUsername();
270
- const { token, expiresAt } = await this.fetchPassword();
271
- this.credentials = {
272
- username,
273
- password: token,
274
- expiresAt
275
- };
276
- return {
277
- username,
278
- password: token
279
- };
280
- }
281
- /** Rotate credentials and recreate pool */
282
- async rotateCredentials() {
283
- this.credentials = null;
284
- if (this.pool) {
285
- const oldPool = this.pool;
286
- this.pool = null;
287
- oldPool.end().catch((error) => {
288
- logger.error("Error closing old connection pool during rotation: %O", error);
289
- });
290
- }
291
- }
292
- /** Fetch username from Databricks */
293
- async fetchUsername() {
294
- const user = await this.getWorkspaceClient().currentUser.me();
295
- if (!user.userName) throw AuthenticationError.userLookupFailed();
296
- return user.userName;
297
- }
298
- /** Fetch password (OAuth token) from Databricks */
299
- async fetchPassword() {
300
- const apiClient = new ApiClient(new Config({ host: this.getWorkspaceClient().config.host }));
301
- if (!this.connectionConfig.appName) throw ConfigurationError.resourceNotFound("Database app name");
302
- const credentials = await apiClient.request({
303
- path: `/api/2.0/database/credentials`,
304
- method: "POST",
305
- headers: new Headers(),
306
- raw: false,
307
- payload: {
308
- instance_names: [this.connectionConfig.appName],
309
- request_id: randomUUID()
310
- }
311
- });
312
- if (!this.validateCredentials(credentials)) throw AuthenticationError.credentialsFailed(this.connectionConfig.appName);
313
- const expiresAt = new Date(credentials.expiration_time).getTime();
314
- return {
315
- token: credentials.token,
316
- expiresAt
317
- };
318
- }
319
- /** Check if error is auth failure */
320
- isAuthError(error) {
321
- return typeof error === "object" && error !== null && "code" in error && error.code === "28P01";
322
- }
323
- /** Check if error is transient */
324
- isTransientError(error) {
325
- if (typeof error !== "object" || error === null || !("code" in error)) return false;
326
- const code = error.code;
327
- return code === "ECONNRESET" || code === "ECONNREFUSED" || code === "ETIMEDOUT" || code === "57P01" || code === "57P03" || code === "08006" || code === "08003" || code === "08000";
328
- }
329
- /** Type guard for credentials */
330
- validateCredentials(value) {
331
- if (typeof value !== "object" || value === null) return false;
332
- const credentials = value;
333
- return "token" in credentials && typeof credentials.token === "string" && "expiration_time" in credentials && typeof credentials.expiration_time === "string" && new Date(credentials.expiration_time).getTime() > Date.now();
334
- }
335
- /** Parse connection configuration from config or environment */
336
- parseConnectionConfig() {
337
- if (this.config.connectionString) return this.parseConnectionString(this.config.connectionString);
338
- if (this.config.host && this.config.database && this.config.appName) return {
339
- host: this.config.host,
340
- database: this.config.database,
341
- port: this.config.port ?? 5432,
342
- sslMode: this.config.sslMode ?? "require",
343
- appName: this.config.appName
344
- };
345
- const pgHost = process.env.PGHOST;
346
- const pgDatabase = process.env.PGDATABASE;
347
- const pgAppName = process.env.PGAPPNAME;
348
- if (!pgHost || !pgDatabase || !pgAppName) throw ConfigurationError.invalidConnection("Lakebase", "Required env vars: PGHOST, PGDATABASE, PGAPPNAME. Optional: PGPORT (default: 5432), PGSSLMODE (default: require)");
349
- const pgPort = process.env.PGPORT;
350
- const port = pgPort ? parseInt(pgPort, 10) : 5432;
351
- if (Number.isNaN(port)) throw ValidationError.invalidValue("port", pgPort, "a number");
352
- return {
353
- host: pgHost,
354
- database: pgDatabase,
355
- port,
356
- sslMode: process.env.PGSSLMODE || "require",
357
- appName: pgAppName
358
- };
359
- }
360
- parseConnectionString(connectionString) {
361
- const url = new URL(connectionString);
362
- const appName = url.searchParams.get("appName");
363
- if (!appName) throw ConfigurationError.missingConnectionParam("appName");
364
- return {
365
- host: url.hostname,
366
- database: url.pathname.slice(1),
367
- port: url.port ? parseInt(url.port, 10) : 5432,
368
- sslMode: url.searchParams.get("sslmode") ?? "require",
369
- appName
370
- };
371
- }
372
- };
373
10
 
374
11
  //#endregion
375
- export { LakebaseV1Connector };
12
+ export { };
376
13
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../../../src/connectors/lakebase-v1/client.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport { ApiClient, Config } from \"@databricks/sdk-experimental\";\nimport pg from \"pg\";\nimport {\n type Counter,\n type Histogram,\n SpanStatusCode,\n TelemetryManager,\n type TelemetryProvider,\n} from \"@/telemetry\";\nimport {\n AppKitError,\n AuthenticationError,\n ConfigurationError,\n ConnectionError,\n ValidationError,\n} from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport { deepMerge } from \"../../utils\";\nimport { lakebaseV1Defaults } from \"./defaults\";\nimport type {\n LakebaseV1Config,\n LakebaseV1ConnectionConfig,\n LakebaseV1Credentials,\n} from \"./types\";\n\nconst logger = createLogger(\"connectors:lakebase-v1\");\n\n/**\n * Enterprise-grade connector for Databricks Lakebase Provisioned\n *\n * @deprecated This connector is for Lakebase Provisioned only.\n * For new projects, use Lakebase Autoscaling instead: https://docs.databricks.com/aws/en/oltp/projects/\n *\n * This connector is compatible with Lakebase Provisioned: https://docs.databricks.com/aws/en/oltp/instances/\n *\n * Lakebase Autoscaling offers:\n * - Automatic compute scaling\n * - Scale-to-zero for cost optimization\n * - Database branching for development\n * - Instant restore capabilities\n *\n * Use the new LakebaseConnector (coming in a future release) for Lakebase Autoscaling support.\n *\n * @example Simplest - everything from env/context\n * ```typescript\n * const connector = new LakebaseV1Connector();\n * await connector.query('SELECT * FROM users');\n * ```\n *\n * @example With explicit connection string\n * ```typescript\n * const connector = new LakebaseV1Connector({\n * connectionString: 'postgresql://...'\n * });\n * ```\n */\nexport class LakebaseV1Connector {\n private readonly name: string = \"lakebase-v1\";\n private readonly CACHE_BUFFER_MS = 2 * 60 * 1000;\n private readonly config: LakebaseV1Config;\n private readonly connectionConfig: LakebaseV1ConnectionConfig;\n private pool: pg.Pool | null = null;\n private credentials: LakebaseV1Credentials | null = null;\n\n // telemetry\n private readonly telemetry: TelemetryProvider;\n private readonly telemetryMetrics: {\n queryCount: Counter;\n queryDuration: Histogram;\n };\n\n constructor(userConfig?: Partial<LakebaseV1Config>) {\n this.config = deepMerge(lakebaseV1Defaults, userConfig);\n this.connectionConfig = this.parseConnectionConfig();\n\n this.telemetry = TelemetryManager.getProvider(\n this.name,\n this.config.telemetry,\n );\n this.telemetryMetrics = {\n queryCount: this.telemetry\n .getMeter()\n .createCounter(\"lakebase.v1.query.count\", {\n description: \"Total number of queries executed\",\n unit: \"1\",\n }),\n queryDuration: this.telemetry\n .getMeter()\n .createHistogram(\"lakebase.v1.query.duration\", {\n description: \"Duration of queries executed\",\n unit: \"ms\",\n }),\n };\n\n // validate configuration\n if (this.config.maxPoolSize < 1) {\n throw ValidationError.invalidValue(\n \"maxPoolSize\",\n this.config.maxPoolSize,\n \"at least 1\",\n );\n }\n }\n\n /**\n * Execute a SQL query\n *\n * @example\n * ```typescript\n * const users = await connector.query('SELECT * FROM users');\n * const user = await connector.query('SELECT * FROM users WHERE id = $1', [123]);\n * ```\n */\n async query<T extends pg.QueryResultRow>(\n sql: string,\n params?: any[],\n retryCount: number = 0,\n ): Promise<pg.QueryResult<T>> {\n const startTime = Date.now();\n\n return this.telemetry.startActiveSpan(\n \"lakebase.v1.query\",\n {\n attributes: {\n \"db.system\": \"lakebase-v1\",\n \"db.statement\": sql.substring(0, 500),\n \"db.retry_count\": retryCount,\n },\n },\n async (span) => {\n try {\n const pool = await this.getPool();\n const result = await pool.query<T>(sql, params);\n span.setAttribute(\"db.rows_affected\", result.rowCount ?? 0);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n // retry on auth failure\n if (this.isAuthError(error)) {\n span.addEvent(\"auth_error_retry\");\n await this.rotateCredentials();\n const newPool = await this.getPool();\n const result = await newPool.query<T>(sql, params);\n span.setAttribute(\"db.rows_affected\", result.rowCount ?? 0);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n }\n\n // retry on transient errors, but only once\n if (this.isTransientError(error) && retryCount < 1) {\n span.addEvent(\"transient_error_retry\");\n await new Promise((resolve) => setTimeout(resolve, 100));\n return await this.query<T>(sql, params, retryCount + 1);\n }\n\n span.recordException(error as Error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n\n if (error instanceof AppKitError) {\n throw error;\n }\n throw ConnectionError.queryFailed(error as Error);\n } finally {\n const duration = Date.now() - startTime;\n this.telemetryMetrics.queryCount.add(1);\n this.telemetryMetrics.queryDuration.record(duration);\n span.end();\n }\n },\n );\n }\n\n /**\n * Execute a transaction\n *\n * COMMIT and ROLLBACK are automatically managed by the transaction function.\n *\n * @param callback - Callback function to execute within the transaction context\n * @example\n * ```typescript\n * await connector.transaction(async (client) => {\n * await client.query('INSERT INTO accounts (name) VALUES ($1)', ['Alice']);\n * await client.query('INSERT INTO logs (action) VALUES ($1)', ['Created Alice']);\n * });\n * ```\n */\n async transaction<T>(\n callback: (client: pg.PoolClient) => Promise<T>,\n retryCount: number = 0,\n ): Promise<T> {\n const startTime = Date.now();\n return this.telemetry.startActiveSpan(\n \"lakebase.v1.transaction\",\n {\n attributes: {\n \"db.system\": \"lakebase-v1\",\n \"db.retry_count\": retryCount,\n },\n },\n async (span) => {\n const pool = await this.getPool();\n const client = await pool.connect();\n try {\n await client.query(\"BEGIN\");\n const result = await callback(client);\n await client.query(\"COMMIT\");\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n try {\n await client.query(\"ROLLBACK\");\n } catch {}\n // retry on auth failure\n if (this.isAuthError(error)) {\n span.addEvent(\"auth_error_retry\");\n client.release();\n await this.rotateCredentials();\n const newPool = await this.getPool();\n const retryClient = await newPool.connect();\n try {\n await client.query(\"BEGIN\");\n const result = await callback(retryClient);\n await client.query(\"COMMIT\");\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (retryError) {\n try {\n await retryClient.query(\"ROLLBACK\");\n } catch {}\n throw retryError;\n } finally {\n retryClient.release();\n }\n }\n\n // retry on transient errors, but only once\n if (this.isTransientError(error) && retryCount < 1) {\n span.addEvent(\"transaction_error_retry\");\n client.release();\n await new Promise((resolve) => setTimeout(resolve, 100));\n return await this.transaction<T>(callback, retryCount + 1);\n }\n span.recordException(error as Error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n\n if (error instanceof AppKitError) {\n throw error;\n }\n throw ConnectionError.transactionFailed(error as Error);\n } finally {\n client.release();\n const duration = Date.now() - startTime;\n this.telemetryMetrics.queryCount.add(1);\n this.telemetryMetrics.queryDuration.record(duration);\n span.end();\n }\n },\n );\n }\n\n /** Check if database connection is healthy */\n async healthCheck(): Promise<boolean> {\n return this.telemetry.startActiveSpan(\n \"lakebase.v1.healthCheck\",\n {},\n async (span) => {\n try {\n const result = await this.query<{ result: number }>(\n \"SELECT 1 as result\",\n );\n const healthy = result.rows[0]?.result === 1;\n span.setAttribute(\"db.healthy\", healthy);\n span.setStatus({ code: SpanStatusCode.OK });\n return healthy;\n } catch {\n span.setAttribute(\"db.healthy\", false);\n span.setStatus({ code: SpanStatusCode.ERROR });\n return false;\n } finally {\n span.end();\n }\n },\n );\n }\n\n /** Close connection pool (call on shutdown) */\n async close(): Promise<void> {\n if (this.pool) {\n await this.pool.end().catch((error: unknown) => {\n logger.error(\"Error closing connection pool: %O\", error);\n });\n this.pool = null;\n }\n this.credentials = null;\n }\n\n /** Setup graceful shutdown to close connection pools */\n shutdown(): void {\n process.on(\"SIGTERM\", () => this.close());\n process.on(\"SIGINT\", () => this.close());\n this.close();\n }\n\n /** Get Databricks workspace client - from config or execution context */\n private getWorkspaceClient(): WorkspaceClient {\n if (this.config.workspaceClient) {\n return this.config.workspaceClient;\n }\n\n try {\n const { getWorkspaceClient: getClient } = require(\"../../context\");\n const client = getClient();\n\n // cache it for subsequent calls\n this.config.workspaceClient = client;\n return client;\n } catch (_error) {\n throw ConnectionError.clientUnavailable(\n \"Databricks workspace client\",\n \"Either pass it in config or ensure ServiceContext is initialized\",\n );\n }\n }\n\n /** Get or create connection pool */\n private async getPool(): Promise<pg.Pool> {\n if (!this.connectionConfig) {\n throw ConfigurationError.invalidConnection(\n \"Lakebase\",\n \"Set PGHOST, PGDATABASE, PGAPPNAME env vars, provide a connectionString, or pass explicit config\",\n );\n }\n\n if (!this.pool) {\n const creds = await this.getCredentials();\n this.pool = this.createPool(creds);\n }\n return this.pool;\n }\n\n /** Create PostgreSQL pool */\n private createPool(credentials: {\n username: string;\n password: string;\n }): pg.Pool {\n const { host, database, port, sslMode } = this.connectionConfig;\n\n const pool = new pg.Pool({\n host,\n port,\n database,\n user: credentials.username,\n password: credentials.password,\n max: this.config.maxPoolSize,\n idleTimeoutMillis: this.config.idleTimeoutMs,\n connectionTimeoutMillis: this.config.connectionTimeoutMs,\n ssl: sslMode === \"require\" ? { rejectUnauthorized: true } : false,\n });\n\n pool.on(\"error\", (error: Error & { code?: string }) => {\n logger.error(\n \"Connection pool error: %s (code: %s)\",\n error.message,\n error.code,\n );\n });\n\n return pool;\n }\n\n /** Get or fetch credentials with caching */\n private async getCredentials(): Promise<{\n username: string;\n password: string;\n }> {\n const now = Date.now();\n\n // return cached if still valid\n if (\n this.credentials &&\n now < this.credentials.expiresAt - this.CACHE_BUFFER_MS\n ) {\n return this.credentials;\n }\n\n // fetch new credentials\n const username = await this.fetchUsername();\n const { token, expiresAt } = await this.fetchPassword();\n\n this.credentials = {\n username,\n password: token,\n expiresAt,\n };\n\n return { username, password: token };\n }\n\n /** Rotate credentials and recreate pool */\n private async rotateCredentials(): Promise<void> {\n // clear cached credentials\n this.credentials = null;\n\n if (this.pool) {\n const oldPool = this.pool;\n this.pool = null;\n oldPool.end().catch((error: unknown) => {\n logger.error(\n \"Error closing old connection pool during rotation: %O\",\n error,\n );\n });\n }\n }\n\n /** Fetch username from Databricks */\n private async fetchUsername(): Promise<string> {\n const workspaceClient = this.getWorkspaceClient();\n const user = await workspaceClient.currentUser.me();\n if (!user.userName) {\n throw AuthenticationError.userLookupFailed();\n }\n return user.userName;\n }\n\n /** Fetch password (OAuth token) from Databricks */\n private async fetchPassword(): Promise<{ token: string; expiresAt: number }> {\n const workspaceClient = this.getWorkspaceClient();\n const config = new Config({ host: workspaceClient.config.host });\n const apiClient = new ApiClient(config);\n\n if (!this.connectionConfig.appName) {\n throw ConfigurationError.resourceNotFound(\"Database app name\");\n }\n\n const credentials = await apiClient.request({\n path: `/api/2.0/database/credentials`,\n method: \"POST\",\n headers: new Headers(),\n raw: false,\n payload: {\n instance_names: [this.connectionConfig.appName],\n request_id: randomUUID(),\n },\n });\n\n if (!this.validateCredentials(credentials)) {\n throw AuthenticationError.credentialsFailed(\n this.connectionConfig.appName,\n );\n }\n\n const expiresAt = new Date(credentials.expiration_time).getTime();\n\n return { token: credentials.token, expiresAt };\n }\n\n /** Check if error is auth failure */\n private isAuthError(error: unknown): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n (error as any).code === \"28P01\"\n );\n }\n\n /** Check if error is transient */\n private isTransientError(error: unknown): boolean {\n if (typeof error !== \"object\" || error === null || !(\"code\" in error)) {\n return false;\n }\n\n const code = (error as any).code;\n return (\n code === \"ECONNRESET\" ||\n code === \"ECONNREFUSED\" ||\n code === \"ETIMEDOUT\" ||\n code === \"57P01\" || // admin_shutdown\n code === \"57P03\" || // cannot_connect_now\n code === \"08006\" || // connection_failure\n code === \"08003\" || // connection_does_not_exist\n code === \"08000\" // connection_exception\n );\n }\n\n /** Type guard for credentials */\n private validateCredentials(\n value: unknown,\n ): value is { token: string; expiration_time: string } {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n\n const credentials = value as { token: string; expiration_time: string };\n return (\n \"token\" in credentials &&\n typeof credentials.token === \"string\" &&\n \"expiration_time\" in credentials &&\n typeof credentials.expiration_time === \"string\" &&\n new Date(credentials.expiration_time).getTime() > Date.now()\n );\n }\n\n /** Parse connection configuration from config or environment */\n private parseConnectionConfig(): LakebaseV1ConnectionConfig {\n if (this.config.connectionString) {\n return this.parseConnectionString(this.config.connectionString);\n }\n\n // get connection from config\n if (this.config.host && this.config.database && this.config.appName) {\n return {\n host: this.config.host,\n database: this.config.database,\n port: this.config.port ?? 5432,\n sslMode: this.config.sslMode ?? \"require\",\n appName: this.config.appName,\n };\n }\n\n // get connection from environment variables\n const pgHost = process.env.PGHOST;\n const pgDatabase = process.env.PGDATABASE;\n const pgAppName = process.env.PGAPPNAME;\n if (!pgHost || !pgDatabase || !pgAppName) {\n throw ConfigurationError.invalidConnection(\n \"Lakebase\",\n \"Required env vars: PGHOST, PGDATABASE, PGAPPNAME. Optional: PGPORT (default: 5432), PGSSLMODE (default: require)\",\n );\n }\n const pgPort = process.env.PGPORT;\n const port = pgPort ? parseInt(pgPort, 10) : 5432;\n\n if (Number.isNaN(port)) {\n throw ValidationError.invalidValue(\"port\", pgPort, \"a number\");\n }\n\n const pgSSLMode = process.env.PGSSLMODE;\n const sslMode =\n (pgSSLMode as \"require\" | \"disable\" | \"prefer\") || \"require\";\n\n return {\n host: pgHost,\n database: pgDatabase,\n port,\n sslMode,\n appName: pgAppName,\n };\n }\n\n private parseConnectionString(\n connectionString: string,\n ): LakebaseV1ConnectionConfig {\n const url = new URL(connectionString);\n const appName = url.searchParams.get(\"appName\");\n if (!appName) {\n throw ConfigurationError.missingConnectionParam(\"appName\");\n }\n\n return {\n host: url.hostname,\n database: url.pathname.slice(1), // remove leading slash\n port: url.port ? parseInt(url.port, 10) : 5432,\n sslMode:\n (url.searchParams.get(\"sslmode\") as \"require\" | \"disable\" | \"prefer\") ??\n \"require\",\n appName: appName,\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../../src/connectors/lakebase-v1/client.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport { ApiClient, Config } from \"@databricks/sdk-experimental\";\nimport pg from \"pg\";\nimport {\n type Counter,\n type Histogram,\n SpanStatusCode,\n TelemetryManager,\n type TelemetryProvider,\n} from \"@/telemetry\";\nimport {\n AppKitError,\n AuthenticationError,\n ConfigurationError,\n ConnectionError,\n ValidationError,\n} from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport { deepMerge } from \"../../utils\";\nimport { lakebaseV1Defaults } from \"./defaults\";\nimport type {\n LakebaseV1Config,\n LakebaseV1ConnectionConfig,\n LakebaseV1Credentials,\n} from \"./types\";\n\nconst logger = createLogger(\"connectors:lakebase-v1\");\n\n/**\n * Enterprise-grade connector for Databricks Lakebase Provisioned\n *\n * @deprecated This connector is for Lakebase Provisioned only.\n * For new projects, use Lakebase Autoscaling instead: https://docs.databricks.com/aws/en/oltp/projects/\n *\n * This connector is compatible with Lakebase Provisioned: https://docs.databricks.com/aws/en/oltp/instances/\n *\n * Lakebase Autoscaling offers:\n * - Automatic compute scaling\n * - Scale-to-zero for cost optimization\n * - Database branching for development\n * - Instant restore capabilities\n *\n * Use the new LakebaseConnector (coming in a future release) for Lakebase Autoscaling support.\n *\n * @example Simplest - everything from env/context\n * ```typescript\n * const connector = new LakebaseV1Connector();\n * await connector.query('SELECT * FROM users');\n * ```\n *\n * @example With explicit connection string\n * ```typescript\n * const connector = new LakebaseV1Connector({\n * connectionString: 'postgresql://...'\n * });\n * ```\n */\nexport class LakebaseV1Connector {\n private readonly name: string = \"lakebase-v1\";\n private readonly CACHE_BUFFER_MS = 2 * 60 * 1000;\n private readonly config: LakebaseV1Config;\n private readonly connectionConfig: LakebaseV1ConnectionConfig;\n private pool: pg.Pool | null = null;\n private credentials: LakebaseV1Credentials | null = null;\n\n // telemetry\n private readonly telemetry: TelemetryProvider;\n private readonly telemetryMetrics: {\n queryCount: Counter;\n queryDuration: Histogram;\n };\n\n constructor(userConfig?: Partial<LakebaseV1Config>) {\n this.config = deepMerge(lakebaseV1Defaults, userConfig);\n this.connectionConfig = this.parseConnectionConfig();\n\n this.telemetry = TelemetryManager.getProvider(\n this.name,\n this.config.telemetry,\n );\n this.telemetryMetrics = {\n queryCount: this.telemetry\n .getMeter()\n .createCounter(\"lakebase.v1.query.count\", {\n description: \"Total number of queries executed\",\n unit: \"1\",\n }),\n queryDuration: this.telemetry\n .getMeter()\n .createHistogram(\"lakebase.v1.query.duration\", {\n description: \"Duration of queries executed\",\n unit: \"ms\",\n }),\n };\n\n // validate configuration\n if (this.config.maxPoolSize < 1) {\n throw ValidationError.invalidValue(\n \"maxPoolSize\",\n this.config.maxPoolSize,\n \"at least 1\",\n );\n }\n }\n\n /**\n * Execute a SQL query\n *\n * @example\n * ```typescript\n * const users = await connector.query('SELECT * FROM users');\n * const user = await connector.query('SELECT * FROM users WHERE id = $1', [123]);\n * ```\n */\n async query<T extends pg.QueryResultRow>(\n sql: string,\n params?: any[],\n retryCount: number = 0,\n ): Promise<pg.QueryResult<T>> {\n const startTime = Date.now();\n\n return this.telemetry.startActiveSpan(\n \"lakebase.v1.query\",\n {\n attributes: {\n \"db.system\": \"lakebase-v1\",\n \"db.statement\": sql.substring(0, 500),\n \"db.retry_count\": retryCount,\n },\n },\n async (span) => {\n try {\n const pool = await this.getPool();\n const result = await pool.query<T>(sql, params);\n span.setAttribute(\"db.rows_affected\", result.rowCount ?? 0);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n // retry on auth failure\n if (this.isAuthError(error)) {\n span.addEvent(\"auth_error_retry\");\n await this.rotateCredentials();\n const newPool = await this.getPool();\n const result = await newPool.query<T>(sql, params);\n span.setAttribute(\"db.rows_affected\", result.rowCount ?? 0);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n }\n\n // retry on transient errors, but only once\n if (this.isTransientError(error) && retryCount < 1) {\n span.addEvent(\"transient_error_retry\");\n await new Promise((resolve) => setTimeout(resolve, 100));\n return await this.query<T>(sql, params, retryCount + 1);\n }\n\n span.recordException(error as Error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n\n if (error instanceof AppKitError) {\n throw error;\n }\n throw ConnectionError.queryFailed(error as Error);\n } finally {\n const duration = Date.now() - startTime;\n this.telemetryMetrics.queryCount.add(1);\n this.telemetryMetrics.queryDuration.record(duration);\n span.end();\n }\n },\n );\n }\n\n /**\n * Execute a transaction\n *\n * COMMIT and ROLLBACK are automatically managed by the transaction function.\n *\n * @param callback - Callback function to execute within the transaction context\n * @example\n * ```typescript\n * await connector.transaction(async (client) => {\n * await client.query('INSERT INTO accounts (name) VALUES ($1)', ['Alice']);\n * await client.query('INSERT INTO logs (action) VALUES ($1)', ['Created Alice']);\n * });\n * ```\n */\n async transaction<T>(\n callback: (client: pg.PoolClient) => Promise<T>,\n retryCount: number = 0,\n ): Promise<T> {\n const startTime = Date.now();\n return this.telemetry.startActiveSpan(\n \"lakebase.v1.transaction\",\n {\n attributes: {\n \"db.system\": \"lakebase-v1\",\n \"db.retry_count\": retryCount,\n },\n },\n async (span) => {\n const pool = await this.getPool();\n const client = await pool.connect();\n try {\n await client.query(\"BEGIN\");\n const result = await callback(client);\n await client.query(\"COMMIT\");\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n try {\n await client.query(\"ROLLBACK\");\n } catch {}\n // retry on auth failure\n if (this.isAuthError(error)) {\n span.addEvent(\"auth_error_retry\");\n client.release();\n await this.rotateCredentials();\n const newPool = await this.getPool();\n const retryClient = await newPool.connect();\n try {\n await client.query(\"BEGIN\");\n const result = await callback(retryClient);\n await client.query(\"COMMIT\");\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (retryError) {\n try {\n await retryClient.query(\"ROLLBACK\");\n } catch {}\n throw retryError;\n } finally {\n retryClient.release();\n }\n }\n\n // retry on transient errors, but only once\n if (this.isTransientError(error) && retryCount < 1) {\n span.addEvent(\"transaction_error_retry\");\n client.release();\n await new Promise((resolve) => setTimeout(resolve, 100));\n return await this.transaction<T>(callback, retryCount + 1);\n }\n span.recordException(error as Error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n\n if (error instanceof AppKitError) {\n throw error;\n }\n throw ConnectionError.transactionFailed(error as Error);\n } finally {\n client.release();\n const duration = Date.now() - startTime;\n this.telemetryMetrics.queryCount.add(1);\n this.telemetryMetrics.queryDuration.record(duration);\n span.end();\n }\n },\n );\n }\n\n /** Check if database connection is healthy */\n async healthCheck(): Promise<boolean> {\n return this.telemetry.startActiveSpan(\n \"lakebase.v1.healthCheck\",\n {},\n async (span) => {\n try {\n const result = await this.query<{ result: number }>(\n \"SELECT 1 as result\",\n );\n const healthy = result.rows[0]?.result === 1;\n span.setAttribute(\"db.healthy\", healthy);\n span.setStatus({ code: SpanStatusCode.OK });\n return healthy;\n } catch {\n span.setAttribute(\"db.healthy\", false);\n span.setStatus({ code: SpanStatusCode.ERROR });\n return false;\n } finally {\n span.end();\n }\n },\n );\n }\n\n /** Close connection pool (call on shutdown) */\n async close(): Promise<void> {\n if (this.pool) {\n await this.pool.end().catch((error: unknown) => {\n logger.error(\"Error closing connection pool: %O\", error);\n });\n this.pool = null;\n }\n this.credentials = null;\n }\n\n /** Setup graceful shutdown to close connection pools */\n shutdown(): void {\n process.on(\"SIGTERM\", () => this.close());\n process.on(\"SIGINT\", () => this.close());\n this.close();\n }\n\n /** Get Databricks workspace client - from config or execution context */\n private getWorkspaceClient(): WorkspaceClient {\n if (this.config.workspaceClient) {\n return this.config.workspaceClient;\n }\n\n try {\n const { getWorkspaceClient: getClient } = require(\"../../context\");\n const client = getClient();\n\n // cache it for subsequent calls\n this.config.workspaceClient = client;\n return client;\n } catch (_error) {\n throw ConnectionError.clientUnavailable(\n \"Databricks workspace client\",\n \"Either pass it in config or ensure ServiceContext is initialized\",\n );\n }\n }\n\n /** Get or create connection pool */\n private async getPool(): Promise<pg.Pool> {\n if (!this.connectionConfig) {\n throw ConfigurationError.invalidConnection(\n \"Lakebase\",\n \"Set PGHOST, PGDATABASE, PGAPPNAME env vars, provide a connectionString, or pass explicit config\",\n );\n }\n\n if (!this.pool) {\n const creds = await this.getCredentials();\n this.pool = this.createPool(creds);\n }\n return this.pool;\n }\n\n /** Create PostgreSQL pool */\n private createPool(credentials: {\n username: string;\n password: string;\n }): pg.Pool {\n const { host, database, port, sslMode } = this.connectionConfig;\n\n const pool = new pg.Pool({\n host,\n port,\n database,\n user: credentials.username,\n password: credentials.password,\n max: this.config.maxPoolSize,\n idleTimeoutMillis: this.config.idleTimeoutMs,\n connectionTimeoutMillis: this.config.connectionTimeoutMs,\n ssl: sslMode === \"require\" ? { rejectUnauthorized: true } : false,\n });\n\n pool.on(\"error\", (error: Error & { code?: string }) => {\n logger.error(\n \"Connection pool error: %s (code: %s)\",\n error.message,\n error.code,\n );\n });\n\n return pool;\n }\n\n /** Get or fetch credentials with caching */\n private async getCredentials(): Promise<{\n username: string;\n password: string;\n }> {\n const now = Date.now();\n\n // return cached if still valid\n if (\n this.credentials &&\n now < this.credentials.expiresAt - this.CACHE_BUFFER_MS\n ) {\n return this.credentials;\n }\n\n // fetch new credentials\n const username = await this.fetchUsername();\n const { token, expiresAt } = await this.fetchPassword();\n\n this.credentials = {\n username,\n password: token,\n expiresAt,\n };\n\n return { username, password: token };\n }\n\n /** Rotate credentials and recreate pool */\n private async rotateCredentials(): Promise<void> {\n // clear cached credentials\n this.credentials = null;\n\n if (this.pool) {\n const oldPool = this.pool;\n this.pool = null;\n oldPool.end().catch((error: unknown) => {\n logger.error(\n \"Error closing old connection pool during rotation: %O\",\n error,\n );\n });\n }\n }\n\n /** Fetch username from Databricks */\n private async fetchUsername(): Promise<string> {\n const workspaceClient = this.getWorkspaceClient();\n const user = await workspaceClient.currentUser.me();\n if (!user.userName) {\n throw AuthenticationError.userLookupFailed();\n }\n return user.userName;\n }\n\n /** Fetch password (OAuth token) from Databricks */\n private async fetchPassword(): Promise<{ token: string; expiresAt: number }> {\n const workspaceClient = this.getWorkspaceClient();\n const config = new Config({ host: workspaceClient.config.host });\n const apiClient = new ApiClient(config);\n\n if (!this.connectionConfig.appName) {\n throw ConfigurationError.resourceNotFound(\"Database app name\");\n }\n\n const credentials = await apiClient.request({\n path: `/api/2.0/database/credentials`,\n method: \"POST\",\n headers: new Headers(),\n raw: false,\n payload: {\n instance_names: [this.connectionConfig.appName],\n request_id: randomUUID(),\n },\n });\n\n if (!this.validateCredentials(credentials)) {\n throw AuthenticationError.credentialsFailed(\n this.connectionConfig.appName,\n );\n }\n\n const expiresAt = new Date(credentials.expiration_time).getTime();\n\n return { token: credentials.token, expiresAt };\n }\n\n /** Check if error is auth failure */\n private isAuthError(error: unknown): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n (error as any).code === \"28P01\"\n );\n }\n\n /** Check if error is transient */\n private isTransientError(error: unknown): boolean {\n if (typeof error !== \"object\" || error === null || !(\"code\" in error)) {\n return false;\n }\n\n const code = (error as any).code;\n return (\n code === \"ECONNRESET\" ||\n code === \"ECONNREFUSED\" ||\n code === \"ETIMEDOUT\" ||\n code === \"57P01\" || // admin_shutdown\n code === \"57P03\" || // cannot_connect_now\n code === \"08006\" || // connection_failure\n code === \"08003\" || // connection_does_not_exist\n code === \"08000\" // connection_exception\n );\n }\n\n /** Type guard for credentials */\n private validateCredentials(\n value: unknown,\n ): value is { token: string; expiration_time: string } {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n\n const credentials = value as { token: string; expiration_time: string };\n return (\n \"token\" in credentials &&\n typeof credentials.token === \"string\" &&\n \"expiration_time\" in credentials &&\n typeof credentials.expiration_time === \"string\" &&\n new Date(credentials.expiration_time).getTime() > Date.now()\n );\n }\n\n /** Parse connection configuration from config or environment */\n private parseConnectionConfig(): LakebaseV1ConnectionConfig {\n if (this.config.connectionString) {\n return this.parseConnectionString(this.config.connectionString);\n }\n\n // get connection from config\n if (this.config.host && this.config.database && this.config.appName) {\n return {\n host: this.config.host,\n database: this.config.database,\n port: this.config.port ?? 5432,\n sslMode: this.config.sslMode ?? \"require\",\n appName: this.config.appName,\n };\n }\n\n // get connection from environment variables\n const pgHost = process.env.PGHOST;\n const pgDatabase = process.env.PGDATABASE;\n const pgAppName = process.env.PGAPPNAME;\n if (!pgHost || !pgDatabase || !pgAppName) {\n throw ConfigurationError.invalidConnection(\n \"Lakebase\",\n \"Required env vars: PGHOST, PGDATABASE, PGAPPNAME. Optional: PGPORT (default: 5432), PGSSLMODE (default: require)\",\n );\n }\n const pgPort = process.env.PGPORT;\n const port = pgPort ? parseInt(pgPort, 10) : 5432;\n\n if (Number.isNaN(port)) {\n throw ValidationError.invalidValue(\"port\", pgPort, \"a number\");\n }\n\n const pgSSLMode = process.env.PGSSLMODE;\n const sslMode =\n (pgSSLMode as \"require\" | \"disable\" | \"prefer\") || \"require\";\n\n return {\n host: pgHost,\n database: pgDatabase,\n port,\n sslMode,\n appName: pgAppName,\n };\n }\n\n private parseConnectionString(\n connectionString: string,\n ): LakebaseV1ConnectionConfig {\n const url = new URL(connectionString);\n const appName = url.searchParams.get(\"appName\");\n if (!appName) {\n throw ConfigurationError.missingConnectionParam(\"appName\");\n }\n\n return {\n host: url.hostname,\n database: url.pathname.slice(1), // remove leading slash\n port: url.port ? parseInt(url.port, 10) : 5432,\n sslMode:\n (url.searchParams.get(\"sslmode\") as \"require\" | \"disable\" | \"prefer\") ??\n \"require\",\n appName: appName,\n };\n }\n}\n"],"mappings":";;;;;;;;AA2BA,MAAM,SAAS,aAAa,yBAAyB"}
@@ -1,3 +1,3 @@
1
- import { LakebaseV1Connector } from "./client.js";
1
+ import "./client.js";
2
2
 
3
3
  export { };
@@ -1,11 +1,11 @@
1
1
  import { createLogger } from "../../logging/logger.js";
2
- import { TelemetryManager } from "../../telemetry/telemetry-manager.js";
3
- import { SpanKind, SpanStatusCode } from "../../telemetry/index.js";
4
2
  import { AppKitError } from "../../errors/base.js";
5
3
  import { ConnectionError } from "../../errors/connection.js";
6
4
  import { ExecutionError } from "../../errors/execution.js";
7
5
  import { ValidationError } from "../../errors/validation.js";
8
6
  import { init_errors } from "../../errors/index.js";
7
+ import { TelemetryManager } from "../../telemetry/telemetry-manager.js";
8
+ import { SpanKind, SpanStatusCode } from "../../telemetry/index.js";
9
9
  import { ArrowStreamProcessor } from "../../stream/arrow-stream-processor.js";
10
10
  import { executeStatementDefaults } from "./defaults.js";
11
11
  import { Context } from "@databricks/sdk-experimental";
@@ -56,12 +56,6 @@ function getWarehouseId() {
56
56
  function getWorkspaceId() {
57
57
  return getExecutionContext().workspaceId;
58
58
  }
59
- /**
60
- * Check if currently running in a user context.
61
- */
62
- function isInUserContext() {
63
- return executionContextStorage.getStore() !== void 0;
64
- }
65
59
  var executionContextStorage;
66
60
  var init_execution_context = __esmMin((() => {
67
61
  init_service_context();
@@ -71,5 +65,5 @@ var init_execution_context = __esmMin((() => {
71
65
 
72
66
  //#endregion
73
67
  init_execution_context();
74
- export { getCurrentUserId, getExecutionContext, getWarehouseId, getWorkspaceClient, getWorkspaceId, init_execution_context, isInUserContext, runInUserContext };
68
+ export { getCurrentUserId, getExecutionContext, getWarehouseId, getWorkspaceClient, getWorkspaceId, init_execution_context, runInUserContext };
75
69
  //# sourceMappingURL=execution-context.js.map
@@ -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;;;;;AAM/B,SAAgB,kBAA2B;AAEzC,QADY,wBAAwB,UAAU,KAC/B;;;;uBAhFkC;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 { 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,27 +1,15 @@
1
- import { __esmMin, __exportAll } from "../_virtual/_rolldown/runtime.js";
1
+ import { __esmMin } from "../_virtual/_rolldown/runtime.js";
2
2
  import { ServiceContext, init_service_context } from "./service-context.js";
3
- import { init_user_context, isUserContext } from "./user-context.js";
4
- import { getCurrentUserId, getExecutionContext, getWarehouseId, getWorkspaceClient, getWorkspaceId, init_execution_context, isInUserContext, runInUserContext } from "./execution-context.js";
3
+ import { isUserContext } from "./user-context.js";
4
+ import { getCurrentUserId, getExecutionContext, getWarehouseId, getWorkspaceClient, getWorkspaceId, init_execution_context, runInUserContext } from "./execution-context.js";
5
5
 
6
6
  //#region src/context/index.ts
7
- var context_exports = /* @__PURE__ */ __exportAll({
8
- ServiceContext: () => ServiceContext,
9
- getCurrentUserId: () => getCurrentUserId,
10
- getExecutionContext: () => getExecutionContext,
11
- getWarehouseId: () => getWarehouseId,
12
- getWorkspaceClient: () => getWorkspaceClient,
13
- getWorkspaceId: () => getWorkspaceId,
14
- isInUserContext: () => isInUserContext,
15
- isUserContext: () => isUserContext,
16
- runInUserContext: () => runInUserContext
17
- });
18
7
  var init_context = __esmMin((() => {
19
8
  init_execution_context();
20
9
  init_service_context();
21
- init_user_context();
22
10
  }));
23
11
 
24
12
  //#endregion
25
13
  init_context();
26
- export { context_exports, init_context };
14
+ export { init_context };
27
15
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/context/index.ts"],"sourcesContent":["export {\n getCurrentUserId,\n getExecutionContext,\n getWarehouseId,\n getWorkspaceClient,\n getWorkspaceId,\n isInUserContext,\n runInUserContext,\n} from \"./execution-context\";\nexport { ServiceContext, type ServiceContextState } from \"./service-context\";\nexport {\n type ExecutionContext,\n isUserContext,\n type UserContext,\n} from \"./user-context\";\n"],"mappings":";;;;;;;;;;;;;;;;;;yBAQ6B;uBACgD;oBAKrD"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/context/index.ts"],"sourcesContent":["export {\n getCurrentUserId,\n getExecutionContext,\n getWarehouseId,\n getWorkspaceClient,\n getWorkspaceId,\n isInUserContext,\n runInUserContext,\n} from \"./execution-context\";\nexport { ServiceContext, type ServiceContextState } from \"./service-context\";\nexport {\n type ExecutionContext,\n isUserContext,\n type UserContext,\n} from \"./user-context\";\n"],"mappings":";;;;;;;yBAQ6B;uBACgD"}
@@ -1,8 +1,8 @@
1
1
  import { TelemetryManager } from "../telemetry/telemetry-manager.js";
2
2
  import "../telemetry/index.js";
3
+ import { CacheManager } from "../cache/index.js";
3
4
  import { ServiceContext } from "../context/service-context.js";
4
5
  import { init_context } from "../context/index.js";
5
- import { CacheManager } from "../cache/index.js";
6
6
  import { ResourceRegistry } from "../registry/resource-registry.js";
7
7
  import "../registry/index.js";
8
8
 
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ import { CacheConfig } from "./shared/src/cache.js";
3
3
  import { StreamExecutionSettings } from "./shared/src/execute.js";
4
4
  import { isSQLTypeMarker, sql } from "./shared/src/sql/helpers.js";
5
5
  import { CacheManager } from "./cache/index.js";
6
+ import { DatabaseCredential, GenerateDatabaseCredentialRequest, LakebasePoolConfig, RequestedClaims, RequestedClaimsPermissionSet, RequestedResource, createLakebasePool, generateDatabaseCredential, getLakebaseOrmConfig, getLakebasePgConfig, getWorkspaceClient } from "./connectors/lakebase/index.js";
6
7
  import { getExecutionContext } from "./context/execution-context.js";
7
8
  import { ITelemetry, TelemetryConfig } from "./telemetry/types.js";
8
9
  import { Counter, Histogram, SeverityNumber, Span, SpanStatusCode } from "./telemetry/index.js";
@@ -24,4 +25,4 @@ import { getPluginManifest, getResourceRequirements } from "./registry/manifest-
24
25
  import { ResourceRegistry } from "./registry/resource-registry.js";
25
26
  import { server } from "./plugins/server/index.js";
26
27
  import { appKitTypesPlugin } from "./type-generator/vite-plugin.js";
27
- export { AppKitError, AuthenticationError, type BasePluginConfig, type CacheConfig, CacheManager, type ConfigSchema, ConfigurationError, ConnectionError, type Counter, ExecutionError, type Histogram, type IAppRouter, type ITelemetry, InitializationError, Plugin, type PluginManifest, type ResourceEntry, type ResourceFieldEntry, type ResourcePermission, ResourceRegistry, type ResourceRequirement, ResourceType, ServerError, SeverityNumber, type Span, SpanStatusCode, type StreamExecutionSettings, type TelemetryConfig, TunnelError, ValidationError, type ValidationResult, analytics, appKitTypesPlugin, createApp, getExecutionContext, getPluginManifest, getResourceRequirements, isSQLTypeMarker, server, sql, toPlugin };
28
+ export { AppKitError, AuthenticationError, type BasePluginConfig, type CacheConfig, CacheManager, type ConfigSchema, ConfigurationError, ConnectionError, type Counter, type DatabaseCredential, ExecutionError, type GenerateDatabaseCredentialRequest, type Histogram, type IAppRouter, type ITelemetry, InitializationError, type LakebasePoolConfig, Plugin, type PluginManifest, type RequestedClaims, RequestedClaimsPermissionSet, type RequestedResource, type ResourceEntry, type ResourceFieldEntry, type ResourcePermission, ResourceRegistry, type ResourceRequirement, ResourceType, ServerError, SeverityNumber, type Span, SpanStatusCode, type StreamExecutionSettings, type TelemetryConfig, TunnelError, ValidationError, type ValidationResult, analytics, appKitTypesPlugin, createApp, createLakebasePool, generateDatabaseCredential, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getWorkspaceClient, isSQLTypeMarker, server, sql, toPlugin };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { isSQLTypeMarker, sql } from "./shared/src/sql/helpers.js";
2
- import { SeverityNumber, SpanStatusCode } from "./telemetry/index.js";
2
+ import { RequestedClaimsPermissionSet, createLakebasePool, generateDatabaseCredential, getLakebaseOrmConfig, getLakebasePgConfig, getWorkspaceClient } from "./connectors/lakebase/index.js";
3
3
  import { AppKitError } from "./errors/base.js";
4
4
  import { AuthenticationError } from "./errors/authentication.js";
5
5
  import { ConfigurationError } from "./errors/configuration.js";
@@ -10,9 +10,10 @@ import { ServerError } from "./errors/server.js";
10
10
  import { TunnelError } from "./errors/tunnel.js";
11
11
  import { ValidationError } from "./errors/validation.js";
12
12
  import { init_errors } from "./errors/index.js";
13
+ import { SeverityNumber, SpanStatusCode } from "./telemetry/index.js";
14
+ import { CacheManager } from "./cache/index.js";
13
15
  import { getExecutionContext } from "./context/execution-context.js";
14
16
  import { init_context } from "./context/index.js";
15
- import { CacheManager } from "./cache/index.js";
16
17
  import { ResourceType } from "./registry/types.js";
17
18
  import { getPluginManifest, getResourceRequirements } from "./registry/manifest-loader.js";
18
19
  import { ResourceRegistry } from "./registry/resource-registry.js";
@@ -32,5 +33,5 @@ init_context();
32
33
  init_errors();
33
34
 
34
35
  //#endregion
35
- export { AppKitError, AuthenticationError, CacheManager, ConfigurationError, ConnectionError, ExecutionError, InitializationError, Plugin, ResourceRegistry, ResourceType, ServerError, SeverityNumber, SpanStatusCode, TunnelError, ValidationError, analytics, appKitTypesPlugin, createApp, getExecutionContext, getPluginManifest, getResourceRequirements, isSQLTypeMarker, server, sql, toPlugin };
36
+ export { AppKitError, AuthenticationError, CacheManager, ConfigurationError, ConnectionError, ExecutionError, InitializationError, Plugin, RequestedClaimsPermissionSet, ResourceRegistry, ResourceType, ServerError, SeverityNumber, SpanStatusCode, TunnelError, ValidationError, analytics, appKitTypesPlugin, createApp, createLakebasePool, generateDatabaseCredential, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getWorkspaceClient, isSQLTypeMarker, server, sql, toPlugin };
36
37
  //# sourceMappingURL=index.js.map