@databricks/appkit 0.26.0 → 0.27.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 (50) hide show
  1. package/CLAUDE.md +7 -0
  2. package/dist/appkit/package.js +1 -1
  3. package/dist/connectors/index.js +2 -0
  4. package/dist/connectors/jobs/client.d.ts +2 -0
  5. package/dist/connectors/jobs/client.js +132 -0
  6. package/dist/connectors/jobs/client.js.map +1 -0
  7. package/dist/connectors/jobs/index.d.ts +2 -0
  8. package/dist/connectors/jobs/index.js +3 -0
  9. package/dist/connectors/jobs/types.d.ts +10 -0
  10. package/dist/connectors/jobs/types.d.ts.map +1 -0
  11. package/dist/connectors/lakebase-v1/client.js.map +1 -1
  12. package/dist/connectors/sql-warehouse/client.js +1 -0
  13. package/dist/connectors/sql-warehouse/client.js.map +1 -1
  14. package/dist/index.d.ts +6 -1
  15. package/dist/index.js +2 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/plugins/index.d.ts +3 -0
  18. package/dist/plugins/index.js +2 -0
  19. package/dist/plugins/jobs/defaults.js +45 -0
  20. package/dist/plugins/jobs/defaults.js.map +1 -0
  21. package/dist/plugins/jobs/index.d.ts +2 -0
  22. package/dist/plugins/jobs/index.js +3 -0
  23. package/dist/plugins/jobs/manifest.js +40 -0
  24. package/dist/plugins/jobs/manifest.js.map +1 -0
  25. package/dist/plugins/jobs/params.js +35 -0
  26. package/dist/plugins/jobs/params.js.map +1 -0
  27. package/dist/plugins/jobs/plugin.d.ts +66 -0
  28. package/dist/plugins/jobs/plugin.d.ts.map +1 -0
  29. package/dist/plugins/jobs/plugin.js +531 -0
  30. package/dist/plugins/jobs/plugin.js.map +1 -0
  31. package/dist/plugins/jobs/types.d.ts +84 -0
  32. package/dist/plugins/jobs/types.d.ts.map +1 -0
  33. package/dist/registry/manifest-loader.d.ts +2 -2
  34. package/dist/registry/manifest-loader.d.ts.map +1 -1
  35. package/dist/stream/stream-manager.d.ts.map +1 -1
  36. package/dist/stream/stream-manager.js +6 -0
  37. package/dist/stream/stream-manager.js.map +1 -1
  38. package/docs/api/appkit/Interface.BasePluginConfig.md +4 -0
  39. package/docs/api/appkit/Interface.IJobsConfig.md +86 -0
  40. package/docs/api/appkit/Interface.JobAPI.md +163 -0
  41. package/docs/api/appkit/Interface.JobConfig.md +36 -0
  42. package/docs/api/appkit/Interface.JobsConnectorConfig.md +10 -0
  43. package/docs/api/appkit/TypeAlias.JobHandle.md +29 -0
  44. package/docs/api/appkit/TypeAlias.JobsExport.md +34 -0
  45. package/docs/api/appkit.md +6 -0
  46. package/docs/plugins/jobs.md +252 -0
  47. package/docs/plugins.md +2 -1
  48. package/llms.txt +7 -0
  49. package/package.json +2 -1
  50. package/sbom.cdx.json +1 -1
package/CLAUDE.md CHANGED
@@ -48,6 +48,7 @@ npx @databricks/appkit docs <query>
48
48
  - [Execution context](./docs/plugins/execution-context.md): AppKit manages Databricks authentication via two contexts:
49
49
  - [Files plugin](./docs/plugins/files.md): File operations against Databricks Unity Catalog Volumes. Supports listing, reading, downloading, uploading, deleting, and previewing files with built-in caching, retry, and timeout handling via the execution interceptor pipeline.
50
50
  - [Genie plugin](./docs/plugins/genie.md): Integrates Databricks AI/BI Genie spaces into your AppKit application, enabling natural language data queries via a conversational interface.
51
+ - [Jobs plugin](./docs/plugins/jobs.md): Trigger and monitor Databricks Lakeflow Jobs from your AppKit application.
51
52
  - [Lakebase plugin](./docs/plugins/lakebase.md): Provides a PostgreSQL connection pool for Databricks Lakebase Autoscaling with automatic OAuth token refresh.
52
53
  - [Model Serving plugin](./docs/plugins/model-serving.md): Provides an authenticated proxy to Databricks Model Serving endpoints, with invoke and streaming support.
53
54
  - [Plugin management](./docs/plugins/plugin-management.md): AppKit includes a CLI for managing plugins. All commands are available under npx @databricks/appkit plugin.
@@ -93,7 +94,11 @@ npx @databricks/appkit docs <query>
93
94
  - [Interface: FilePolicyUser](./docs/api/appkit/Interface.FilePolicyUser.md): Minimal user identity passed to the policy function.
94
95
  - [Interface: FileResource](./docs/api/appkit/Interface.FileResource.md): Describes the file or directory being acted upon.
95
96
  - [Interface: GenerateDatabaseCredentialRequest](./docs/api/appkit/Interface.GenerateDatabaseCredentialRequest.md): Request parameters for generating database OAuth credentials
97
+ - [Interface: IJobsConfig](./docs/api/appkit/Interface.IJobsConfig.md): Configuration for the Jobs plugin.
96
98
  - [Interface: ITelemetry](./docs/api/appkit/Interface.ITelemetry.md): Plugin-facing interface for OpenTelemetry instrumentation.
99
+ - [Interface: JobAPI](./docs/api/appkit/Interface.JobAPI.md): User-facing API for a single configured job.
100
+ - [Interface: JobConfig](./docs/api/appkit/Interface.JobConfig.md): Per-job configuration options.
101
+ - [Interface: JobsConnectorConfig](./docs/api/appkit/Interface.JobsConnectorConfig.md): Properties
97
102
  - [Interface: LakebasePoolConfig](./docs/api/appkit/Interface.LakebasePoolConfig.md): Configuration for creating a Lakebase connection pool
98
103
  - [Interface: PluginManifest<TName>](./docs/api/appkit/Interface.PluginManifest.md): Plugin manifest that declares metadata and resource requirements.
99
104
  - [Interface: RequestedClaims](./docs/api/appkit/Interface.RequestedClaims.md): Optional claims for fine-grained Unity Catalog table permissions
@@ -111,6 +116,8 @@ npx @databricks/appkit docs <query>
111
116
  - [Type Alias: FileAction](./docs/api/appkit/TypeAlias.FileAction.md): Every action the files plugin can perform.
112
117
  - [Type Alias: FilePolicy()](./docs/api/appkit/TypeAlias.FilePolicy.md): A policy function that decides whether user may perform action on
113
118
  - [Type Alias: IAppRouter](./docs/api/appkit/TypeAlias.IAppRouter.md): Express router type for plugin route registration
119
+ - [Type Alias: JobHandle](./docs/api/appkit/TypeAlias.JobHandle.md): Job handle returned by appkit.jobs("etl").
120
+ - [Type Alias: JobsExport()](./docs/api/appkit/TypeAlias.JobsExport.md): Public API shape of the jobs plugin.
114
121
  - [Type Alias: PluginData<T, U, N>](./docs/api/appkit/TypeAlias.PluginData.md): Tuple of plugin class, config, and name. Created by toPlugin() and passed to createApp().
115
122
  - [Type Alias: ResourcePermission](./docs/api/appkit/TypeAlias.ResourcePermission.md): Union of all possible permission levels across all resource types.
116
123
  - [Type Alias: ServingFactory](./docs/api/appkit/TypeAlias.ServingFactory.md): Factory function returned by AppKit.serving.
@@ -1,6 +1,6 @@
1
1
  //#region package.json
2
2
  var name = "@databricks/appkit";
3
- var version = "0.26.0";
3
+ var version = "0.27.0";
4
4
 
5
5
  //#endregion
6
6
  export { name, version };
@@ -4,6 +4,8 @@ import { FilesConnector } from "./files/client.js";
4
4
  import "./files/index.js";
5
5
  import { GenieConnector } from "./genie/client.js";
6
6
  import "./genie/index.js";
7
+ import { JobsConnector } from "./jobs/client.js";
8
+ import "./jobs/index.js";
7
9
  import "./lakebase-v1/index.js";
8
10
  import { SQLWarehouseConnector } from "./sql-warehouse/client.js";
9
11
  import "./sql-warehouse/index.js";
@@ -0,0 +1,2 @@
1
+ import "./types.js";
2
+ import { WorkspaceClient, jobs } from "@databricks/sdk-experimental";
@@ -0,0 +1,132 @@
1
+ import { createLogger } from "../../logging/logger.js";
2
+ import { AppKitError } from "../../errors/base.js";
3
+ import { ExecutionError } from "../../errors/execution.js";
4
+ import { init_errors } from "../../errors/index.js";
5
+ import { TelemetryManager } from "../../telemetry/telemetry-manager.js";
6
+ import { SpanKind, SpanStatusCode } from "../../telemetry/index.js";
7
+ import { Context } from "@databricks/sdk-experimental";
8
+
9
+ //#region src/connectors/jobs/client.ts
10
+ init_errors();
11
+ const logger = createLogger("connectors:jobs");
12
+ var JobsConnector = class {
13
+ name = "jobs";
14
+ config;
15
+ telemetry;
16
+ telemetryMetrics;
17
+ constructor(config) {
18
+ this.config = config;
19
+ this.telemetry = TelemetryManager.getProvider(this.name, this.config.telemetry);
20
+ this.telemetryMetrics = {
21
+ apiCallCount: this.telemetry.getMeter().createCounter("jobs.api_call.count", {
22
+ description: "Total number of Jobs API calls",
23
+ unit: "1"
24
+ }),
25
+ apiCallDuration: this.telemetry.getMeter().createHistogram("jobs.api_call.duration", {
26
+ description: "Duration of Jobs API calls",
27
+ unit: "ms"
28
+ })
29
+ };
30
+ }
31
+ async submitRun(workspaceClient, request, signal) {
32
+ return this._callApi("submit", async () => {
33
+ return workspaceClient.jobs.submit(request, this._createContext(signal));
34
+ });
35
+ }
36
+ async runNow(workspaceClient, request, signal) {
37
+ return this._callApi("runNow", async () => {
38
+ return workspaceClient.jobs.runNow(request, this._createContext(signal));
39
+ });
40
+ }
41
+ async getRun(workspaceClient, request, signal) {
42
+ return this._callApi("getRun", async () => {
43
+ return workspaceClient.jobs.getRun(request, this._createContext(signal));
44
+ });
45
+ }
46
+ async getRunOutput(workspaceClient, request, signal) {
47
+ return this._callApi("getRunOutput", async () => {
48
+ return workspaceClient.jobs.getRunOutput(request, this._createContext(signal));
49
+ });
50
+ }
51
+ async cancelRun(workspaceClient, request, signal) {
52
+ await this._callApi("cancelRun", async () => {
53
+ return workspaceClient.jobs.cancelRun(request, this._createContext(signal));
54
+ });
55
+ }
56
+ async listRuns(workspaceClient, request, signal) {
57
+ return this._callApi("listRuns", async () => {
58
+ const runs = [];
59
+ const limit = Math.max(1, Math.min(request.limit ?? 100, 100));
60
+ for await (const run of workspaceClient.jobs.listRuns({
61
+ ...request,
62
+ limit
63
+ }, this._createContext(signal))) {
64
+ runs.push(run);
65
+ if (runs.length >= limit) break;
66
+ }
67
+ return runs;
68
+ });
69
+ }
70
+ async getJob(workspaceClient, request, signal) {
71
+ return this._callApi("getJob", async () => {
72
+ return workspaceClient.jobs.get(request, this._createContext(signal));
73
+ });
74
+ }
75
+ async _callApi(operation, fn) {
76
+ const startTime = Date.now();
77
+ let success = false;
78
+ return this.telemetry.startActiveSpan(`jobs.${operation}`, {
79
+ kind: SpanKind.CLIENT,
80
+ attributes: { "jobs.operation": operation }
81
+ }, async (span) => {
82
+ try {
83
+ const result = await fn();
84
+ success = true;
85
+ span.setStatus({ code: SpanStatusCode.OK });
86
+ return result;
87
+ } catch (error) {
88
+ span.recordException(error);
89
+ span.setStatus({
90
+ code: SpanStatusCode.ERROR,
91
+ message: error instanceof Error ? error.message : String(error)
92
+ });
93
+ if (error instanceof AppKitError) throw error;
94
+ if (error instanceof Error && "statusCode" in error && typeof error.statusCode === "number") throw error;
95
+ throw new ExecutionError(`Jobs API call failed: ${error instanceof Error ? error.message : String(error)}`);
96
+ } finally {
97
+ const duration = Date.now() - startTime;
98
+ span.end();
99
+ this.telemetryMetrics.apiCallCount.add(1, {
100
+ operation,
101
+ success: success.toString()
102
+ });
103
+ this.telemetryMetrics.apiCallDuration.record(duration, {
104
+ operation,
105
+ success: success.toString()
106
+ });
107
+ logger.event()?.setContext("jobs", {
108
+ operation,
109
+ duration_ms: duration,
110
+ success
111
+ });
112
+ }
113
+ }, {
114
+ name: this.name,
115
+ includePrefix: true
116
+ });
117
+ }
118
+ _createContext(signal) {
119
+ return new Context({ cancellationToken: {
120
+ get isCancellationRequested() {
121
+ return signal?.aborted ?? false;
122
+ },
123
+ onCancellationRequested: (cb) => {
124
+ signal?.addEventListener("abort", cb, { once: true });
125
+ }
126
+ } });
127
+ }
128
+ };
129
+
130
+ //#endregion
131
+ export { JobsConnector };
132
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../../src/connectors/jobs/client.ts"],"sourcesContent":["import {\n Context,\n type jobs,\n type WorkspaceClient,\n} from \"@databricks/sdk-experimental\";\nimport { AppKitError, ExecutionError } from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport type { TelemetryProvider } from \"../../telemetry\";\nimport {\n type Counter,\n type Histogram,\n type Span,\n SpanKind,\n SpanStatusCode,\n TelemetryManager,\n} from \"../../telemetry\";\nimport type { JobsConnectorConfig } from \"./types\";\n\nconst logger = createLogger(\"connectors:jobs\");\n\nexport class JobsConnector {\n private readonly name = \"jobs\";\n private readonly config: JobsConnectorConfig;\n private readonly telemetry: TelemetryProvider;\n private readonly telemetryMetrics: {\n apiCallCount: Counter;\n apiCallDuration: Histogram;\n };\n\n constructor(config: JobsConnectorConfig) {\n this.config = config;\n this.telemetry = TelemetryManager.getProvider(\n this.name,\n this.config.telemetry,\n );\n this.telemetryMetrics = {\n apiCallCount: this.telemetry\n .getMeter()\n .createCounter(\"jobs.api_call.count\", {\n description: \"Total number of Jobs API calls\",\n unit: \"1\",\n }),\n apiCallDuration: this.telemetry\n .getMeter()\n .createHistogram(\"jobs.api_call.duration\", {\n description: \"Duration of Jobs API calls\",\n unit: \"ms\",\n }),\n };\n }\n\n async submitRun(\n workspaceClient: WorkspaceClient,\n request: jobs.SubmitRun,\n signal?: AbortSignal,\n ): Promise<jobs.SubmitRunResponse> {\n return this._callApi(\"submit\", async () => {\n return workspaceClient.jobs.submit(request, this._createContext(signal));\n });\n }\n\n async runNow(\n workspaceClient: WorkspaceClient,\n request: jobs.RunNow,\n signal?: AbortSignal,\n ): Promise<jobs.RunNowResponse> {\n return this._callApi(\"runNow\", async () => {\n return workspaceClient.jobs.runNow(request, this._createContext(signal));\n });\n }\n\n async getRun(\n workspaceClient: WorkspaceClient,\n request: jobs.GetRunRequest,\n signal?: AbortSignal,\n ): Promise<jobs.Run> {\n return this._callApi(\"getRun\", async () => {\n return workspaceClient.jobs.getRun(request, this._createContext(signal));\n });\n }\n\n async getRunOutput(\n workspaceClient: WorkspaceClient,\n request: jobs.GetRunOutputRequest,\n signal?: AbortSignal,\n ): Promise<jobs.RunOutput> {\n return this._callApi(\"getRunOutput\", async () => {\n return workspaceClient.jobs.getRunOutput(\n request,\n this._createContext(signal),\n );\n });\n }\n\n async cancelRun(\n workspaceClient: WorkspaceClient,\n request: jobs.CancelRun,\n signal?: AbortSignal,\n ): Promise<void> {\n await this._callApi(\"cancelRun\", async () => {\n return workspaceClient.jobs.cancelRun(\n request,\n this._createContext(signal),\n );\n });\n }\n\n async listRuns(\n workspaceClient: WorkspaceClient,\n request: jobs.ListRunsRequest,\n signal?: AbortSignal,\n ): Promise<jobs.BaseRun[]> {\n return this._callApi(\"listRuns\", async () => {\n const runs: jobs.BaseRun[] = [];\n const limit = Math.max(1, Math.min(request.limit ?? 100, 100));\n for await (const run of workspaceClient.jobs.listRuns(\n { ...request, limit },\n this._createContext(signal),\n )) {\n runs.push(run);\n if (runs.length >= limit) break;\n }\n return runs;\n });\n }\n\n async getJob(\n workspaceClient: WorkspaceClient,\n request: jobs.GetJobRequest,\n signal?: AbortSignal,\n ): Promise<jobs.Job> {\n return this._callApi(\"getJob\", async () => {\n return workspaceClient.jobs.get(request, this._createContext(signal));\n });\n }\n\n private async _callApi<T>(\n operation: string,\n fn: () => Promise<T>,\n ): Promise<T> {\n const startTime = Date.now();\n let success = false;\n\n return this.telemetry.startActiveSpan(\n `jobs.${operation}`,\n {\n kind: SpanKind.CLIENT,\n attributes: {\n \"jobs.operation\": operation,\n },\n },\n async (span: Span) => {\n try {\n const result = await fn();\n success = true;\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n if (error instanceof AppKitError) {\n throw error;\n }\n // Preserve SDK ApiError (and any error with a numeric statusCode)\n // so Plugin.execute() can map it to the correct HTTP status.\n if (\n error instanceof Error &&\n \"statusCode\" in error &&\n typeof (error as Record<string, unknown>).statusCode === \"number\"\n ) {\n throw error;\n }\n throw new ExecutionError(\n `Jobs API call failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n } finally {\n const duration = Date.now() - startTime;\n span.end();\n this.telemetryMetrics.apiCallCount.add(1, {\n operation,\n success: success.toString(),\n });\n this.telemetryMetrics.apiCallDuration.record(duration, {\n operation,\n success: success.toString(),\n });\n\n logger.event()?.setContext(\"jobs\", {\n operation,\n duration_ms: duration,\n success,\n });\n }\n },\n { name: this.name, includePrefix: true },\n );\n }\n\n private _createContext(signal?: AbortSignal) {\n return new Context({\n cancellationToken: {\n // Getter — evaluated on every read so SDK code paths that poll\n // (rather than subscribe) observe cancellation live.\n get isCancellationRequested() {\n return signal?.aborted ?? false;\n },\n onCancellationRequested: (cb: () => void) => {\n signal?.addEventListener(\"abort\", cb, { once: true });\n },\n },\n });\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { JobsConnectorConfig } from "./types.js";
2
+ import "./client.js";
@@ -0,0 +1,3 @@
1
+ import { JobsConnector } from "./client.js";
2
+
3
+ export { };
@@ -0,0 +1,10 @@
1
+ import { TelemetryOptions } from "../../shared/src/plugin.js";
2
+ import "../../shared/src/index.js";
3
+
4
+ //#region src/connectors/jobs/types.d.ts
5
+ interface JobsConnectorConfig {
6
+ telemetry?: TelemetryOptions;
7
+ }
8
+ //#endregion
9
+ export { JobsConnectorConfig };
10
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/connectors/jobs/types.ts"],"mappings":";;;;UAEiB,mBAAA;EACf,SAAA,GAAY,gBAAA;AAAA"}
@@ -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 AppKitError,\n AuthenticationError,\n ConfigurationError,\n ConnectionError,\n ValidationError,\n} from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport {\n type Counter,\n type Histogram,\n SpanStatusCode,\n TelemetryManager,\n type TelemetryProvider,\n} from \"../../telemetry\";\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
+ {"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 AppKitError,\n AuthenticationError,\n ConfigurationError,\n ConnectionError,\n ValidationError,\n} from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport {\n type Counter,\n type Histogram,\n SpanStatusCode,\n TelemetryManager,\n type TelemetryProvider,\n} from \"../../telemetry\";\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 logger.error(\n \"Query execution failed: %s (code=%s)\",\n error instanceof Error ? error.message : String(error),\n (error as any)?.code,\n );\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 logger.error(\n \"Transaction execution failed: %s (code=%s)\",\n error instanceof Error ? error.message : String(error),\n (error as any)?.code,\n );\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"}
@@ -135,6 +135,7 @@ var SQLWarehouseConnector = class {
135
135
  code: SpanStatusCode.ERROR,
136
136
  message: error instanceof Error ? error.message : String(error)
137
137
  });
138
+ logger.error("Statement execution failed: %s", error instanceof Error ? error.message : String(error));
138
139
  }
139
140
  if (error instanceof AppKitError) throw error;
140
141
  throw ExecutionError.statementFailed(error instanceof Error ? error.message : String(error));
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../../../src/connectors/sql-warehouse/client.ts"],"sourcesContent":["import {\n Context,\n type sql,\n type WorkspaceClient,\n} from \"@databricks/sdk-experimental\";\nimport type { TelemetryOptions } from \"shared\";\nimport {\n AppKitError,\n ConnectionError,\n ExecutionError,\n ValidationError,\n} from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport { ArrowStreamProcessor } from \"../../stream/arrow-stream-processor\";\nimport type { TelemetryProvider } from \"../../telemetry\";\nimport {\n type Counter,\n type Histogram,\n type Span,\n SpanKind,\n SpanStatusCode,\n TelemetryManager,\n} from \"../../telemetry\";\nimport { executeStatementDefaults } from \"./defaults\";\n\nconst logger = createLogger(\"connectors:sql-warehouse\");\n\ninterface SQLWarehouseConfig {\n timeout?: number;\n telemetry?: TelemetryOptions;\n}\n\nexport class SQLWarehouseConnector {\n private readonly name = \"sql-warehouse\";\n\n private config: SQLWarehouseConfig;\n\n // Lazy-initialized: only created when Arrow format is used\n private _arrowProcessor: ArrowStreamProcessor | null = null;\n // telemetry\n private readonly telemetry: TelemetryProvider;\n private readonly telemetryMetrics: {\n queryCount: Counter;\n queryDuration: Histogram;\n };\n\n constructor(config: SQLWarehouseConfig) {\n this.config = config;\n\n this.telemetry = TelemetryManager.getProvider(\n this.name,\n this.config.telemetry,\n );\n this.telemetryMetrics = {\n queryCount: this.telemetry.getMeter().createCounter(\"query.count\", {\n description: \"Total number of queries executed\",\n unit: \"1\",\n }),\n queryDuration: this.telemetry\n .getMeter()\n .createHistogram(\"query.duration\", {\n description: \"Duration of queries executed\",\n unit: \"ms\",\n }),\n };\n }\n\n /**\n * Lazily initializes and returns the ArrowStreamProcessor.\n * Only created on first Arrow format query to avoid unnecessary allocation.\n */\n private get arrowProcessor(): ArrowStreamProcessor {\n if (!this._arrowProcessor) {\n this._arrowProcessor = new ArrowStreamProcessor({\n timeout: this.config.timeout || executeStatementDefaults.timeout,\n maxConcurrentDownloads:\n ArrowStreamProcessor.DEFAULT_MAX_CONCURRENT_DOWNLOADS,\n retries: ArrowStreamProcessor.DEFAULT_RETRIES,\n });\n }\n return this._arrowProcessor;\n }\n\n async executeStatement(\n workspaceClient: WorkspaceClient,\n input: sql.ExecuteStatementRequest,\n signal?: AbortSignal,\n ) {\n const startTime = Date.now();\n let success = false;\n\n // if signal is aborted, throw an error\n if (signal?.aborted) {\n throw ExecutionError.canceled();\n }\n\n return this.telemetry.startActiveSpan(\n \"sql.query\",\n {\n kind: SpanKind.CLIENT,\n attributes: {\n \"db.system\": \"databricks\",\n \"db.warehouse_id\": input.warehouse_id || \"\",\n \"db.catalog\": input.catalog ?? \"\",\n \"db.schema\": input.schema ?? \"\",\n \"db.statement\": input.statement?.substring(0, 500) || \"\",\n \"db.has_parameters\": !!input.parameters,\n },\n },\n async (span: Span) => {\n let abortHandler: (() => void) | undefined;\n let isAborted = false;\n\n if (signal) {\n abortHandler = () => {\n // abort span if not recording\n if (!span.isRecording()) return;\n isAborted = true;\n span.setAttribute(\"cancelled\", true);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: \"Query cancelled by client\",\n });\n span.end();\n };\n signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n\n try {\n // validate required fields\n if (!input.statement) {\n throw ValidationError.missingField(\"statement\");\n }\n\n if (!input.warehouse_id) {\n throw ValidationError.missingField(\"warehouse_id\");\n }\n\n const body: sql.ExecuteStatementRequest = {\n statement: input.statement,\n parameters: input.parameters,\n warehouse_id: input.warehouse_id,\n catalog: input.catalog,\n schema: input.schema,\n wait_timeout:\n input.wait_timeout || executeStatementDefaults.wait_timeout,\n disposition:\n input.disposition || executeStatementDefaults.disposition,\n format: input.format || executeStatementDefaults.format,\n byte_limit: input.byte_limit,\n row_limit: input.row_limit,\n on_wait_timeout:\n input.on_wait_timeout || executeStatementDefaults.on_wait_timeout,\n };\n\n span.addEvent(\"statement.submitting\", {\n \"db.warehouse_id\": input.warehouse_id,\n });\n\n const response =\n await workspaceClient.statementExecution.executeStatement(\n body,\n this._createContext(signal),\n );\n\n if (!response) {\n throw ConnectionError.apiFailure(\"SQL Warehouse\");\n }\n const status = response.status;\n const statementId = response.statement_id as string;\n\n span.setAttribute(\"db.statement_id\", statementId);\n span.addEvent(\"statement.submitted\", {\n \"db.statement_id\": response.statement_id,\n \"db.status\": status?.state,\n });\n\n let result:\n | sql.StatementResponse\n | { result: { statement_id: string; status: sql.StatementStatus } };\n\n switch (status?.state) {\n case \"RUNNING\":\n case \"PENDING\":\n span.addEvent(\"statement.polling_started\", {\n \"db.status\": response.status?.state,\n });\n result = await this._pollForStatementResult(\n workspaceClient,\n statementId,\n this.config.timeout,\n signal,\n );\n break;\n case \"SUCCEEDED\":\n result = this._transformDataArray(response);\n break;\n case \"FAILED\":\n throw ExecutionError.statementFailed(status.error?.message);\n case \"CANCELED\":\n throw ExecutionError.canceled();\n case \"CLOSED\":\n throw ExecutionError.resultsClosed();\n default:\n throw ExecutionError.unknownState(\n String(status?.state ?? \"unknown\"),\n );\n }\n\n const resultData = result.result as any;\n const rowCount =\n resultData?.data?.length ?? resultData?.data_array?.length ?? 0;\n\n if (rowCount > 0) {\n span.setAttribute(\"db.result.row_count\", rowCount);\n }\n\n const duration = Date.now() - startTime;\n logger.event()?.setContext(\"sql-warehouse\", {\n warehouse_id: input.warehouse_id,\n rows_returned: rowCount,\n query_duration_ms: duration,\n });\n\n success = true;\n // only set success status if not aborted\n if (!isAborted) {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n return result;\n } catch (error) {\n // only record error if not already handled by abort\n if (!isAborted) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n }\n\n if (error instanceof AppKitError) {\n throw error;\n }\n throw ExecutionError.statementFailed(\n error instanceof Error ? error.message : String(error),\n );\n } finally {\n // remove abort handler\n if (abortHandler && signal) {\n signal.removeEventListener(\"abort\", abortHandler);\n }\n\n const duration = Date.now() - startTime;\n\n // end span if not already ended by abort handler\n if (!isAborted) {\n span.end();\n }\n\n const attributes = {\n \"db.warehouse_id\": input.warehouse_id,\n \"db.catalog\": input.catalog ?? \"\",\n \"db.schema\": input.schema ?? \"\",\n \"db.statement\": input.statement?.substring(0, 500) || \"\",\n success: success.toString(),\n };\n\n this.telemetryMetrics.queryCount.add(1, attributes);\n this.telemetryMetrics.queryDuration.record(duration, attributes);\n }\n },\n { name: this.name, includePrefix: true },\n );\n }\n\n private async _pollForStatementResult(\n workspaceClient: WorkspaceClient,\n statementId: string,\n timeout = executeStatementDefaults.timeout,\n signal?: AbortSignal,\n ) {\n return this.telemetry.startActiveSpan(\n \"sql.poll\",\n {\n attributes: {\n \"db.statement_id\": statementId,\n \"db.polling.timeout\": timeout,\n },\n },\n async (span: Span) => {\n try {\n const startTime = Date.now();\n let delay = 1000;\n const maxDelayBetweenPolls = 5000; // max 5 seconds between polls\n let pollCount = 0;\n\n while (true) {\n pollCount++;\n span.setAttribute(\"db.polling.current_attempt\", pollCount);\n\n // check if timeout exceeded\n const elapsedTime = Date.now() - startTime;\n if (elapsedTime > timeout) {\n const error = ExecutionError.statementFailed(\n `Polling timeout exceeded after ${timeout}ms (elapsed: ${elapsedTime}ms)`,\n );\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n throw error;\n }\n\n if (signal?.aborted) {\n const error = ExecutionError.canceled();\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n throw error;\n }\n\n span.addEvent(\"polling.attempt\", {\n \"poll.attempt\": pollCount,\n \"poll.delay_ms\": delay,\n \"poll.elapsed_ms\": elapsedTime,\n });\n\n const response =\n await workspaceClient.statementExecution.getStatement(\n {\n statement_id: statementId,\n },\n this._createContext(signal),\n );\n if (!response) {\n throw ConnectionError.apiFailure(\"SQL Warehouse\");\n }\n\n const status = response.status;\n\n span.addEvent(\"polling.status_check\", {\n \"db.status\": status?.state,\n \"poll.attempt\": pollCount,\n });\n\n switch (status?.state) {\n case \"PENDING\":\n case \"RUNNING\":\n // continue polling\n break;\n case \"SUCCEEDED\":\n span.setAttribute(\"db.polling.attempts\", pollCount);\n span.setAttribute(\"db.polling.total_duration_ms\", elapsedTime);\n span.addEvent(\"polling.completed\", {\n \"poll.attempts\": pollCount,\n \"poll.duration_ms\": elapsedTime,\n });\n span.setStatus({ code: SpanStatusCode.OK });\n return this._transformDataArray(response);\n case \"FAILED\":\n throw ExecutionError.statementFailed(status.error?.message);\n case \"CANCELED\":\n throw ExecutionError.canceled();\n case \"CLOSED\":\n throw ExecutionError.resultsClosed();\n default:\n throw ExecutionError.unknownState(\n String(status?.state ?? \"unknown\"),\n );\n }\n\n // continue polling after delay\n await new Promise((resolve) => setTimeout(resolve, delay));\n delay = Math.min(delay * 2, maxDelayBetweenPolls);\n }\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n\n if (error instanceof AppKitError) {\n throw error;\n }\n throw ExecutionError.statementFailed(\n error instanceof Error ? error.message : String(error),\n );\n } finally {\n span.end();\n }\n },\n { name: this.name, includePrefix: true },\n );\n }\n\n private _transformDataArray(response: sql.StatementResponse) {\n if (response.manifest?.format === \"ARROW_STREAM\") {\n return this.updateWithArrowStatus(response);\n }\n\n if (!response.result?.data_array || !response.manifest?.schema?.columns) {\n return response;\n }\n\n const columns = response.manifest.schema.columns;\n\n const transformedData = response.result.data_array.map((row) => {\n const obj: Record<string, unknown> = {};\n row.forEach((value, index) => {\n const column = columns[index];\n const columnName = column?.name || `column_${index}`;\n\n // attempt to parse JSON strings for string columns\n if (\n column?.type_name === \"STRING\" &&\n typeof value === \"string\" &&\n value &&\n (value[0] === \"{\" || value[0] === \"[\")\n ) {\n try {\n obj[columnName] = JSON.parse(value);\n } catch {\n // if parsing fails, keep as string\n obj[columnName] = value;\n }\n } else {\n obj[columnName] = value;\n }\n });\n return obj;\n });\n\n // remove data_array\n const { data_array: _data_array, ...restResult } = response.result;\n return {\n ...response,\n result: {\n ...restResult,\n data: transformedData,\n },\n };\n }\n\n private updateWithArrowStatus(response: sql.StatementResponse): {\n result: { statement_id: string; status: sql.StatementStatus };\n } {\n return {\n result: {\n statement_id: response.statement_id as string,\n status: {\n state: response.status?.state,\n error: response.status?.error,\n } as sql.StatementStatus,\n },\n };\n }\n\n async getArrowData(\n workspaceClient: WorkspaceClient,\n jobId: string,\n signal?: AbortSignal,\n ): Promise<ReturnType<typeof this.arrowProcessor.processChunks>> {\n const startTime = Date.now();\n\n return this.telemetry.startActiveSpan(\n \"arrow.getData\",\n {\n kind: SpanKind.CLIENT,\n attributes: {\n \"db.system\": \"databricks\",\n \"arrow.job_id\": jobId,\n },\n },\n async (span: Span) => {\n try {\n const response =\n await workspaceClient.statementExecution.getStatement(\n { statement_id: jobId },\n this._createContext(signal),\n );\n\n const chunks = response.result?.external_links;\n const schema = response.manifest?.schema;\n\n if (!chunks || !schema) {\n throw ExecutionError.missingData(\"chunks or schema\");\n }\n\n span.setAttribute(\"arrow.chunk_count\", chunks.length);\n\n const result = await this.arrowProcessor.processChunks(\n chunks,\n schema,\n signal,\n );\n\n span.setAttribute(\"arrow.data_size_bytes\", result.data.length);\n span.setStatus({ code: SpanStatusCode.OK });\n\n const duration = Date.now() - startTime;\n this.telemetryMetrics.queryDuration.record(duration, {\n operation: \"arrow.getData\",\n status: \"success\",\n });\n\n logger.event()?.setContext(\"sql-warehouse\", {\n arrow_data_size_bytes: result.data.length,\n arrow_job_id: jobId,\n });\n\n return result;\n } catch (error) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : \"Unknown error\",\n });\n span.recordException(error as Error);\n\n const duration = Date.now() - startTime;\n this.telemetryMetrics.queryDuration.record(duration, {\n operation: \"arrow.getData\",\n status: \"error\",\n });\n\n logger.error(\"Failed Arrow job: %s %O\", jobId, error);\n\n if (error instanceof AppKitError) {\n throw error;\n }\n throw ExecutionError.statementFailed(\n error instanceof Error ? error.message : String(error),\n );\n }\n },\n );\n }\n\n // create context for cancellation token\n private _createContext(signal?: AbortSignal) {\n return new Context({\n cancellationToken: {\n isCancellationRequested: signal?.aborted ?? false,\n onCancellationRequested: (cb: () => void) => {\n signal?.addEventListener(\"abort\", cb, { once: true });\n },\n },\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../../src/connectors/sql-warehouse/client.ts"],"sourcesContent":["import {\n Context,\n type sql,\n type WorkspaceClient,\n} from \"@databricks/sdk-experimental\";\nimport type { TelemetryOptions } from \"shared\";\nimport {\n AppKitError,\n ConnectionError,\n ExecutionError,\n ValidationError,\n} from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport { ArrowStreamProcessor } from \"../../stream/arrow-stream-processor\";\nimport type { TelemetryProvider } from \"../../telemetry\";\nimport {\n type Counter,\n type Histogram,\n type Span,\n SpanKind,\n SpanStatusCode,\n TelemetryManager,\n} from \"../../telemetry\";\nimport { executeStatementDefaults } from \"./defaults\";\n\nconst logger = createLogger(\"connectors:sql-warehouse\");\n\ninterface SQLWarehouseConfig {\n timeout?: number;\n telemetry?: TelemetryOptions;\n}\n\nexport class SQLWarehouseConnector {\n private readonly name = \"sql-warehouse\";\n\n private config: SQLWarehouseConfig;\n\n // Lazy-initialized: only created when Arrow format is used\n private _arrowProcessor: ArrowStreamProcessor | null = null;\n // telemetry\n private readonly telemetry: TelemetryProvider;\n private readonly telemetryMetrics: {\n queryCount: Counter;\n queryDuration: Histogram;\n };\n\n constructor(config: SQLWarehouseConfig) {\n this.config = config;\n\n this.telemetry = TelemetryManager.getProvider(\n this.name,\n this.config.telemetry,\n );\n this.telemetryMetrics = {\n queryCount: this.telemetry.getMeter().createCounter(\"query.count\", {\n description: \"Total number of queries executed\",\n unit: \"1\",\n }),\n queryDuration: this.telemetry\n .getMeter()\n .createHistogram(\"query.duration\", {\n description: \"Duration of queries executed\",\n unit: \"ms\",\n }),\n };\n }\n\n /**\n * Lazily initializes and returns the ArrowStreamProcessor.\n * Only created on first Arrow format query to avoid unnecessary allocation.\n */\n private get arrowProcessor(): ArrowStreamProcessor {\n if (!this._arrowProcessor) {\n this._arrowProcessor = new ArrowStreamProcessor({\n timeout: this.config.timeout || executeStatementDefaults.timeout,\n maxConcurrentDownloads:\n ArrowStreamProcessor.DEFAULT_MAX_CONCURRENT_DOWNLOADS,\n retries: ArrowStreamProcessor.DEFAULT_RETRIES,\n });\n }\n return this._arrowProcessor;\n }\n\n async executeStatement(\n workspaceClient: WorkspaceClient,\n input: sql.ExecuteStatementRequest,\n signal?: AbortSignal,\n ) {\n const startTime = Date.now();\n let success = false;\n\n // if signal is aborted, throw an error\n if (signal?.aborted) {\n throw ExecutionError.canceled();\n }\n\n return this.telemetry.startActiveSpan(\n \"sql.query\",\n {\n kind: SpanKind.CLIENT,\n attributes: {\n \"db.system\": \"databricks\",\n \"db.warehouse_id\": input.warehouse_id || \"\",\n \"db.catalog\": input.catalog ?? \"\",\n \"db.schema\": input.schema ?? \"\",\n \"db.statement\": input.statement?.substring(0, 500) || \"\",\n \"db.has_parameters\": !!input.parameters,\n },\n },\n async (span: Span) => {\n let abortHandler: (() => void) | undefined;\n let isAborted = false;\n\n if (signal) {\n abortHandler = () => {\n // abort span if not recording\n if (!span.isRecording()) return;\n isAborted = true;\n span.setAttribute(\"cancelled\", true);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: \"Query cancelled by client\",\n });\n span.end();\n };\n signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n\n try {\n // validate required fields\n if (!input.statement) {\n throw ValidationError.missingField(\"statement\");\n }\n\n if (!input.warehouse_id) {\n throw ValidationError.missingField(\"warehouse_id\");\n }\n\n const body: sql.ExecuteStatementRequest = {\n statement: input.statement,\n parameters: input.parameters,\n warehouse_id: input.warehouse_id,\n catalog: input.catalog,\n schema: input.schema,\n wait_timeout:\n input.wait_timeout || executeStatementDefaults.wait_timeout,\n disposition:\n input.disposition || executeStatementDefaults.disposition,\n format: input.format || executeStatementDefaults.format,\n byte_limit: input.byte_limit,\n row_limit: input.row_limit,\n on_wait_timeout:\n input.on_wait_timeout || executeStatementDefaults.on_wait_timeout,\n };\n\n span.addEvent(\"statement.submitting\", {\n \"db.warehouse_id\": input.warehouse_id,\n });\n\n const response =\n await workspaceClient.statementExecution.executeStatement(\n body,\n this._createContext(signal),\n );\n\n if (!response) {\n throw ConnectionError.apiFailure(\"SQL Warehouse\");\n }\n const status = response.status;\n const statementId = response.statement_id as string;\n\n span.setAttribute(\"db.statement_id\", statementId);\n span.addEvent(\"statement.submitted\", {\n \"db.statement_id\": response.statement_id,\n \"db.status\": status?.state,\n });\n\n let result:\n | sql.StatementResponse\n | { result: { statement_id: string; status: sql.StatementStatus } };\n\n switch (status?.state) {\n case \"RUNNING\":\n case \"PENDING\":\n span.addEvent(\"statement.polling_started\", {\n \"db.status\": response.status?.state,\n });\n result = await this._pollForStatementResult(\n workspaceClient,\n statementId,\n this.config.timeout,\n signal,\n );\n break;\n case \"SUCCEEDED\":\n result = this._transformDataArray(response);\n break;\n case \"FAILED\":\n throw ExecutionError.statementFailed(status.error?.message);\n case \"CANCELED\":\n throw ExecutionError.canceled();\n case \"CLOSED\":\n throw ExecutionError.resultsClosed();\n default:\n throw ExecutionError.unknownState(\n String(status?.state ?? \"unknown\"),\n );\n }\n\n const resultData = result.result as any;\n const rowCount =\n resultData?.data?.length ?? resultData?.data_array?.length ?? 0;\n\n if (rowCount > 0) {\n span.setAttribute(\"db.result.row_count\", rowCount);\n }\n\n const duration = Date.now() - startTime;\n logger.event()?.setContext(\"sql-warehouse\", {\n warehouse_id: input.warehouse_id,\n rows_returned: rowCount,\n query_duration_ms: duration,\n });\n\n success = true;\n // only set success status if not aborted\n if (!isAborted) {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n return result;\n } catch (error) {\n // only record error if not already handled by abort\n if (!isAborted) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n\n logger.error(\n \"Statement execution failed: %s\",\n error instanceof Error ? error.message : String(error),\n );\n }\n\n if (error instanceof AppKitError) {\n throw error;\n }\n throw ExecutionError.statementFailed(\n error instanceof Error ? error.message : String(error),\n );\n } finally {\n // remove abort handler\n if (abortHandler && signal) {\n signal.removeEventListener(\"abort\", abortHandler);\n }\n\n const duration = Date.now() - startTime;\n\n // end span if not already ended by abort handler\n if (!isAborted) {\n span.end();\n }\n\n const attributes = {\n \"db.warehouse_id\": input.warehouse_id,\n \"db.catalog\": input.catalog ?? \"\",\n \"db.schema\": input.schema ?? \"\",\n \"db.statement\": input.statement?.substring(0, 500) || \"\",\n success: success.toString(),\n };\n\n this.telemetryMetrics.queryCount.add(1, attributes);\n this.telemetryMetrics.queryDuration.record(duration, attributes);\n }\n },\n { name: this.name, includePrefix: true },\n );\n }\n\n private async _pollForStatementResult(\n workspaceClient: WorkspaceClient,\n statementId: string,\n timeout = executeStatementDefaults.timeout,\n signal?: AbortSignal,\n ) {\n return this.telemetry.startActiveSpan(\n \"sql.poll\",\n {\n attributes: {\n \"db.statement_id\": statementId,\n \"db.polling.timeout\": timeout,\n },\n },\n async (span: Span) => {\n try {\n const startTime = Date.now();\n let delay = 1000;\n const maxDelayBetweenPolls = 5000; // max 5 seconds between polls\n let pollCount = 0;\n\n while (true) {\n pollCount++;\n span.setAttribute(\"db.polling.current_attempt\", pollCount);\n\n // check if timeout exceeded\n const elapsedTime = Date.now() - startTime;\n if (elapsedTime > timeout) {\n const error = ExecutionError.statementFailed(\n `Polling timeout exceeded after ${timeout}ms (elapsed: ${elapsedTime}ms)`,\n );\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n throw error;\n }\n\n if (signal?.aborted) {\n const error = ExecutionError.canceled();\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n throw error;\n }\n\n span.addEvent(\"polling.attempt\", {\n \"poll.attempt\": pollCount,\n \"poll.delay_ms\": delay,\n \"poll.elapsed_ms\": elapsedTime,\n });\n\n const response =\n await workspaceClient.statementExecution.getStatement(\n {\n statement_id: statementId,\n },\n this._createContext(signal),\n );\n if (!response) {\n throw ConnectionError.apiFailure(\"SQL Warehouse\");\n }\n\n const status = response.status;\n\n span.addEvent(\"polling.status_check\", {\n \"db.status\": status?.state,\n \"poll.attempt\": pollCount,\n });\n\n switch (status?.state) {\n case \"PENDING\":\n case \"RUNNING\":\n // continue polling\n break;\n case \"SUCCEEDED\":\n span.setAttribute(\"db.polling.attempts\", pollCount);\n span.setAttribute(\"db.polling.total_duration_ms\", elapsedTime);\n span.addEvent(\"polling.completed\", {\n \"poll.attempts\": pollCount,\n \"poll.duration_ms\": elapsedTime,\n });\n span.setStatus({ code: SpanStatusCode.OK });\n return this._transformDataArray(response);\n case \"FAILED\":\n throw ExecutionError.statementFailed(status.error?.message);\n case \"CANCELED\":\n throw ExecutionError.canceled();\n case \"CLOSED\":\n throw ExecutionError.resultsClosed();\n default:\n throw ExecutionError.unknownState(\n String(status?.state ?? \"unknown\"),\n );\n }\n\n // continue polling after delay\n await new Promise((resolve) => setTimeout(resolve, delay));\n delay = Math.min(delay * 2, maxDelayBetweenPolls);\n }\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n\n // error logging is handled by executeStatement's catch block (gated on isAborted)\n if (error instanceof AppKitError) {\n throw error;\n }\n throw ExecutionError.statementFailed(\n error instanceof Error ? error.message : String(error),\n );\n } finally {\n span.end();\n }\n },\n { name: this.name, includePrefix: true },\n );\n }\n\n private _transformDataArray(response: sql.StatementResponse) {\n if (response.manifest?.format === \"ARROW_STREAM\") {\n return this.updateWithArrowStatus(response);\n }\n\n if (!response.result?.data_array || !response.manifest?.schema?.columns) {\n return response;\n }\n\n const columns = response.manifest.schema.columns;\n\n const transformedData = response.result.data_array.map((row) => {\n const obj: Record<string, unknown> = {};\n row.forEach((value, index) => {\n const column = columns[index];\n const columnName = column?.name || `column_${index}`;\n\n // attempt to parse JSON strings for string columns\n if (\n column?.type_name === \"STRING\" &&\n typeof value === \"string\" &&\n value &&\n (value[0] === \"{\" || value[0] === \"[\")\n ) {\n try {\n obj[columnName] = JSON.parse(value);\n } catch {\n // if parsing fails, keep as string\n obj[columnName] = value;\n }\n } else {\n obj[columnName] = value;\n }\n });\n return obj;\n });\n\n // remove data_array\n const { data_array: _data_array, ...restResult } = response.result;\n return {\n ...response,\n result: {\n ...restResult,\n data: transformedData,\n },\n };\n }\n\n private updateWithArrowStatus(response: sql.StatementResponse): {\n result: { statement_id: string; status: sql.StatementStatus };\n } {\n return {\n result: {\n statement_id: response.statement_id as string,\n status: {\n state: response.status?.state,\n error: response.status?.error,\n } as sql.StatementStatus,\n },\n };\n }\n\n async getArrowData(\n workspaceClient: WorkspaceClient,\n jobId: string,\n signal?: AbortSignal,\n ): Promise<ReturnType<typeof this.arrowProcessor.processChunks>> {\n const startTime = Date.now();\n\n return this.telemetry.startActiveSpan(\n \"arrow.getData\",\n {\n kind: SpanKind.CLIENT,\n attributes: {\n \"db.system\": \"databricks\",\n \"arrow.job_id\": jobId,\n },\n },\n async (span: Span) => {\n try {\n const response =\n await workspaceClient.statementExecution.getStatement(\n { statement_id: jobId },\n this._createContext(signal),\n );\n\n const chunks = response.result?.external_links;\n const schema = response.manifest?.schema;\n\n if (!chunks || !schema) {\n throw ExecutionError.missingData(\"chunks or schema\");\n }\n\n span.setAttribute(\"arrow.chunk_count\", chunks.length);\n\n const result = await this.arrowProcessor.processChunks(\n chunks,\n schema,\n signal,\n );\n\n span.setAttribute(\"arrow.data_size_bytes\", result.data.length);\n span.setStatus({ code: SpanStatusCode.OK });\n\n const duration = Date.now() - startTime;\n this.telemetryMetrics.queryDuration.record(duration, {\n operation: \"arrow.getData\",\n status: \"success\",\n });\n\n logger.event()?.setContext(\"sql-warehouse\", {\n arrow_data_size_bytes: result.data.length,\n arrow_job_id: jobId,\n });\n\n return result;\n } catch (error) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : \"Unknown error\",\n });\n span.recordException(error as Error);\n\n const duration = Date.now() - startTime;\n this.telemetryMetrics.queryDuration.record(duration, {\n operation: \"arrow.getData\",\n status: \"error\",\n });\n\n logger.error(\"Failed Arrow job: %s %O\", jobId, error);\n\n if (error instanceof AppKitError) {\n throw error;\n }\n throw ExecutionError.statementFailed(\n error instanceof Error ? error.message : String(error),\n );\n }\n },\n );\n }\n\n // create context for cancellation token\n private _createContext(signal?: AbortSignal) {\n return new Context({\n cancellationToken: {\n isCancellationRequested: signal?.aborted ?? false,\n onCancellationRequested: (cb: () => void) => {\n signal?.addEventListener(\"abort\", cb, { once: true });\n },\n },\n });\n }\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -5,6 +5,8 @@ import { StreamExecutionSettings } from "./shared/src/execute.js";
5
5
  import { isSQLTypeMarker, sql } from "./shared/src/sql/helpers.js";
6
6
  import "./shared/src/index.js";
7
7
  import { CacheManager } from "./cache/index.js";
8
+ import { JobsConnectorConfig } from "./connectors/jobs/types.js";
9
+ import "./connectors/jobs/index.js";
8
10
  import { DatabaseCredential, GenerateDatabaseCredentialRequest, LakebasePoolConfig, RequestedClaims, RequestedClaimsPermissionSet, RequestedResource, createLakebasePool, generateDatabaseCredential, getLakebaseOrmConfig, getLakebasePgConfig, getUsernameWithApiLookup, getWorkspaceClient } from "./connectors/lakebase/index.js";
9
11
  import { getExecutionContext } from "./context/execution-context.js";
10
12
  import "./context/index.js";
@@ -34,6 +36,9 @@ import { analytics } from "./plugins/analytics/analytics.js";
34
36
  import { FileAction, FilePolicy, FilePolicyUser, FileResource, PolicyDeniedError, READ_ACTIONS, WRITE_ACTIONS } from "./plugins/files/policy.js";
35
37
  import { files } from "./plugins/files/plugin.js";
36
38
  import { genie } from "./plugins/genie/genie.js";
39
+ import { IJobsConfig, JobAPI, JobConfig, JobHandle, JobsExport } from "./plugins/jobs/types.js";
40
+ import { jobs } from "./plugins/jobs/plugin.js";
41
+ import "./plugins/jobs/index.js";
37
42
  import { lakebase } from "./plugins/lakebase/lakebase.js";
38
43
  import { server } from "./plugins/server/index.js";
39
44
  import { EndpointConfig, ServingEndpointEntry, ServingEndpointRegistry, ServingFactory } from "./plugins/serving/types.js";
@@ -42,4 +47,4 @@ import "./plugins/index.js";
42
47
  import { extractServingEndpoints, findServerFile } from "./type-generator/serving/server-file-extractor.js";
43
48
  import { appKitServingTypesPlugin } from "./type-generator/serving/vite-plugin.js";
44
49
  import { appKitTypesPlugin } from "./type-generator/vite-plugin.js";
45
- export { AppKitError, AuthenticationError, type BasePluginConfig, type CacheConfig, CacheManager, type ConfigSchema, ConfigurationError, ConnectionError, type Counter, type DatabaseCredential, type EndpointConfig, ExecutionError, type ExecutionResult, type FileAction, type FilePolicy, type FilePolicyUser, type FileResource, type GenerateDatabaseCredentialRequest, type Histogram, type IAppRouter, type ITelemetry, InitializationError, type LakebasePoolConfig, Plugin, type PluginData, type PluginManifest, PolicyDeniedError, READ_ACTIONS, type RequestedClaims, RequestedClaimsPermissionSet, type RequestedResource, type ResourceEntry, type ResourceFieldEntry, type ResourcePermission, ResourceRegistry, type ResourceRequirement, ResourceType, ServerError, type ServingEndpointEntry, type ServingEndpointRegistry, type ServingFactory, SeverityNumber, type Span, SpanStatusCode, type StreamExecutionSettings, type TelemetryConfig, type ToPlugin, TunnelError, ValidationError, type ValidationResult, WRITE_ACTIONS, analytics, appKitServingTypesPlugin, appKitTypesPlugin, createApp, createLakebasePool, extractServingEndpoints, files, findServerFile, generateDatabaseCredential, genie, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUsernameWithApiLookup, getWorkspaceClient, isSQLTypeMarker, lakebase, server, serving, sql, toPlugin };
50
+ export { AppKitError, AuthenticationError, type BasePluginConfig, type CacheConfig, CacheManager, type ConfigSchema, ConfigurationError, ConnectionError, type Counter, type DatabaseCredential, type EndpointConfig, ExecutionError, type ExecutionResult, type FileAction, type FilePolicy, type FilePolicyUser, type FileResource, type GenerateDatabaseCredentialRequest, type Histogram, type IAppRouter, type IJobsConfig, type ITelemetry, InitializationError, type JobAPI, type JobConfig, type JobHandle, type JobsConnectorConfig, type JobsExport, type LakebasePoolConfig, Plugin, type PluginData, type PluginManifest, PolicyDeniedError, READ_ACTIONS, type RequestedClaims, RequestedClaimsPermissionSet, type RequestedResource, type ResourceEntry, type ResourceFieldEntry, type ResourcePermission, ResourceRegistry, type ResourceRequirement, ResourceType, ServerError, type ServingEndpointEntry, type ServingEndpointRegistry, type ServingFactory, SeverityNumber, type Span, SpanStatusCode, type StreamExecutionSettings, type TelemetryConfig, type ToPlugin, TunnelError, ValidationError, type ValidationResult, WRITE_ACTIONS, analytics, appKitServingTypesPlugin, appKitTypesPlugin, createApp, createLakebasePool, extractServingEndpoints, files, findServerFile, generateDatabaseCredential, genie, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUsernameWithApiLookup, getWorkspaceClient, isSQLTypeMarker, jobs, lakebase, server, serving, sql, toPlugin };
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ import { analytics } from "./plugins/analytics/analytics.js";
27
27
  import { PolicyDeniedError, READ_ACTIONS, WRITE_ACTIONS } from "./plugins/files/policy.js";
28
28
  import { files } from "./plugins/files/plugin.js";
29
29
  import { genie } from "./plugins/genie/genie.js";
30
+ import { jobs } from "./plugins/jobs/plugin.js";
30
31
  import { lakebase } from "./plugins/lakebase/lakebase.js";
31
32
  import { extractServingEndpoints, findServerFile } from "./type-generator/serving/server-file-extractor.js";
32
33
  import { appKitServingTypesPlugin } from "./type-generator/serving/vite-plugin.js";
@@ -40,5 +41,5 @@ init_context();
40
41
  init_errors();
41
42
 
42
43
  //#endregion
43
- export { AppKitError, AuthenticationError, CacheManager, ConfigurationError, ConnectionError, ExecutionError, InitializationError, Plugin, PolicyDeniedError, READ_ACTIONS, RequestedClaimsPermissionSet, ResourceRegistry, ResourceType, ServerError, SeverityNumber, SpanStatusCode, TunnelError, ValidationError, WRITE_ACTIONS, analytics, appKitServingTypesPlugin, appKitTypesPlugin, createApp, createLakebasePool, extractServingEndpoints, files, findServerFile, generateDatabaseCredential, genie, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUsernameWithApiLookup, getWorkspaceClient, isSQLTypeMarker, lakebase, server, serving, sql, toPlugin };
44
+ export { AppKitError, AuthenticationError, CacheManager, ConfigurationError, ConnectionError, ExecutionError, InitializationError, Plugin, PolicyDeniedError, READ_ACTIONS, RequestedClaimsPermissionSet, ResourceRegistry, ResourceType, ServerError, SeverityNumber, SpanStatusCode, TunnelError, ValidationError, WRITE_ACTIONS, analytics, appKitServingTypesPlugin, appKitTypesPlugin, createApp, createLakebasePool, extractServingEndpoints, files, findServerFile, generateDatabaseCredential, genie, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUsernameWithApiLookup, getWorkspaceClient, isSQLTypeMarker, jobs, lakebase, server, serving, sql, toPlugin };
44
45
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @packageDocumentation\n *\n * Core library for building Databricks applications with type-safe SQL queries,\n * plugin architecture, and React integration.\n */\n\n// Types from shared\nexport type {\n BasePluginConfig,\n CacheConfig,\n IAppRouter,\n PluginData,\n StreamExecutionSettings,\n} from \"shared\";\nexport { isSQLTypeMarker, sql } from \"shared\";\nexport { CacheManager } from \"./cache\";\nexport type {\n DatabaseCredential,\n GenerateDatabaseCredentialRequest,\n LakebasePoolConfig,\n RequestedClaims,\n RequestedResource,\n} from \"./connectors/lakebase\";\n// Lakebase Autoscaling connector\nexport {\n createLakebasePool,\n generateDatabaseCredential,\n getLakebaseOrmConfig,\n getLakebasePgConfig,\n getUsernameWithApiLookup,\n getWorkspaceClient,\n RequestedClaimsPermissionSet,\n} from \"./connectors/lakebase\";\nexport { getExecutionContext } from \"./context\";\nexport { createApp } from \"./core\";\n// Errors\nexport {\n AppKitError,\n AuthenticationError,\n ConfigurationError,\n ConnectionError,\n ExecutionError,\n InitializationError,\n ServerError,\n TunnelError,\n ValidationError,\n} from \"./errors\";\n// Plugin authoring\nexport {\n type ExecutionResult,\n Plugin,\n type ToPlugin,\n toPlugin,\n} from \"./plugin\";\nexport { analytics, files, genie, lakebase, server, serving } from \"./plugins\";\n// Files plugin types (for custom policy authoring)\nexport type {\n FileAction,\n FilePolicy,\n FilePolicyUser,\n FileResource,\n} from \"./plugins/files/policy\";\nexport {\n PolicyDeniedError,\n READ_ACTIONS,\n WRITE_ACTIONS,\n} from \"./plugins/files/policy\";\nexport type {\n EndpointConfig,\n ServingEndpointEntry,\n ServingEndpointRegistry,\n ServingFactory,\n} from \"./plugins/serving/types\";\n// Registry types and utilities for plugin manifests\nexport type {\n ConfigSchema,\n PluginManifest,\n ResourceEntry,\n ResourceFieldEntry,\n ResourcePermission,\n ResourceRequirement,\n ValidationResult,\n} from \"./registry\";\nexport {\n getPluginManifest,\n getResourceRequirements,\n ResourceRegistry,\n ResourceType,\n} from \"./registry\";\n// Telemetry (for advanced custom telemetry)\nexport {\n type Counter,\n type Histogram,\n type ITelemetry,\n SeverityNumber,\n type Span,\n SpanStatusCode,\n type TelemetryConfig,\n} from \"./telemetry\";\nexport {\n extractServingEndpoints,\n findServerFile,\n} from \"./type-generator/serving/server-file-extractor\";\nexport { appKitServingTypesPlugin } from \"./type-generator/serving/vite-plugin\";\n// Vite plugin and type generation\nexport { appKitTypesPlugin } from \"./type-generator/vite-plugin\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkCgD;aAa9B"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @packageDocumentation\n *\n * Core library for building Databricks applications with type-safe SQL queries,\n * plugin architecture, and React integration.\n */\n\n// Types from shared\nexport type {\n BasePluginConfig,\n CacheConfig,\n IAppRouter,\n PluginData,\n StreamExecutionSettings,\n} from \"shared\";\nexport { isSQLTypeMarker, sql } from \"shared\";\nexport { CacheManager } from \"./cache\";\nexport type { JobsConnectorConfig } from \"./connectors/jobs\";\nexport type {\n DatabaseCredential,\n GenerateDatabaseCredentialRequest,\n LakebasePoolConfig,\n RequestedClaims,\n RequestedResource,\n} from \"./connectors/lakebase\";\n// Lakebase Autoscaling connector\nexport {\n createLakebasePool,\n generateDatabaseCredential,\n getLakebaseOrmConfig,\n getLakebasePgConfig,\n getUsernameWithApiLookup,\n getWorkspaceClient,\n RequestedClaimsPermissionSet,\n} from \"./connectors/lakebase\";\nexport { getExecutionContext } from \"./context\";\nexport { createApp } from \"./core\";\n// Errors\nexport {\n AppKitError,\n AuthenticationError,\n ConfigurationError,\n ConnectionError,\n ExecutionError,\n InitializationError,\n ServerError,\n TunnelError,\n ValidationError,\n} from \"./errors\";\n// Plugin authoring\nexport {\n type ExecutionResult,\n Plugin,\n type ToPlugin,\n toPlugin,\n} from \"./plugin\";\nexport {\n analytics,\n files,\n genie,\n jobs,\n lakebase,\n server,\n serving,\n} from \"./plugins\";\n// Files plugin types (for custom policy authoring)\nexport type {\n FileAction,\n FilePolicy,\n FilePolicyUser,\n FileResource,\n} from \"./plugins/files/policy\";\nexport {\n PolicyDeniedError,\n READ_ACTIONS,\n WRITE_ACTIONS,\n} from \"./plugins/files/policy\";\nexport type {\n IJobsConfig,\n JobAPI,\n JobConfig,\n JobHandle,\n JobsExport,\n} from \"./plugins/jobs\";\nexport type {\n EndpointConfig,\n ServingEndpointEntry,\n ServingEndpointRegistry,\n ServingFactory,\n} from \"./plugins/serving/types\";\n// Registry types and utilities for plugin manifests\nexport type {\n ConfigSchema,\n PluginManifest,\n ResourceEntry,\n ResourceFieldEntry,\n ResourcePermission,\n ResourceRequirement,\n ValidationResult,\n} from \"./registry\";\nexport {\n getPluginManifest,\n getResourceRequirements,\n ResourceRegistry,\n ResourceType,\n} from \"./registry\";\n// Telemetry (for advanced custom telemetry)\nexport {\n type Counter,\n type Histogram,\n type ITelemetry,\n SeverityNumber,\n type Span,\n SpanStatusCode,\n type TelemetryConfig,\n} from \"./telemetry\";\nexport {\n extractServingEndpoints,\n findServerFile,\n} from \"./type-generator/serving/server-file-extractor\";\nexport { appKitServingTypesPlugin } from \"./type-generator/serving/vite-plugin\";\n// Vite plugin and type generation\nexport { appKitTypesPlugin } from \"./type-generator/vite-plugin\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAmCgD;aAa9B"}
@@ -9,6 +9,9 @@ import { GenieConversationHistoryResponse } from "../connectors/genie/types.js";
9
9
  import { IGenieConfig } from "./genie/types.js";
10
10
  import { GeniePlugin, genie } from "./genie/genie.js";
11
11
  import "./genie/index.js";
12
+ import { IJobsConfig, JobAPI, JobConfig, JobHandle, JobsExport } from "./jobs/types.js";
13
+ import { jobs } from "./jobs/plugin.js";
14
+ import "./jobs/index.js";
12
15
  import { ILakebaseConfig } from "./lakebase/types.js";
13
16
  import { lakebase } from "./lakebase/lakebase.js";
14
17
  import "./lakebase/index.js";
@@ -5,6 +5,8 @@ import { FilesPlugin, files } from "./files/plugin.js";
5
5
  import "./files/index.js";
6
6
  import { GeniePlugin, genie } from "./genie/genie.js";
7
7
  import "./genie/index.js";
8
+ import { jobs } from "./jobs/plugin.js";
9
+ import "./jobs/index.js";
8
10
  import { lakebase } from "./lakebase/lakebase.js";
9
11
  import "./lakebase/index.js";
10
12
  import { ServerPlugin, server } from "./server/index.js";
@@ -0,0 +1,45 @@
1
+ //#region src/plugins/jobs/defaults.ts
2
+ /**
3
+ * Execution defaults for read-tier operations (getRun, getJob, listRuns, lastRun, getRunOutput).
4
+ * Cache 60s (ttl in seconds)
5
+ * Retry 3x with 1s backoff
6
+ * Timeout 30s
7
+ */
8
+ const JOBS_READ_DEFAULTS = {
9
+ cache: {
10
+ enabled: true,
11
+ ttl: 60
12
+ },
13
+ retry: {
14
+ enabled: true,
15
+ initialDelay: 1e3,
16
+ attempts: 3
17
+ },
18
+ timeout: 3e4
19
+ };
20
+ /**
21
+ * Execution defaults for write-tier operations (runNow, cancelRun).
22
+ * No cache
23
+ * No retry
24
+ * Timeout 120s
25
+ */
26
+ const JOBS_WRITE_DEFAULTS = {
27
+ cache: { enabled: false },
28
+ retry: { enabled: false },
29
+ timeout: 12e4
30
+ };
31
+ /**
32
+ * Execution defaults for stream-tier operations (runNowAndWait with polling).
33
+ * No cache
34
+ * No retry
35
+ * Timeout 600s
36
+ */
37
+ const JOBS_STREAM_DEFAULTS = {
38
+ cache: { enabled: false },
39
+ retry: { enabled: false },
40
+ timeout: 6e5
41
+ };
42
+
43
+ //#endregion
44
+ export { JOBS_READ_DEFAULTS, JOBS_STREAM_DEFAULTS, JOBS_WRITE_DEFAULTS };
45
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.js","names":[],"sources":["../../../src/plugins/jobs/defaults.ts"],"sourcesContent":["import type { PluginExecuteConfig } from \"shared\";\n\n/**\n * Execution defaults for read-tier operations (getRun, getJob, listRuns, lastRun, getRunOutput).\n * Cache 60s (ttl in seconds)\n * Retry 3x with 1s backoff\n * Timeout 30s\n */\nexport const JOBS_READ_DEFAULTS: PluginExecuteConfig = {\n cache: { enabled: true, ttl: 60 },\n retry: { enabled: true, initialDelay: 1000, attempts: 3 },\n timeout: 30_000,\n};\n\n/**\n * Execution defaults for write-tier operations (runNow, cancelRun).\n * No cache\n * No retry\n * Timeout 120s\n */\nexport const JOBS_WRITE_DEFAULTS: PluginExecuteConfig = {\n cache: { enabled: false },\n retry: { enabled: false },\n timeout: 120_000,\n};\n\n/**\n * Execution defaults for stream-tier operations (runNowAndWait with polling).\n * No cache\n * No retry\n * Timeout 600s\n */\nexport const JOBS_STREAM_DEFAULTS: PluginExecuteConfig = {\n cache: { enabled: false },\n retry: { enabled: false },\n timeout: 600_000,\n};\n"],"mappings":";;;;;;;AAQA,MAAa,qBAA0C;CACrD,OAAO;EAAE,SAAS;EAAM,KAAK;EAAI;CACjC,OAAO;EAAE,SAAS;EAAM,cAAc;EAAM,UAAU;EAAG;CACzD,SAAS;CACV;;;;;;;AAQD,MAAa,sBAA2C;CACtD,OAAO,EAAE,SAAS,OAAO;CACzB,OAAO,EAAE,SAAS,OAAO;CACzB,SAAS;CACV;;;;;;;AAQD,MAAa,uBAA4C;CACvD,OAAO,EAAE,SAAS,OAAO;CACzB,OAAO,EAAE,SAAS,OAAO;CACzB,SAAS;CACV"}
@@ -0,0 +1,2 @@
1
+ import { IJobsConfig, JobAPI, JobConfig, JobHandle, JobsExport } from "./types.js";
2
+ import { jobs } from "./plugin.js";
@@ -0,0 +1,3 @@
1
+ import { jobs } from "./plugin.js";
2
+
3
+ export { };