@databricks/appkit 0.20.2 → 0.20.3
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.
- package/CLAUDE.md +5 -13
- package/NOTICE.md +3 -6
- package/dist/app/index.js.map +1 -1
- package/dist/appkit/package.js +1 -1
- package/dist/cli/commands/plugin/add-resource/add-resource.js.map +1 -1
- package/dist/connectors/files/client.js.map +1 -1
- package/dist/connectors/files/defaults.js +1 -1
- package/dist/connectors/files/defaults.js.map +1 -1
- package/dist/connectors/files/index.js +1 -1
- package/dist/connectors/genie/client.js.map +1 -1
- package/dist/connectors/genie/index.d.ts +0 -1
- package/dist/connectors/genie/index.js +0 -2
- package/dist/connectors/index.js +1 -3
- package/dist/connectors/lakebase/index.js.map +1 -1
- package/dist/connectors/sql-warehouse/client.js.map +1 -1
- package/dist/context/index.d.ts +2 -2
- package/dist/context/index.js +0 -1
- package/dist/context/index.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/logging/index.js +0 -13
- package/dist/logging/logger.js.map +1 -1
- package/dist/logging/sampling.js.map +1 -1
- package/dist/plugins/files/defaults.js +0 -2
- package/dist/plugins/files/defaults.js.map +1 -1
- package/dist/plugins/files/index.js +0 -1
- package/dist/plugins/genie/index.d.ts +1 -1
- package/dist/plugins/genie/types.d.ts +1 -1
- package/dist/plugins/genie/types.d.ts.map +1 -1
- package/dist/plugins/index.d.ts +2 -3
- package/dist/plugins/index.js +1 -2
- package/dist/plugins/lakebase/index.d.ts +1 -1
- package/dist/plugins/lakebase/index.js +1 -1
- package/dist/plugins/lakebase/lakebase.d.ts +1 -1
- package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
- package/dist/plugins/lakebase/lakebase.js +1 -1
- package/dist/plugins/lakebase/lakebase.js.map +1 -1
- package/dist/plugins/server/index.js.map +1 -1
- package/dist/plugins/server/utils.js.map +1 -1
- package/dist/registry/index.d.ts +3 -2
- package/dist/registry/manifest-loader.d.ts +4 -3
- package/dist/registry/manifest-loader.d.ts.map +1 -1
- package/dist/registry/types.d.ts +19 -57
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry/types.generated.d.ts +1 -1
- package/dist/registry/types.js +7 -2
- package/dist/registry/types.js.map +1 -1
- package/dist/schemas/plugin-manifest.generated.d.ts +178 -0
- package/dist/schemas/plugin-manifest.generated.d.ts.map +1 -0
- package/dist/schemas/plugin-manifest.schema.json +2 -0
- package/dist/shared/src/cache.d.ts +1 -1
- package/dist/shared/src/execute.d.ts +7 -1
- package/dist/shared/src/execute.d.ts.map +1 -1
- package/dist/shared/src/index.d.ts +2 -1
- package/dist/shared/src/plugin.d.ts +19 -48
- package/dist/shared/src/plugin.d.ts.map +1 -1
- package/dist/shared/src/schemas/plugin-manifest.generated.d.ts +178 -0
- package/dist/shared/src/schemas/plugin-manifest.generated.d.ts.map +1 -0
- package/dist/stream/arrow-stream-processor.js.map +1 -1
- package/dist/stream/index.d.ts +1 -3
- package/dist/stream/index.js +0 -3
- package/dist/stream/stream-manager.js +1 -1
- package/dist/stream/types.js.map +1 -1
- package/dist/telemetry/config.js +1 -0
- package/dist/telemetry/config.js.map +1 -1
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/telemetry/noop.js.map +1 -1
- package/dist/telemetry/types.d.ts +1 -1
- package/dist/type-generator/query-registry.js.map +1 -1
- package/dist/utils/path-exclusions.js.map +1 -1
- package/docs/api/appkit/Interface.CacheConfig.md +1 -1
- package/docs/api/appkit/Interface.PluginManifest.md +104 -6
- package/docs/api/appkit/Interface.ResourceEntry.md +5 -7
- package/docs/api/appkit/Interface.ResourceFieldEntry.md +2 -0
- package/docs/api/appkit/Interface.ResourceRequirement.md +63 -7
- package/docs/api/appkit/Interface.StreamExecutionSettings.md +1 -1
- package/docs/api/appkit/TypeAlias.PluginData.md +2 -0
- package/docs/api/appkit/TypeAlias.ToPlugin.md +2 -0
- package/docs/api/appkit.md +6 -6
- package/llms.txt +5 -13
- package/package.json +2 -2
- package/dist/stream/arrow-stream-processor.d.ts +0 -1
- package/dist/stream/buffers.d.ts +0 -1
- package/dist/stream/types.d.ts +0 -3
- package/docs/api/appkit-ui/data/AreaChart.md +0 -79
- package/docs/api/appkit-ui/data/BarChart.md +0 -74
- package/docs/api/appkit-ui/data/DonutChart.md +0 -72
- package/docs/api/appkit-ui/data/HeatmapChart.md +0 -91
- package/docs/api/appkit-ui/data/LineChart.md +0 -77
- package/docs/api/appkit-ui/data/PieChart.md +0 -72
- package/docs/api/appkit-ui/data/RadarChart.md +0 -74
- package/docs/api/appkit-ui/data/ScatterChart.md +0 -76
package/CLAUDE.md
CHANGED
|
@@ -79,7 +79,7 @@ npx @databricks/appkit docs <query>
|
|
|
79
79
|
- [Function: getWorkspaceClient()](./docs/api/appkit/Function.getWorkspaceClient.md): Get workspace client from config or SDK default auth chain
|
|
80
80
|
- [Function: isSQLTypeMarker()](./docs/api/appkit/Function.isSQLTypeMarker.md): Type guard to check if a value is a SQL type marker
|
|
81
81
|
- [Interface: BasePluginConfig](./docs/api/appkit/Interface.BasePluginConfig.md): Base configuration interface for AppKit plugins
|
|
82
|
-
- [Interface: CacheConfig](./docs/api/appkit/Interface.CacheConfig.md): Configuration for
|
|
82
|
+
- [Interface: CacheConfig](./docs/api/appkit/Interface.CacheConfig.md): Configuration for the CacheInterceptor. Controls TTL, size limits, storage backend, and probabilistic cleanup.
|
|
83
83
|
- [Interface: DatabaseCredential](./docs/api/appkit/Interface.DatabaseCredential.md): Database credentials with OAuth token for Postgres connection
|
|
84
84
|
- [Interface: GenerateDatabaseCredentialRequest](./docs/api/appkit/Interface.GenerateDatabaseCredentialRequest.md): Request parameters for generating database OAuth credentials
|
|
85
85
|
- [Interface: ITelemetry](./docs/api/appkit/Interface.ITelemetry.md): Plugin-facing interface for OpenTelemetry instrumentation.
|
|
@@ -88,30 +88,22 @@ npx @databricks/appkit docs <query>
|
|
|
88
88
|
- [Interface: RequestedClaims](./docs/api/appkit/Interface.RequestedClaims.md): Optional claims for fine-grained Unity Catalog table permissions
|
|
89
89
|
- [Interface: RequestedResource](./docs/api/appkit/Interface.RequestedResource.md): Resource to request permissions for in Unity Catalog
|
|
90
90
|
- [Interface: ResourceEntry](./docs/api/appkit/Interface.ResourceEntry.md): Internal representation of a resource in the registry.
|
|
91
|
-
- [Interface: ResourceFieldEntry](./docs/api/appkit/Interface.ResourceFieldEntry.md): Defines a single field for a resource. Each field has its own environment variable and optional description.
|
|
91
|
+
- [Interface: ResourceFieldEntry](./docs/api/appkit/Interface.ResourceFieldEntry.md): Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instancename, databasename or scope, key).
|
|
92
92
|
- [Interface: ResourceRequirement](./docs/api/appkit/Interface.ResourceRequirement.md): Declares a resource requirement for a plugin.
|
|
93
|
-
- [Interface: StreamExecutionSettings](./docs/api/appkit/Interface.StreamExecutionSettings.md):
|
|
93
|
+
- [Interface: StreamExecutionSettings](./docs/api/appkit/Interface.StreamExecutionSettings.md): Execution settings for streaming endpoints. Extends PluginExecutionSettings with SSE stream configuration.
|
|
94
94
|
- [Interface: TelemetryConfig](./docs/api/appkit/Interface.TelemetryConfig.md): OpenTelemetry configuration for AppKit applications
|
|
95
95
|
- [Interface: ValidationResult](./docs/api/appkit/Interface.ValidationResult.md): Result of validating all registered resources against the environment.
|
|
96
96
|
- [Type Alias: ConfigSchema](./docs/api/appkit/TypeAlias.ConfigSchema.md): Configuration schema definition for plugin config.
|
|
97
97
|
- [Type Alias: IAppRouter](./docs/api/appkit/TypeAlias.IAppRouter.md): Express router type for plugin route registration
|
|
98
|
-
- [Type Alias: PluginData<T, U, N>](./docs/api/appkit/TypeAlias.PluginData.md):
|
|
98
|
+
- [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().
|
|
99
99
|
- [Type Alias: ResourcePermission](./docs/api/appkit/TypeAlias.ResourcePermission.md): Union of all possible permission levels across all resource types.
|
|
100
|
-
- [Type Alias: ToPlugin()<T, U, N>](./docs/api/appkit/TypeAlias.ToPlugin.md):
|
|
100
|
+
- [Type Alias: ToPlugin()<T, U, N>](./docs/api/appkit/TypeAlias.ToPlugin.md): Factory function type returned by toPlugin(). Accepts optional config and returns a PluginData tuple.
|
|
101
101
|
- [Variable: sql](./docs/api/appkit/Variable.sql.md): SQL helper namespace
|
|
102
102
|
|
|
103
103
|
## appkit-ui API reference [collapsed]
|
|
104
104
|
|
|
105
105
|
- [@databricks/appkit-ui](./docs/api/appkit-ui.md): The library provides a set of UI primitives for building Databricks apps in React.
|
|
106
|
-
- [AreaChart](./docs/api/appkit-ui/data/AreaChart.md): Area Chart component for trend visualization with filled areas.
|
|
107
|
-
- [BarChart](./docs/api/appkit-ui/data/BarChart.md): Bar Chart component for categorical comparisons.
|
|
108
106
|
- [DataTable](./docs/api/appkit-ui/data/DataTable.md): Production-ready data table with automatic data fetching and state management
|
|
109
|
-
- [DonutChart](./docs/api/appkit-ui/data/DonutChart.md): Donut Chart component (Pie chart with inner radius).
|
|
110
|
-
- [HeatmapChart](./docs/api/appkit-ui/data/HeatmapChart.md): Heatmap Chart component for matrix-style data visualization.
|
|
111
|
-
- [LineChart](./docs/api/appkit-ui/data/LineChart.md): Line Chart component for time-series and trend visualization.
|
|
112
|
-
- [PieChart](./docs/api/appkit-ui/data/PieChart.md): Pie Chart component for proportional data visualization.
|
|
113
|
-
- [RadarChart](./docs/api/appkit-ui/data/RadarChart.md): Radar Chart component for multi-dimensional data comparison.
|
|
114
|
-
- [ScatterChart](./docs/api/appkit-ui/data/ScatterChart.md): Scatter Chart component for correlation and distribution visualization.
|
|
115
107
|
- [DirectoryList](./docs/api/appkit-ui/files/DirectoryList.md): Card-wrapped directory listing with loading, error, and empty states
|
|
116
108
|
- [FileBreadcrumb](./docs/api/appkit-ui/files/FileBreadcrumb.md): Path-aware breadcrumb navigation built on top of Breadcrumb primitives
|
|
117
109
|
- [FileEntry](./docs/api/appkit-ui/files/FileEntry.md): Single file or directory row with icon, name, size, and selection state
|
package/NOTICE.md
CHANGED
|
@@ -8,7 +8,6 @@ This Software contains code from the following open source projects:
|
|
|
8
8
|
| :--------------- | :---------------- | :----------- | :--------------------------------------------------- |
|
|
9
9
|
| [@ast-grep/napi](https://www.npmjs.com/package/@ast-grep/napi) | 0.37.0 | MIT | https://ast-grep.github.io |
|
|
10
10
|
| [@clack/prompts](https://www.npmjs.com/package/@clack/prompts) | 1.0.1 | MIT | https://github.com/bombshell-dev/clack/tree/main/packages/prompts#readme |
|
|
11
|
-
| [@hookform/resolvers](https://www.npmjs.com/package/@hookform/resolvers) | 5.2.2 | MIT | https://react-hook-form.com |
|
|
12
11
|
| [@opentelemetry/api](https://www.npmjs.com/package/@opentelemetry/api) | 1.9.0 | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js/tree/main/api |
|
|
13
12
|
| [@opentelemetry/api-logs](https://www.npmjs.com/package/@opentelemetry/api-logs) | 0.208.0 | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/api-logs |
|
|
14
13
|
| [@opentelemetry/auto-instrumentations-node](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node) | 0.67.2 | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/auto-instrumentations-node#readme |
|
|
@@ -18,10 +17,11 @@ This Software contains code from the following open source projects:
|
|
|
18
17
|
| [@opentelemetry/instrumentation](https://www.npmjs.com/package/@opentelemetry/instrumentation) | 0.208.0 | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation |
|
|
19
18
|
| [@opentelemetry/instrumentation-express](https://www.npmjs.com/package/@opentelemetry/instrumentation-express) | 0.57.0 | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-express#readme |
|
|
20
19
|
| [@opentelemetry/instrumentation-http](https://www.npmjs.com/package/@opentelemetry/instrumentation-http) | 0.208.0 | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-http |
|
|
21
|
-
| [@opentelemetry/resources](https://www.npmjs.com/package/@opentelemetry/resources) | 2.2.0 | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-resources |
|
|
20
|
+
| [@opentelemetry/resources](https://www.npmjs.com/package/@opentelemetry/resources) | 2.2.0, 2.6.0 | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-resources |
|
|
22
21
|
| [@opentelemetry/sdk-logs](https://www.npmjs.com/package/@opentelemetry/sdk-logs) | 0.208.0 | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/sdk-logs |
|
|
23
22
|
| [@opentelemetry/sdk-metrics](https://www.npmjs.com/package/@opentelemetry/sdk-metrics) | 2.2.0 | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/sdk-metrics |
|
|
24
23
|
| [@opentelemetry/sdk-node](https://www.npmjs.com/package/@opentelemetry/sdk-node) | 0.208.0 | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-sdk-node |
|
|
24
|
+
| [@opentelemetry/sdk-trace-base](https://www.npmjs.com/package/@opentelemetry/sdk-trace-base) | 2.2.0, 2.6.0 | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-base |
|
|
25
25
|
| [@opentelemetry/semantic-conventions](https://www.npmjs.com/package/@opentelemetry/semantic-conventions) | 1.38.0 | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js/tree/main/semantic-conventions |
|
|
26
26
|
| [@radix-ui/react-accordion](https://www.npmjs.com/package/@radix-ui/react-accordion) | 1.2.12 | MIT | https://radix-ui.com/primitives |
|
|
27
27
|
| [@radix-ui/react-alert-dialog](https://www.npmjs.com/package/@radix-ui/react-alert-dialog) | 1.1.15 | MIT | https://radix-ui.com/primitives |
|
|
@@ -50,7 +50,6 @@ This Software contains code from the following open source projects:
|
|
|
50
50
|
| [@radix-ui/react-toggle-group](https://www.npmjs.com/package/@radix-ui/react-toggle-group) | 1.1.11 | MIT | https://radix-ui.com/primitives |
|
|
51
51
|
| [@radix-ui/react-tooltip](https://www.npmjs.com/package/@radix-ui/react-tooltip) | 1.2.8 | MIT | https://radix-ui.com/primitives |
|
|
52
52
|
| [@tanstack/react-table](https://www.npmjs.com/package/@tanstack/react-table) | 8.21.3 | MIT | https://tanstack.com/table |
|
|
53
|
-
| [@tanstack/react-virtual](https://www.npmjs.com/package/@tanstack/react-virtual) | 3.13.12 | MIT | https://tanstack.com/virtual |
|
|
54
53
|
| [@types/semver](https://www.npmjs.com/package/@types/semver) | 7.7.1 | MIT | https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/semver |
|
|
55
54
|
| [ajv](https://www.npmjs.com/package/ajv) | 6.12.6, 8.17.1 | MIT | https://ajv.js.org |
|
|
56
55
|
| [ajv-formats](https://www.npmjs.com/package/ajv-formats) | 2.1.1, 3.0.1 | MIT | https://github.com/ajv-validator/ajv-formats#readme |
|
|
@@ -59,8 +58,8 @@ This Software contains code from the following open source projects:
|
|
|
59
58
|
| [clsx](https://www.npmjs.com/package/clsx) | 2.1.1 | MIT | https://github.com/lukeed/clsx#readme |
|
|
60
59
|
| [cmdk](https://www.npmjs.com/package/cmdk) | 1.1.1 | MIT | https://github.com/pacocoursey/cmdk#readme |
|
|
61
60
|
| [commander](https://www.npmjs.com/package/commander) | 2.20.3, 5.1.0, 7.2.0, 8.3.0, 10.0.1, 12.1.0 | MIT | https://github.com/tj/commander.js#readme |
|
|
62
|
-
| [date-fns](https://www.npmjs.com/package/date-fns) | 4.1.0 | MIT | https://github.com/date-fns/date-fns#readme |
|
|
63
61
|
| [dotenv](https://www.npmjs.com/package/dotenv) | 16.6.1 | BSD-2-Clause | https://github.com/motdotla/dotenv#readme |
|
|
62
|
+
| [echarts](https://www.npmjs.com/package/echarts) | 6.0.0 | Apache-2.0 | https://echarts.apache.org |
|
|
64
63
|
| [echarts-for-react](https://www.npmjs.com/package/echarts-for-react) | 3.0.5 | MIT | https://github.com/hustcc/echarts-for-react |
|
|
65
64
|
| [embla-carousel-react](https://www.npmjs.com/package/embla-carousel-react) | 8.6.0 | MIT | https://www.embla-carousel.com |
|
|
66
65
|
| [express](https://www.npmjs.com/package/express) | 4.22.0 | MIT | http://expressjs.com/ |
|
|
@@ -79,6 +78,4 @@ This Software contains code from the following open source projects:
|
|
|
79
78
|
| [tailwind-merge](https://www.npmjs.com/package/tailwind-merge) | 3.4.0 | MIT | https://github.com/dcastil/tailwind-merge |
|
|
80
79
|
| [vaul](https://www.npmjs.com/package/vaul) | 1.1.2 | MIT | https://vaul.emilkowal.ski/ |
|
|
81
80
|
| [ws](https://www.npmjs.com/package/ws) | 7.5.10, 8.18.3 | MIT | https://github.com/websockets/ws |
|
|
82
|
-
| [zod](https://www.npmjs.com/package/zod) | 4.1.13 | MIT | https://zod.dev |
|
|
83
|
-
| [zod-to-ts](https://www.npmjs.com/package/zod-to-ts) | 2.0.0 | MIT | https://github.com/sachinraja/zod-to-ts#readme |
|
|
84
81
|
|
package/dist/app/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/app/index.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { createLogger } from \"../logging/logger\";\n\nconst logger = createLogger(\"app\");\n\ninterface RequestLike {\n query?: Record<string, any>;\n headers: Record<string, string | string[] | undefined>;\n}\n\ninterface DevFileReader {\n readFile(filePath: string, req: RequestLike): Promise<string>;\n readdir(dirPath: string, req: RequestLike): Promise<string[]>;\n}\n\ninterface QueryResult {\n query: string;\n isAsUser: boolean;\n}\n\n/**\n * Abstraction for filesystem operations that works in both dev and production modes\n */\ninterface FileSystemAdapter {\n readdir(dirPath: string): Promise<string[]>;\n readFile(filePath: string): Promise<string>;\n}\n\nexport class AppManager {\n private readonly queriesDir = path.resolve(process.cwd(), \"config/queries\");\n\n /**\n * Validates that a file path is within the queries directory\n */\n private validatePath(fileName: string): string | null {\n const queryFilePath = path.join(this.queriesDir, fileName);\n const resolvedPath = path.resolve(queryFilePath);\n const resolvedQueriesDir = path.resolve(this.queriesDir);\n\n if (!resolvedPath.startsWith(resolvedQueriesDir)) {\n logger.error(\"Invalid query path: path traversal detected\");\n return null;\n }\n\n return resolvedPath;\n }\n\n /**\n * Creates a filesystem adapter based on dev mode or production mode\n */\n private createFsAdapter(\n req?: RequestLike,\n devFileReader?: DevFileReader,\n ): FileSystemAdapter {\n const isDevMode = req?.query?.dev !== undefined;\n\n if (isDevMode && devFileReader && req) {\n // Dev mode: use WebSocket tunnel to read from local filesystem\n return {\n readdir: async (dirPath: string) => {\n const relativePath = path.relative(process.cwd(), dirPath);\n return devFileReader.readdir(relativePath, req);\n },\n readFile: async (filePath: string) => {\n const relativePath = path.relative(process.cwd(), filePath);\n return devFileReader.readFile(relativePath, req);\n },\n };\n }\n\n // Production mode: use server filesystem\n return {\n readdir: (dirPath: string) => fs.readdir(dirPath),\n readFile: (filePath: string) => fs.readFile(filePath, \"utf8\"),\n };\n }\n\n /**\n * Retrieves a query file by key from the queries directory\n * In dev mode with a request context, reads from local filesystem via WebSocket\n * @param queryKey - The query file name (without extension)\n * @param req - Optional request object to detect dev mode\n * @param devFileReader - Optional DevFileReader instance to read files from local filesystem\n * @returns The query content and execution mode (as user or as service principal)\n */\n async getAppQuery(\n queryKey: string,\n req?: RequestLike,\n devFileReader?: DevFileReader,\n ): Promise<QueryResult | null> {\n // Security: Sanitize query key to prevent path traversal\n if (!queryKey || !/^[a-zA-Z0-9_-]+$/.test(queryKey)) {\n logger.error(\n \"Invalid query key format: %s. Only alphanumeric characters, underscores, and hyphens are allowed.\",\n queryKey,\n );\n return null;\n }\n\n // Create filesystem adapter for dev or production mode\n const fsAdapter = this.createFsAdapter(req, devFileReader);\n\n // Priority order: .obo.sql first (as user), then .sql (as service principal)\n const oboFileName = `${queryKey}.obo.sql`;\n const defaultFileName = `${queryKey}.sql`;\n\n // List directory to find which query file exists\n let files: string[];\n try {\n files = await fsAdapter.readdir(this.queriesDir);\n } catch (error) {\n logger.error(\n `Failed to read queries directory: ${(error as Error).message}`,\n );\n return null;\n }\n\n // Determine which query file to use\n let queryFileName: string | null = null;\n let isAsUser = false;\n\n if (files.includes(oboFileName)) {\n queryFileName = oboFileName;\n isAsUser = true;\n\n // Warn if both variants exist\n if (files.includes(defaultFileName)) {\n logger.warn(\n `Both ${oboFileName} and ${defaultFileName} found for query ${queryKey}. Using ${oboFileName}.`,\n );\n }\n } else if (files.includes(defaultFileName)) {\n queryFileName = defaultFileName;\n isAsUser = false;\n }\n\n if (!queryFileName) {\n logger.error(`Query file not found: ${queryKey}`);\n return null;\n }\n\n // Validate and resolve the file path\n const resolvedPath = this.validatePath(queryFileName);\n if (!resolvedPath) {\n return null;\n }\n\n // Read the query file\n try {\n const query = await fsAdapter.readFile(resolvedPath);\n return { query, isAsUser };\n } catch (error) {\n logger.error(`Failed to read query file: ${(error as Error).message}`);\n return null;\n }\n }\n}\n\nexport type { DevFileReader
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/app/index.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { createLogger } from \"../logging/logger\";\n\nconst logger = createLogger(\"app\");\n\ninterface RequestLike {\n query?: Record<string, any>;\n headers: Record<string, string | string[] | undefined>;\n}\n\ninterface DevFileReader {\n readFile(filePath: string, req: RequestLike): Promise<string>;\n readdir(dirPath: string, req: RequestLike): Promise<string[]>;\n}\n\ninterface QueryResult {\n query: string;\n isAsUser: boolean;\n}\n\n/**\n * Abstraction for filesystem operations that works in both dev and production modes\n */\ninterface FileSystemAdapter {\n readdir(dirPath: string): Promise<string[]>;\n readFile(filePath: string): Promise<string>;\n}\n\nexport class AppManager {\n private readonly queriesDir = path.resolve(process.cwd(), \"config/queries\");\n\n /**\n * Validates that a file path is within the queries directory\n */\n private validatePath(fileName: string): string | null {\n const queryFilePath = path.join(this.queriesDir, fileName);\n const resolvedPath = path.resolve(queryFilePath);\n const resolvedQueriesDir = path.resolve(this.queriesDir);\n\n if (!resolvedPath.startsWith(resolvedQueriesDir)) {\n logger.error(\"Invalid query path: path traversal detected\");\n return null;\n }\n\n return resolvedPath;\n }\n\n /**\n * Creates a filesystem adapter based on dev mode or production mode\n */\n private createFsAdapter(\n req?: RequestLike,\n devFileReader?: DevFileReader,\n ): FileSystemAdapter {\n const isDevMode = req?.query?.dev !== undefined;\n\n if (isDevMode && devFileReader && req) {\n // Dev mode: use WebSocket tunnel to read from local filesystem\n return {\n readdir: async (dirPath: string) => {\n const relativePath = path.relative(process.cwd(), dirPath);\n return devFileReader.readdir(relativePath, req);\n },\n readFile: async (filePath: string) => {\n const relativePath = path.relative(process.cwd(), filePath);\n return devFileReader.readFile(relativePath, req);\n },\n };\n }\n\n // Production mode: use server filesystem\n return {\n readdir: (dirPath: string) => fs.readdir(dirPath),\n readFile: (filePath: string) => fs.readFile(filePath, \"utf8\"),\n };\n }\n\n /**\n * Retrieves a query file by key from the queries directory\n * In dev mode with a request context, reads from local filesystem via WebSocket\n * @param queryKey - The query file name (without extension)\n * @param req - Optional request object to detect dev mode\n * @param devFileReader - Optional DevFileReader instance to read files from local filesystem\n * @returns The query content and execution mode (as user or as service principal)\n */\n async getAppQuery(\n queryKey: string,\n req?: RequestLike,\n devFileReader?: DevFileReader,\n ): Promise<QueryResult | null> {\n // Security: Sanitize query key to prevent path traversal\n if (!queryKey || !/^[a-zA-Z0-9_-]+$/.test(queryKey)) {\n logger.error(\n \"Invalid query key format: %s. Only alphanumeric characters, underscores, and hyphens are allowed.\",\n queryKey,\n );\n return null;\n }\n\n // Create filesystem adapter for dev or production mode\n const fsAdapter = this.createFsAdapter(req, devFileReader);\n\n // Priority order: .obo.sql first (as user), then .sql (as service principal)\n const oboFileName = `${queryKey}.obo.sql`;\n const defaultFileName = `${queryKey}.sql`;\n\n // List directory to find which query file exists\n let files: string[];\n try {\n files = await fsAdapter.readdir(this.queriesDir);\n } catch (error) {\n logger.error(\n `Failed to read queries directory: ${(error as Error).message}`,\n );\n return null;\n }\n\n // Determine which query file to use\n let queryFileName: string | null = null;\n let isAsUser = false;\n\n if (files.includes(oboFileName)) {\n queryFileName = oboFileName;\n isAsUser = true;\n\n // Warn if both variants exist\n if (files.includes(defaultFileName)) {\n logger.warn(\n `Both ${oboFileName} and ${defaultFileName} found for query ${queryKey}. Using ${oboFileName}.`,\n );\n }\n } else if (files.includes(defaultFileName)) {\n queryFileName = defaultFileName;\n isAsUser = false;\n }\n\n if (!queryFileName) {\n logger.error(`Query file not found: ${queryKey}`);\n return null;\n }\n\n // Validate and resolve the file path\n const resolvedPath = this.validatePath(queryFileName);\n if (!resolvedPath) {\n return null;\n }\n\n // Read the query file\n try {\n const query = await fsAdapter.readFile(resolvedPath);\n return { query, isAsUser };\n } catch (error) {\n logger.error(`Failed to read query file: ${(error as Error).message}`);\n return null;\n }\n }\n}\n\nexport type { DevFileReader };\n"],"mappings":";;;;;AAIA,MAAM,SAAS,aAAa,MAAM;AAyBlC,IAAa,aAAb,MAAwB;CACtB,AAAiB,aAAa,KAAK,QAAQ,QAAQ,KAAK,EAAE,iBAAiB;;;;CAK3E,AAAQ,aAAa,UAAiC;EACpD,MAAM,gBAAgB,KAAK,KAAK,KAAK,YAAY,SAAS;EAC1D,MAAM,eAAe,KAAK,QAAQ,cAAc;EAChD,MAAM,qBAAqB,KAAK,QAAQ,KAAK,WAAW;AAExD,MAAI,CAAC,aAAa,WAAW,mBAAmB,EAAE;AAChD,UAAO,MAAM,8CAA8C;AAC3D,UAAO;;AAGT,SAAO;;;;;CAMT,AAAQ,gBACN,KACA,eACmB;AAGnB,MAFkB,KAAK,OAAO,QAAQ,UAErB,iBAAiB,IAEhC,QAAO;GACL,SAAS,OAAO,YAAoB;IAClC,MAAM,eAAe,KAAK,SAAS,QAAQ,KAAK,EAAE,QAAQ;AAC1D,WAAO,cAAc,QAAQ,cAAc,IAAI;;GAEjD,UAAU,OAAO,aAAqB;IACpC,MAAM,eAAe,KAAK,SAAS,QAAQ,KAAK,EAAE,SAAS;AAC3D,WAAO,cAAc,SAAS,cAAc,IAAI;;GAEnD;AAIH,SAAO;GACL,UAAU,YAAoB,GAAG,QAAQ,QAAQ;GACjD,WAAW,aAAqB,GAAG,SAAS,UAAU,OAAO;GAC9D;;;;;;;;;;CAWH,MAAM,YACJ,UACA,KACA,eAC6B;AAE7B,MAAI,CAAC,YAAY,CAAC,mBAAmB,KAAK,SAAS,EAAE;AACnD,UAAO,MACL,qGACA,SACD;AACD,UAAO;;EAIT,MAAM,YAAY,KAAK,gBAAgB,KAAK,cAAc;EAG1D,MAAM,cAAc,GAAG,SAAS;EAChC,MAAM,kBAAkB,GAAG,SAAS;EAGpC,IAAI;AACJ,MAAI;AACF,WAAQ,MAAM,UAAU,QAAQ,KAAK,WAAW;WACzC,OAAO;AACd,UAAO,MACL,qCAAsC,MAAgB,UACvD;AACD,UAAO;;EAIT,IAAI,gBAA+B;EACnC,IAAI,WAAW;AAEf,MAAI,MAAM,SAAS,YAAY,EAAE;AAC/B,mBAAgB;AAChB,cAAW;AAGX,OAAI,MAAM,SAAS,gBAAgB,CACjC,QAAO,KACL,QAAQ,YAAY,OAAO,gBAAgB,mBAAmB,SAAS,UAAU,YAAY,GAC9F;aAEM,MAAM,SAAS,gBAAgB,EAAE;AAC1C,mBAAgB;AAChB,cAAW;;AAGb,MAAI,CAAC,eAAe;AAClB,UAAO,MAAM,yBAAyB,WAAW;AACjD,UAAO;;EAIT,MAAM,eAAe,KAAK,aAAa,cAAc;AACrD,MAAI,CAAC,aACH,QAAO;AAIT,MAAI;AAEF,UAAO;IAAE,OADK,MAAM,UAAU,SAAS,aAAa;IACpC;IAAU;WACnB,OAAO;AACd,UAAO,MAAM,8BAA+B,MAAgB,UAAU;AACtE,UAAO"}
|
package/dist/appkit/package.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add-resource.js","names":[],"sources":["../../../../../src/cli/commands/plugin/add-resource/add-resource.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { cancel, intro, outro } from \"@clack/prompts\";\nimport { Command } from \"commander\";\nimport { promptOneResource } from \"../create/prompt-resource\";\nimport { humanizeResourceType } from \"../create/resource-defaults\";\nimport { resolveManifestInDir } from \"../manifest-resolve\";\nimport type { PluginManifest } from \"../manifest-types\";\nimport { validateManifest } from \"../validate/validate-manifest\";\n\n/** Extended manifest type that preserves extra JSON fields (e.g. $schema, author, version) for round-trip writes. */\ninterface ManifestWithExtras extends PluginManifest {\n [key: string]: unknown;\n}\n\nasync function runPluginAddResource(options: { path?: string }): Promise<void> {\n intro(\"Add resource to plugin manifest\");\n\n const cwd = process.cwd();\n const pluginDir = path.resolve(cwd, options.path ?? \".\");\n const resolved = resolveManifestInDir(pluginDir, { allowJsManifest: true });\n\n if (!resolved) {\n console.error(\n `No manifest found in ${pluginDir}. This command requires manifest.json (manifest.js cannot be edited in place).`,\n );\n process.exit(1);\n }\n\n if (resolved.type !== \"json\") {\n console.error(\n `Editable manifest not found. add-resource only supports plugin directories that contain manifest.json (found ${path.basename(resolved.path)}).`,\n );\n process.exit(1);\n }\n\n const manifestPath = resolved.path;\n\n let manifest: ManifestWithExtras;\n try {\n const raw = fs.readFileSync(manifestPath, \"utf-8\");\n const parsed = JSON.parse(raw) as unknown;\n const result = validateManifest(parsed);\n if (!result.valid || !result.manifest) {\n console.error(\n \"Invalid manifest. Run `appkit plugin validate` for details.\",\n );\n process.exit(1);\n }\n manifest = parsed as ManifestWithExtras;\n } catch (err) {\n console.error(\n \"Failed to read or parse manifest.json:\",\n err instanceof Error ? err.message : err,\n );\n process.exit(1);\n }\n\n const spec = await promptOneResource();\n if (!spec) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const alias = humanizeResourceType(spec.type);\n const entry = {\n type: spec.type,\n alias,\n resourceKey: spec.resourceKey,\n description: spec.description || `Required for ${alias} functionality.`,\n permission: spec.permission,\n fields: spec.fields,\n };\n\n if (spec.required) {\n manifest.resources.required.push(entry);\n } else {\n manifest.resources.optional.push(entry);\n }\n\n fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\\n`);\n\n outro(\"Resource added.\");\n console.log(\n `\\nAdded ${alias} as ${spec.required ? \"required\" : \"optional\"} to ${path.relative(cwd, manifestPath)}`,\n );\n}\n\nexport const pluginAddResourceCommand = new Command(\"add-resource\")\n .description(\n \"Add a resource requirement to an existing plugin manifest (interactive). Overwrites manifest.json in place.\",\n )\n .option(\n \"-p, --path <dir>\",\n \"Plugin directory containing manifest.json, which will be edited in place (default: .)\",\n )\n .action((opts) =>\n runPluginAddResource(opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":";;;;;;;;;;;AAgBA,eAAe,qBAAqB,SAA2C;AAC7E,OAAM,kCAAkC;CAExC,MAAM,MAAM,QAAQ,KAAK;CACzB,MAAM,YAAY,KAAK,QAAQ,KAAK,QAAQ,QAAQ,IAAI;CACxD,MAAM,WAAW,qBAAqB,WAAW,EAAE,iBAAiB,MAAM,CAAC;AAE3E,KAAI,CAAC,UAAU;AACb,UAAQ,MACN,wBAAwB,UAAU,gFACnC;AACD,UAAQ,KAAK,EAAE;;AAGjB,KAAI,SAAS,SAAS,QAAQ;AAC5B,UAAQ,MACN,gHAAgH,KAAK,SAAS,SAAS,KAAK,CAAC,IAC9I;AACD,UAAQ,KAAK,EAAE;;CAGjB,MAAM,eAAe,SAAS;CAE9B,IAAI;AACJ,KAAI;EACF,MAAM,MAAM,GAAG,aAAa,cAAc,QAAQ;EAClD,MAAM,SAAS,KAAK,MAAM,IAAI;EAC9B,MAAM,SAAS,iBAAiB,OAAO;AACvC,MAAI,CAAC,OAAO,SAAS,CAAC,OAAO,UAAU;AACrC,WAAQ,MACN,8DACD;AACD,WAAQ,KAAK,EAAE;;AAEjB,aAAW;UACJ,KAAK;AACZ,UAAQ,MACN,0CACA,eAAe,QAAQ,IAAI,UAAU,IACtC;AACD,UAAQ,KAAK,EAAE;;CAGjB,MAAM,OAAO,MAAM,mBAAmB;AACtC,KAAI,CAAC,MAAM;AACT,SAAO,aAAa;AACpB,UAAQ,KAAK,EAAE;;CAGjB,MAAM,QAAQ,qBAAqB,KAAK,KAAK;CAC7C,MAAM,
|
|
1
|
+
{"version":3,"file":"add-resource.js","names":[],"sources":["../../../../../src/cli/commands/plugin/add-resource/add-resource.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { cancel, intro, outro } from \"@clack/prompts\";\nimport { Command } from \"commander\";\nimport { promptOneResource } from \"../create/prompt-resource\";\nimport { humanizeResourceType } from \"../create/resource-defaults\";\nimport { resolveManifestInDir } from \"../manifest-resolve\";\nimport type { PluginManifest, ResourceRequirement } from \"../manifest-types\";\nimport { validateManifest } from \"../validate/validate-manifest\";\n\n/** Extended manifest type that preserves extra JSON fields (e.g. $schema, author, version) for round-trip writes. */\ninterface ManifestWithExtras extends PluginManifest {\n [key: string]: unknown;\n}\n\nasync function runPluginAddResource(options: { path?: string }): Promise<void> {\n intro(\"Add resource to plugin manifest\");\n\n const cwd = process.cwd();\n const pluginDir = path.resolve(cwd, options.path ?? \".\");\n const resolved = resolveManifestInDir(pluginDir, { allowJsManifest: true });\n\n if (!resolved) {\n console.error(\n `No manifest found in ${pluginDir}. This command requires manifest.json (manifest.js cannot be edited in place).`,\n );\n process.exit(1);\n }\n\n if (resolved.type !== \"json\") {\n console.error(\n `Editable manifest not found. add-resource only supports plugin directories that contain manifest.json (found ${path.basename(resolved.path)}).`,\n );\n process.exit(1);\n }\n\n const manifestPath = resolved.path;\n\n let manifest: ManifestWithExtras;\n try {\n const raw = fs.readFileSync(manifestPath, \"utf-8\");\n const parsed = JSON.parse(raw) as unknown;\n const result = validateManifest(parsed);\n if (!result.valid || !result.manifest) {\n console.error(\n \"Invalid manifest. Run `appkit plugin validate` for details.\",\n );\n process.exit(1);\n }\n manifest = parsed as ManifestWithExtras;\n } catch (err) {\n console.error(\n \"Failed to read or parse manifest.json:\",\n err instanceof Error ? err.message : err,\n );\n process.exit(1);\n }\n\n const spec = await promptOneResource();\n if (!spec) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const alias = humanizeResourceType(spec.type);\n const entry: ResourceRequirement = {\n // Safe cast: spec.type comes from RESOURCE_TYPE_OPTIONS which reads values\n // from the same JSON schema that generates the ResourceType union.\n type: spec.type as ResourceRequirement[\"type\"],\n alias,\n resourceKey: spec.resourceKey,\n description: spec.description || `Required for ${alias} functionality.`,\n permission: spec.permission,\n fields: spec.fields,\n };\n\n if (spec.required) {\n manifest.resources.required.push(entry);\n } else {\n manifest.resources.optional.push(entry);\n }\n\n fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\\n`);\n\n outro(\"Resource added.\");\n console.log(\n `\\nAdded ${alias} as ${spec.required ? \"required\" : \"optional\"} to ${path.relative(cwd, manifestPath)}`,\n );\n}\n\nexport const pluginAddResourceCommand = new Command(\"add-resource\")\n .description(\n \"Add a resource requirement to an existing plugin manifest (interactive). Overwrites manifest.json in place.\",\n )\n .option(\n \"-p, --path <dir>\",\n \"Plugin directory containing manifest.json, which will be edited in place (default: .)\",\n )\n .action((opts) =>\n runPluginAddResource(opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":";;;;;;;;;;;AAgBA,eAAe,qBAAqB,SAA2C;AAC7E,OAAM,kCAAkC;CAExC,MAAM,MAAM,QAAQ,KAAK;CACzB,MAAM,YAAY,KAAK,QAAQ,KAAK,QAAQ,QAAQ,IAAI;CACxD,MAAM,WAAW,qBAAqB,WAAW,EAAE,iBAAiB,MAAM,CAAC;AAE3E,KAAI,CAAC,UAAU;AACb,UAAQ,MACN,wBAAwB,UAAU,gFACnC;AACD,UAAQ,KAAK,EAAE;;AAGjB,KAAI,SAAS,SAAS,QAAQ;AAC5B,UAAQ,MACN,gHAAgH,KAAK,SAAS,SAAS,KAAK,CAAC,IAC9I;AACD,UAAQ,KAAK,EAAE;;CAGjB,MAAM,eAAe,SAAS;CAE9B,IAAI;AACJ,KAAI;EACF,MAAM,MAAM,GAAG,aAAa,cAAc,QAAQ;EAClD,MAAM,SAAS,KAAK,MAAM,IAAI;EAC9B,MAAM,SAAS,iBAAiB,OAAO;AACvC,MAAI,CAAC,OAAO,SAAS,CAAC,OAAO,UAAU;AACrC,WAAQ,MACN,8DACD;AACD,WAAQ,KAAK,EAAE;;AAEjB,aAAW;UACJ,KAAK;AACZ,UAAQ,MACN,0CACA,eAAe,QAAQ,IAAI,UAAU,IACtC;AACD,UAAQ,KAAK,EAAE;;CAGjB,MAAM,OAAO,MAAM,mBAAmB;AACtC,KAAI,CAAC,MAAM;AACT,SAAO,aAAa;AACpB,UAAQ,KAAK,EAAE;;CAGjB,MAAM,QAAQ,qBAAqB,KAAK,KAAK;CAC7C,MAAM,QAA6B;EAGjC,MAAM,KAAK;EACX;EACA,aAAa,KAAK;EAClB,aAAa,KAAK,eAAe,gBAAgB,MAAM;EACvD,YAAY,KAAK;EACjB,QAAQ,KAAK;EACd;AAED,KAAI,KAAK,SACP,UAAS,UAAU,SAAS,KAAK,MAAM;KAEvC,UAAS,UAAU,SAAS,KAAK,MAAM;AAGzC,IAAG,cAAc,cAAc,GAAG,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC,IAAI;AAExE,OAAM,kBAAkB;AACxB,SAAQ,IACN,WAAW,MAAM,MAAM,KAAK,WAAW,aAAa,WAAW,MAAM,KAAK,SAAS,KAAK,aAAa,GACtG;;AAGH,MAAa,2BAA2B,IAAI,QAAQ,eAAe,CAChE,YACC,8GACD,CACA,OACC,oBACA,wFACD,CACA,QAAQ,SACP,qBAAqB,KAAK,CAAC,OAAO,QAAQ;AACxC,SAAQ,MAAM,IAAI;AAClB,SAAQ,KAAK,EAAE;EACf,CACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../../../src/connectors/files/client.ts"],"sourcesContent":["import { ApiError, type WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport type { TelemetryOptions } from \"shared\";\nimport { createLogger } from \"../../logging/logger\";\nimport type {\n DirectoryEntry,\n DownloadResponse,\n FileMetadata,\n FilePreview,\n} from \"../../plugins/files/types\";\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 {\n contentTypeFromPath,\n FILES_MAX_READ_SIZE,\n isTextContentType,\n} from \"./defaults\";\n\nconst logger = createLogger(\"connectors:files\");\n\nexport interface FilesConnectorConfig {\n defaultVolume?: string;\n timeout?: number;\n telemetry?: TelemetryOptions;\n customContentTypes?: Record<string, string>;\n}\n\nexport class FilesConnector {\n private readonly name = \"files\";\n private defaultVolume: string | undefined;\n private readonly customContentTypes: Record<string, string> | undefined;\n\n private readonly telemetry: TelemetryProvider;\n private readonly telemetryMetrics: {\n operationCount: Counter;\n operationDuration: Histogram;\n };\n\n constructor(config: FilesConnectorConfig) {\n this.defaultVolume = config.defaultVolume;\n this.customContentTypes = config.customContentTypes;\n\n this.telemetry = TelemetryManager.getProvider(this.name, config.telemetry);\n this.telemetryMetrics = {\n operationCount: this.telemetry\n .getMeter()\n .createCounter(\"files.operation.count\", {\n description: \"Total number of file operations\",\n unit: \"1\",\n }),\n operationDuration: this.telemetry\n .getMeter()\n .createHistogram(\"files.operation.duration\", {\n description: \"Duration of file operations\",\n unit: \"ms\",\n }),\n };\n }\n\n resolvePath(filePath: string): string {\n if (filePath.length > 4096) {\n throw new Error(\n `Path exceeds maximum length of 4096 characters (got ${filePath.length}).`,\n );\n }\n if (filePath.includes(\"\\0\")) {\n throw new Error(\"Path must not contain null bytes.\");\n }\n\n const segments = filePath.split(\"/\");\n if (segments.some((s) => s === \"..\")) {\n throw new Error('Path traversal (\"../\") is not allowed.');\n }\n if (filePath.startsWith(\"/\")) {\n if (!filePath.startsWith(\"/Volumes/\")) {\n throw new Error(\n 'Absolute paths must start with \"/Volumes/\". ' +\n \"Unity Catalog volume paths follow the format: /Volumes/<catalog>/<schema>/<volume>/\",\n );\n }\n return filePath;\n }\n if (!this.defaultVolume) {\n throw new Error(\n \"Cannot resolve relative path: no default volume set. Use an absolute path or set a default volume.\",\n );\n }\n return `${this.defaultVolume}/${filePath}`;\n }\n\n private async traced<T>(\n operation: string,\n attributes: Record<string, string>,\n fn: (span: Span) => Promise<T>,\n ): Promise<T> {\n const startTime = Date.now();\n let success = false;\n\n return this.telemetry.startActiveSpan(\n `files.${operation}`,\n {\n kind: SpanKind.CLIENT,\n attributes: {\n \"files.operation\": operation,\n ...attributes,\n },\n },\n async (span: Span) => {\n try {\n const result = await fn(span);\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 throw error;\n } finally {\n span.end();\n const duration = Date.now() - startTime;\n const metricAttrs = {\n \"files.operation\": operation,\n success: String(success),\n };\n this.telemetryMetrics.operationCount.add(1, metricAttrs);\n this.telemetryMetrics.operationDuration.record(duration, metricAttrs);\n }\n },\n { name: this.name, includePrefix: true },\n );\n }\n\n async list(\n client: WorkspaceClient,\n directoryPath?: string,\n ): Promise<DirectoryEntry[]> {\n const resolvedPath = directoryPath\n ? this.resolvePath(directoryPath)\n : this.defaultVolume;\n if (!resolvedPath) {\n throw new Error(\"No directory path provided and no default volume set.\");\n }\n\n return this.traced(\"list\", { \"files.path\": resolvedPath }, async () => {\n const entries: DirectoryEntry[] = [];\n for await (const entry of client.files.listDirectoryContents({\n directory_path: resolvedPath,\n })) {\n entries.push(entry);\n }\n return entries;\n });\n }\n\n async read(\n client: WorkspaceClient,\n filePath: string,\n options?: { maxSize?: number },\n ): Promise<string> {\n const resolvedPath = this.resolvePath(filePath);\n const maxSize = options?.maxSize ?? FILES_MAX_READ_SIZE;\n return this.traced(\"read\", { \"files.path\": resolvedPath }, async () => {\n const response = await this.download(client, filePath);\n if (!response.contents) {\n return \"\";\n }\n const reader = response.contents.getReader();\n const decoder = new TextDecoder();\n let result = \"\";\n let bytesRead = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n bytesRead += value.byteLength;\n if (bytesRead > maxSize) {\n await reader.cancel();\n throw new Error(\n `File exceeds maximum read size (${maxSize} bytes). Use download() for large files.`,\n );\n }\n result += decoder.decode(value, { stream: true });\n }\n result += decoder.decode();\n return result;\n });\n }\n\n async download(\n client: WorkspaceClient,\n filePath: string,\n ): Promise<DownloadResponse> {\n const resolvedPath = this.resolvePath(filePath);\n return this.traced(\"download\", { \"files.path\": resolvedPath }, async () => {\n return client.files.download({\n file_path: resolvedPath,\n });\n });\n }\n\n async exists(client: WorkspaceClient, filePath: string): Promise<boolean> {\n const resolvedPath = this.resolvePath(filePath);\n return this.traced(\"exists\", { \"files.path\": resolvedPath }, async () => {\n try {\n await this.metadata(client, filePath);\n return true;\n } catch (error) {\n if (error instanceof ApiError && error.statusCode === 404) {\n return false;\n }\n throw error;\n }\n });\n }\n\n async metadata(\n client: WorkspaceClient,\n filePath: string,\n ): Promise<FileMetadata> {\n const resolvedPath = this.resolvePath(filePath);\n return this.traced(\"metadata\", { \"files.path\": resolvedPath }, async () => {\n const response = await client.files.getMetadata({\n file_path: resolvedPath,\n });\n return {\n contentLength: response[\"content-length\"],\n contentType: contentTypeFromPath(\n filePath,\n response[\"content-type\"],\n this.customContentTypes,\n ),\n lastModified: response[\"last-modified\"],\n };\n });\n }\n\n async upload(\n client: WorkspaceClient,\n filePath: string,\n contents: ReadableStream | Buffer | string,\n options?: { overwrite?: boolean },\n ): Promise<void> {\n const resolvedPath = this.resolvePath(filePath);\n\n return this.traced(\"upload\", { \"files.path\": resolvedPath }, async () => {\n const body = contents;\n const overwrite = options?.overwrite ?? true;\n\n // Workaround: The SDK's files.upload() has two bugs:\n // 1. It ignores the `contents` field (sets body to undefined)\n // 2. apiClient.request() checks `instanceof` against its own ReadableStream\n // subclass, so standard ReadableStream instances get JSON.stringified to \"{}\"\n // Bypass both by calling the REST API directly with SDK-provided auth.\n const hostValue = client.config.host;\n if (!hostValue) {\n throw new Error(\n \"Databricks host is not configured. Set DATABRICKS_HOST or configure client.config.host.\",\n );\n }\n const host = hostValue.startsWith(\"http\")\n ? hostValue\n : `https://${hostValue}`;\n const url = new URL(`/api/2.0/fs/files${resolvedPath}`, host);\n url.searchParams.set(\"overwrite\", String(overwrite));\n\n const headers = new Headers({\n \"Content-Type\": \"application/octet-stream\",\n });\n const fetchOptions: RequestInit = { method: \"PUT\", headers, body };\n\n if (body instanceof ReadableStream) {\n fetchOptions.duplex = \"half\";\n } else if (body instanceof Buffer) {\n headers.set(\"Content-Length\", String(body.length));\n } else if (typeof body === \"string\") {\n headers.set(\"Content-Length\", String(Buffer.byteLength(body)));\n }\n\n await client.config.authenticate(headers);\n\n const res = await fetch(url.toString(), fetchOptions);\n\n if (!res.ok) {\n const text = await res.text();\n logger.error(`Upload failed (${res.status}): ${text}`);\n const safeMessage = text.length > 200 ? `${text.slice(0, 200)}…` : text;\n throw new ApiError(\n `Upload failed: ${safeMessage}`,\n \"UPLOAD_FAILED\",\n res.status,\n undefined,\n [],\n );\n }\n });\n }\n\n async createDirectory(\n client: WorkspaceClient,\n directoryPath: string,\n ): Promise<void> {\n const resolvedPath = this.resolvePath(directoryPath);\n return this.traced(\n \"createDirectory\",\n { \"files.path\": resolvedPath },\n async () => {\n await client.files.createDirectory({\n directory_path: resolvedPath,\n });\n },\n );\n }\n\n async delete(client: WorkspaceClient, filePath: string): Promise<void> {\n const resolvedPath = this.resolvePath(filePath);\n return this.traced(\"delete\", { \"files.path\": resolvedPath }, async () => {\n await client.files.delete({\n file_path: resolvedPath,\n });\n });\n }\n\n async preview(\n client: WorkspaceClient,\n filePath: string,\n options?: { maxChars?: number },\n ): Promise<FilePreview> {\n const resolvedPath = this.resolvePath(filePath);\n return this.traced(\"preview\", { \"files.path\": resolvedPath }, async () => {\n const meta = await this.metadata(client, filePath);\n const isText = isTextContentType(meta.contentType);\n const isImage = meta.contentType?.startsWith(\"image/\") || false;\n\n if (!isText) {\n return { ...meta, textPreview: null, isText: false, isImage };\n }\n\n const response = await client.files.download({\n file_path: resolvedPath,\n });\n if (!response.contents) {\n return { ...meta, textPreview: \"\", isText: true, isImage: false };\n }\n\n const reader = response.contents.getReader();\n const decoder = new TextDecoder();\n let preview = \"\";\n const maxChars = options?.maxChars ?? 1024;\n\n while (preview.length < maxChars) {\n const { done, value } = await reader.read();\n if (done) break;\n preview += decoder.decode(value, { stream: true });\n }\n preview += decoder.decode();\n await reader.cancel();\n\n if (preview.length > maxChars) {\n preview = preview.slice(0, maxChars);\n }\n\n return { ...meta, textPreview: preview, isText: true, isImage: false };\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../../../src/connectors/files/client.ts"],"sourcesContent":["import { ApiError, type WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport type { TelemetryOptions } from \"shared\";\nimport { createLogger } from \"../../logging/logger\";\nimport type {\n DirectoryEntry,\n DownloadResponse,\n FileMetadata,\n FilePreview,\n} from \"../../plugins/files/types\";\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 {\n contentTypeFromPath,\n FILES_MAX_READ_SIZE,\n isTextContentType,\n} from \"./defaults\";\n\nconst logger = createLogger(\"connectors:files\");\n\ninterface FilesConnectorConfig {\n defaultVolume?: string;\n timeout?: number;\n telemetry?: TelemetryOptions;\n customContentTypes?: Record<string, string>;\n}\n\nexport class FilesConnector {\n private readonly name = \"files\";\n private defaultVolume: string | undefined;\n private readonly customContentTypes: Record<string, string> | undefined;\n\n private readonly telemetry: TelemetryProvider;\n private readonly telemetryMetrics: {\n operationCount: Counter;\n operationDuration: Histogram;\n };\n\n constructor(config: FilesConnectorConfig) {\n this.defaultVolume = config.defaultVolume;\n this.customContentTypes = config.customContentTypes;\n\n this.telemetry = TelemetryManager.getProvider(this.name, config.telemetry);\n this.telemetryMetrics = {\n operationCount: this.telemetry\n .getMeter()\n .createCounter(\"files.operation.count\", {\n description: \"Total number of file operations\",\n unit: \"1\",\n }),\n operationDuration: this.telemetry\n .getMeter()\n .createHistogram(\"files.operation.duration\", {\n description: \"Duration of file operations\",\n unit: \"ms\",\n }),\n };\n }\n\n resolvePath(filePath: string): string {\n if (filePath.length > 4096) {\n throw new Error(\n `Path exceeds maximum length of 4096 characters (got ${filePath.length}).`,\n );\n }\n if (filePath.includes(\"\\0\")) {\n throw new Error(\"Path must not contain null bytes.\");\n }\n\n const segments = filePath.split(\"/\");\n if (segments.some((s) => s === \"..\")) {\n throw new Error('Path traversal (\"../\") is not allowed.');\n }\n if (filePath.startsWith(\"/\")) {\n if (!filePath.startsWith(\"/Volumes/\")) {\n throw new Error(\n 'Absolute paths must start with \"/Volumes/\". ' +\n \"Unity Catalog volume paths follow the format: /Volumes/<catalog>/<schema>/<volume>/\",\n );\n }\n return filePath;\n }\n if (!this.defaultVolume) {\n throw new Error(\n \"Cannot resolve relative path: no default volume set. Use an absolute path or set a default volume.\",\n );\n }\n return `${this.defaultVolume}/${filePath}`;\n }\n\n private async traced<T>(\n operation: string,\n attributes: Record<string, string>,\n fn: (span: Span) => Promise<T>,\n ): Promise<T> {\n const startTime = Date.now();\n let success = false;\n\n return this.telemetry.startActiveSpan(\n `files.${operation}`,\n {\n kind: SpanKind.CLIENT,\n attributes: {\n \"files.operation\": operation,\n ...attributes,\n },\n },\n async (span: Span) => {\n try {\n const result = await fn(span);\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 throw error;\n } finally {\n span.end();\n const duration = Date.now() - startTime;\n const metricAttrs = {\n \"files.operation\": operation,\n success: String(success),\n };\n this.telemetryMetrics.operationCount.add(1, metricAttrs);\n this.telemetryMetrics.operationDuration.record(duration, metricAttrs);\n }\n },\n { name: this.name, includePrefix: true },\n );\n }\n\n async list(\n client: WorkspaceClient,\n directoryPath?: string,\n ): Promise<DirectoryEntry[]> {\n const resolvedPath = directoryPath\n ? this.resolvePath(directoryPath)\n : this.defaultVolume;\n if (!resolvedPath) {\n throw new Error(\"No directory path provided and no default volume set.\");\n }\n\n return this.traced(\"list\", { \"files.path\": resolvedPath }, async () => {\n const entries: DirectoryEntry[] = [];\n for await (const entry of client.files.listDirectoryContents({\n directory_path: resolvedPath,\n })) {\n entries.push(entry);\n }\n return entries;\n });\n }\n\n async read(\n client: WorkspaceClient,\n filePath: string,\n options?: { maxSize?: number },\n ): Promise<string> {\n const resolvedPath = this.resolvePath(filePath);\n const maxSize = options?.maxSize ?? FILES_MAX_READ_SIZE;\n return this.traced(\"read\", { \"files.path\": resolvedPath }, async () => {\n const response = await this.download(client, filePath);\n if (!response.contents) {\n return \"\";\n }\n const reader = response.contents.getReader();\n const decoder = new TextDecoder();\n let result = \"\";\n let bytesRead = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n bytesRead += value.byteLength;\n if (bytesRead > maxSize) {\n await reader.cancel();\n throw new Error(\n `File exceeds maximum read size (${maxSize} bytes). Use download() for large files.`,\n );\n }\n result += decoder.decode(value, { stream: true });\n }\n result += decoder.decode();\n return result;\n });\n }\n\n async download(\n client: WorkspaceClient,\n filePath: string,\n ): Promise<DownloadResponse> {\n const resolvedPath = this.resolvePath(filePath);\n return this.traced(\"download\", { \"files.path\": resolvedPath }, async () => {\n return client.files.download({\n file_path: resolvedPath,\n });\n });\n }\n\n async exists(client: WorkspaceClient, filePath: string): Promise<boolean> {\n const resolvedPath = this.resolvePath(filePath);\n return this.traced(\"exists\", { \"files.path\": resolvedPath }, async () => {\n try {\n await this.metadata(client, filePath);\n return true;\n } catch (error) {\n if (error instanceof ApiError && error.statusCode === 404) {\n return false;\n }\n throw error;\n }\n });\n }\n\n async metadata(\n client: WorkspaceClient,\n filePath: string,\n ): Promise<FileMetadata> {\n const resolvedPath = this.resolvePath(filePath);\n return this.traced(\"metadata\", { \"files.path\": resolvedPath }, async () => {\n const response = await client.files.getMetadata({\n file_path: resolvedPath,\n });\n return {\n contentLength: response[\"content-length\"],\n contentType: contentTypeFromPath(\n filePath,\n response[\"content-type\"],\n this.customContentTypes,\n ),\n lastModified: response[\"last-modified\"],\n };\n });\n }\n\n async upload(\n client: WorkspaceClient,\n filePath: string,\n contents: ReadableStream | Buffer | string,\n options?: { overwrite?: boolean },\n ): Promise<void> {\n const resolvedPath = this.resolvePath(filePath);\n\n return this.traced(\"upload\", { \"files.path\": resolvedPath }, async () => {\n const body = contents;\n const overwrite = options?.overwrite ?? true;\n\n // Workaround: The SDK's files.upload() has two bugs:\n // 1. It ignores the `contents` field (sets body to undefined)\n // 2. apiClient.request() checks `instanceof` against its own ReadableStream\n // subclass, so standard ReadableStream instances get JSON.stringified to \"{}\"\n // Bypass both by calling the REST API directly with SDK-provided auth.\n const hostValue = client.config.host;\n if (!hostValue) {\n throw new Error(\n \"Databricks host is not configured. Set DATABRICKS_HOST or configure client.config.host.\",\n );\n }\n const host = hostValue.startsWith(\"http\")\n ? hostValue\n : `https://${hostValue}`;\n const url = new URL(`/api/2.0/fs/files${resolvedPath}`, host);\n url.searchParams.set(\"overwrite\", String(overwrite));\n\n const headers = new Headers({\n \"Content-Type\": \"application/octet-stream\",\n });\n const fetchOptions: RequestInit = { method: \"PUT\", headers, body };\n\n if (body instanceof ReadableStream) {\n fetchOptions.duplex = \"half\";\n } else if (body instanceof Buffer) {\n headers.set(\"Content-Length\", String(body.length));\n } else if (typeof body === \"string\") {\n headers.set(\"Content-Length\", String(Buffer.byteLength(body)));\n }\n\n await client.config.authenticate(headers);\n\n const res = await fetch(url.toString(), fetchOptions);\n\n if (!res.ok) {\n const text = await res.text();\n logger.error(`Upload failed (${res.status}): ${text}`);\n const safeMessage = text.length > 200 ? `${text.slice(0, 200)}…` : text;\n throw new ApiError(\n `Upload failed: ${safeMessage}`,\n \"UPLOAD_FAILED\",\n res.status,\n undefined,\n [],\n );\n }\n });\n }\n\n async createDirectory(\n client: WorkspaceClient,\n directoryPath: string,\n ): Promise<void> {\n const resolvedPath = this.resolvePath(directoryPath);\n return this.traced(\n \"createDirectory\",\n { \"files.path\": resolvedPath },\n async () => {\n await client.files.createDirectory({\n directory_path: resolvedPath,\n });\n },\n );\n }\n\n async delete(client: WorkspaceClient, filePath: string): Promise<void> {\n const resolvedPath = this.resolvePath(filePath);\n return this.traced(\"delete\", { \"files.path\": resolvedPath }, async () => {\n await client.files.delete({\n file_path: resolvedPath,\n });\n });\n }\n\n async preview(\n client: WorkspaceClient,\n filePath: string,\n options?: { maxChars?: number },\n ): Promise<FilePreview> {\n const resolvedPath = this.resolvePath(filePath);\n return this.traced(\"preview\", { \"files.path\": resolvedPath }, async () => {\n const meta = await this.metadata(client, filePath);\n const isText = isTextContentType(meta.contentType);\n const isImage = meta.contentType?.startsWith(\"image/\") || false;\n\n if (!isText) {\n return { ...meta, textPreview: null, isText: false, isImage };\n }\n\n const response = await client.files.download({\n file_path: resolvedPath,\n });\n if (!response.contents) {\n return { ...meta, textPreview: \"\", isText: true, isImage: false };\n }\n\n const reader = response.contents.getReader();\n const decoder = new TextDecoder();\n let preview = \"\";\n const maxChars = options?.maxChars ?? 1024;\n\n while (preview.length < maxChars) {\n const { done, value } = await reader.read();\n if (done) break;\n preview += decoder.decode(value, { stream: true });\n }\n preview += decoder.decode();\n await reader.cancel();\n\n if (preview.length > maxChars) {\n preview = preview.slice(0, maxChars);\n }\n\n return { ...meta, textPreview: preview, isText: true, isImage: false };\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -127,5 +127,5 @@ function contentTypeFromPath(filePath, reported, customTypes) {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
//#endregion
|
|
130
|
-
export {
|
|
130
|
+
export { FILES_MAX_READ_SIZE, SAFE_INLINE_CONTENT_TYPES, contentTypeFromPath, isSafeInlineContentType, isTextContentType, validateCustomContentTypes };
|
|
131
131
|
//# sourceMappingURL=defaults.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.js","names":[],"sources":["../../../src/connectors/files/defaults.ts"],"sourcesContent":["/**\n * Default maximum size for `read()` in bytes (10 MB).\n * Prevents loading very large files into memory as a string.\n * Use `download()` for files larger than this limit.\n */\nexport const FILES_MAX_READ_SIZE = 10 * 1024 * 1024; // 10 MB\n\
|
|
1
|
+
{"version":3,"file":"defaults.js","names":[],"sources":["../../../src/connectors/files/defaults.ts"],"sourcesContent":["/**\n * Default maximum size for `read()` in bytes (10 MB).\n * Prevents loading very large files into memory as a string.\n * Use `download()` for files larger than this limit.\n */\nexport const FILES_MAX_READ_SIZE = 10 * 1024 * 1024; // 10 MB\n\nconst EXTENSION_CONTENT_TYPES: Record<string, string> = Object.freeze({\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".svg\": \"image/svg+xml\",\n \".bmp\": \"image/bmp\",\n \".ico\": \"image/vnd.microsoft.icon\",\n \".html\": \"text/html\",\n \".css\": \"text/css\",\n \".js\": \"text/javascript\",\n \".ts\": \"text/typescript\",\n \".py\": \"text/x-python\",\n \".txt\": \"text/plain\",\n \".md\": \"text/markdown\",\n \".csv\": \"text/csv\",\n \".json\": \"application/json\",\n \".jsonl\": \"application/x-ndjson\",\n \".xml\": \"application/xml\",\n \".yaml\": \"application/x-yaml\",\n \".yml\": \"application/x-yaml\",\n \".sql\": \"application/sql\",\n \".pdf\": \"application/pdf\",\n \".ipynb\": \"application/x-ipynb+json\",\n \".parquet\": \"application/vnd.apache.parquet\",\n \".zip\": \"application/zip\",\n \".gz\": \"application/gzip\",\n});\n\nconst TEXT_KEYWORDS = [\"json\", \"xml\", \"yaml\", \"sql\", \"javascript\"] as const;\n\n/**\n * Determine whether a content type represents text.\n *\n * Returns `true` for any `text/*` type and for known structured-text types\n * such as JSON, XML, YAML, SQL, and JavaScript.\n *\n * @param contentType - MIME content type string to check.\n * @returns `true` if the content type is text-based.\n */\nexport function isTextContentType(contentType: string | undefined): boolean {\n if (!contentType) return false;\n if (contentType.startsWith(\"text/\")) return true;\n return TEXT_KEYWORDS.some((kw) => contentType.includes(kw));\n}\n\n/**\n * MIME types that are safe to serve inline (i.e. browsers cannot execute\n * scripts from them). Any type **not** in this set should be forced to\n * download via `Content-Disposition: attachment` when served by the `/raw`\n * endpoint to prevent stored-XSS attacks.\n */\nexport const SAFE_INLINE_CONTENT_TYPES: ReadonlySet<string> = new Set([\n \"image/png\",\n \"image/jpeg\",\n \"image/gif\",\n \"image/webp\",\n \"image/bmp\",\n \"image/vnd.microsoft.icon\",\n \"text/plain\",\n \"text/csv\",\n \"text/markdown\",\n \"application/json\",\n \"application/pdf\",\n]);\n\n/**\n * Check whether a content type is safe to serve inline.\n *\n * @param contentType - MIME content type string.\n * @returns `true` if the type is in the safe-inline allowlist.\n */\nexport function isSafeInlineContentType(contentType: string): boolean {\n return SAFE_INLINE_CONTENT_TYPES.has(contentType);\n}\n\n/**\n * MIME types that must never be allowed in `customContentTypes` because\n * browsers can execute scripts from them. Allowing these in custom mappings\n * would bypass the `/raw` endpoint's forced-download protection for unsafe types.\n */\nconst DANGEROUS_CONTENT_TYPES: ReadonlySet<string> = new Set([\n \"text/html\",\n \"text/javascript\",\n \"application/javascript\",\n \"application/xhtml+xml\",\n \"image/svg+xml\",\n]);\n\n/**\n * Validate that a `customContentTypes` map does not contain any MIME types\n * that would bypass XSS protections when served inline.\n *\n * @param customTypes - Map of extension → MIME type overrides.\n * @throws {Error} if any mapping resolves to a dangerous MIME type.\n */\nexport function validateCustomContentTypes(\n customTypes: Record<string, string>,\n): void {\n for (const [ext, mimeType] of Object.entries(customTypes)) {\n const normalized = mimeType.toLowerCase().trim();\n if (DANGEROUS_CONTENT_TYPES.has(normalized)) {\n throw new Error(\n `Unsafe customContentTypes mapping: \"${ext}\" → \"${mimeType}\". ` +\n `MIME type \"${normalized}\" can execute scripts in browsers and is not allowed. ` +\n \"Remove this mapping or use a safe content type.\",\n );\n }\n }\n}\n\n/**\n * Resolve the MIME content type for a file path.\n *\n * @param filePath - Path to the file (only the extension is inspected).\n * @param reported - Optional MIME type reported by the caller; used as fallback when the extension is unknown.\n * @param customTypes - Optional map of extension → MIME type overrides (e.g. `{ \".csv\": \"text/csv\" }`).\n * @returns The resolved MIME content type string.\n */\nexport function contentTypeFromPath(\n filePath: string,\n reported?: string,\n customTypes?: Record<string, string>,\n): string {\n const dotIndex = filePath.lastIndexOf(\".\");\n const ext = dotIndex > 0 ? filePath.slice(dotIndex).toLowerCase() : \"\";\n const fromCustom = customTypes?.[ext];\n\n if (fromCustom) {\n return fromCustom;\n }\n\n const fromExt = EXTENSION_CONTENT_TYPES[ext];\n\n if (fromExt) {\n return fromExt;\n }\n\n return reported ?? \"application/octet-stream\";\n}\n"],"mappings":";;;;;;AAKA,MAAa,sBAAsB,KAAK,OAAO;AAE/C,MAAM,0BAAkD,OAAO,OAAO;CACpE,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,OAAO;CACP,OAAO;CACP,OAAO;CACP,QAAQ;CACR,OAAO;CACP,QAAQ;CACR,SAAS;CACT,UAAU;CACV,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,UAAU;CACV,YAAY;CACZ,QAAQ;CACR,OAAO;CACR,CAAC;AAEF,MAAM,gBAAgB;CAAC;CAAQ;CAAO;CAAQ;CAAO;CAAa;;;;;;;;;;AAWlE,SAAgB,kBAAkB,aAA0C;AAC1E,KAAI,CAAC,YAAa,QAAO;AACzB,KAAI,YAAY,WAAW,QAAQ,CAAE,QAAO;AAC5C,QAAO,cAAc,MAAM,OAAO,YAAY,SAAS,GAAG,CAAC;;;;;;;;AAS7D,MAAa,4BAAiD,IAAI,IAAI;CACpE;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;AAQF,SAAgB,wBAAwB,aAA8B;AACpE,QAAO,0BAA0B,IAAI,YAAY;;;;;;;AAQnD,MAAM,0BAA+C,IAAI,IAAI;CAC3D;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;AASF,SAAgB,2BACd,aACM;AACN,MAAK,MAAM,CAAC,KAAK,aAAa,OAAO,QAAQ,YAAY,EAAE;EACzD,MAAM,aAAa,SAAS,aAAa,CAAC,MAAM;AAChD,MAAI,wBAAwB,IAAI,WAAW,CACzC,OAAM,IAAI,MACR,uCAAuC,IAAI,OAAO,SAAS,gBAC3C,WAAW,uGAE5B;;;;;;;;;;;AAaP,SAAgB,oBACd,UACA,UACA,aACQ;CACR,MAAM,WAAW,SAAS,YAAY,IAAI;CAC1C,MAAM,MAAM,WAAW,IAAI,SAAS,MAAM,SAAS,CAAC,aAAa,GAAG;CACpE,MAAM,aAAa,cAAc;AAEjC,KAAI,WACF,QAAO;CAGT,MAAM,UAAU,wBAAwB;AAExC,KAAI,QACF,QAAO;AAGT,QAAO,YAAY"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FILES_MAX_READ_SIZE, SAFE_INLINE_CONTENT_TYPES, contentTypeFromPath, isSafeInlineContentType, isTextContentType, validateCustomContentTypes } from "./defaults.js";
|
|
2
2
|
import { FilesConnector } from "./client.js";
|
|
3
3
|
|
|
4
4
|
export { };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../../../src/connectors/genie/client.ts"],"sourcesContent":["import type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport * as SDK from \"@databricks/sdk-experimental\";\nimport type { GenieMessage } from \"@databricks/sdk-experimental/dist/apis/dashboards\";\nimport type { Waiter } from \"@databricks/sdk-experimental/dist/wait\";\nimport { createLogger } from \"../../logging\";\nimport { genieConnectorDefaults } from \"./defaults\";\nimport { pollWaiter } from \"./poll-waiter\";\nimport type {\n GenieAttachmentResponse,\n GenieConversationHistoryResponse,\n GenieMessageResponse,\n GenieStatementResponse,\n GenieStreamEvent,\n} from \"./types\";\n\nconst { TimeUnits } = SDK;\nconst Time = SDK.Time ?? (SDK as any).default.Time;\n\nconst logger = createLogger(\"connectors:genie\");\n\nconst GenieErrors = {\n SPACE_ACCESS_DENIED: \"You don't have access to this Genie Space.\",\n TABLE_PERMISSIONS:\n \"You may not have access to the data tables. Please verify your table permissions.\",\n REQUEST_FAILED: \"Genie request failed\",\n QUERY_RESULT_FAILED: \"Failed to fetch query result\",\n} as const;\n\ntype CreateMessageWaiter = Waiter<GenieMessage, GenieMessage>;\n\nexport interface GenieConnectorConfig {\n timeout?: number;\n maxMessages?: number;\n}\n\nfunction mapAttachments(message: GenieMessage): GenieAttachmentResponse[] {\n return (\n message.attachments?.map((att) => ({\n attachmentId: att.attachment_id,\n query: att.query\n ? {\n title: att.query.title,\n description: att.query.description,\n query: att.query.query,\n statementId: att.query.statement_id,\n }\n : undefined,\n text: att.text ? { content: att.text.content } : undefined,\n suggestedQuestions: att.suggested_questions?.questions,\n })) ?? []\n );\n}\n\nfunction toMessageResponse(message: GenieMessage): GenieMessageResponse {\n return {\n messageId: message.message_id,\n conversationId: message.conversation_id,\n spaceId: message.space_id,\n status: message.status ?? \"COMPLETED\",\n content: message.content,\n attachments: mapAttachments(message),\n error: message.error?.error,\n };\n}\n\nfunction classifyGenieError(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n\n if (message.includes(\"RESOURCE_DOES_NOT_EXIST\")) {\n return GenieErrors.SPACE_ACCESS_DENIED;\n }\n\n if (\n message.includes(\"failed to reach COMPLETED state\") &&\n message.includes(\"FAILED\")\n ) {\n return GenieErrors.TABLE_PERMISSIONS;\n }\n\n return message || GenieErrors.REQUEST_FAILED;\n}\n\nexport class GenieConnector {\n private readonly config: Required<GenieConnectorConfig>;\n\n constructor(config: GenieConnectorConfig = {}) {\n this.config = {\n timeout: config.timeout ?? genieConnectorDefaults.timeout,\n maxMessages: config.maxMessages ?? genieConnectorDefaults.maxMessages,\n };\n }\n\n async startMessage(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n content: string,\n conversationId: string | undefined,\n ): Promise<{\n messageWaiter: CreateMessageWaiter;\n conversationId: string;\n messageId: string;\n }> {\n if (conversationId) {\n const waiter = await workspaceClient.genie.createMessage({\n space_id: spaceId,\n conversation_id: conversationId,\n content,\n });\n return {\n messageWaiter: waiter,\n conversationId,\n messageId: waiter.message_id ?? \"\",\n };\n }\n const start = await workspaceClient.genie.startConversation({\n space_id: spaceId,\n content,\n });\n return {\n messageWaiter: start as unknown as CreateMessageWaiter,\n conversationId: start.conversation_id,\n messageId: start.message_id,\n };\n }\n\n async waitForMessage(\n messageWaiter: CreateMessageWaiter,\n options?: { timeout?: number },\n ): Promise<GenieMessage> {\n const timeout = options?.timeout ?? this.config.timeout;\n const waitOptions =\n timeout > 0 ? { timeout: new Time(timeout, TimeUnits.milliseconds) } : {};\n return messageWaiter.wait(waitOptions);\n }\n\n async listConversationMessages(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n conversationId: string,\n options?: { pageSize?: number; pageToken?: string },\n ): Promise<{\n messages: GenieMessageResponse[];\n nextPageToken: string | null;\n }> {\n const pageSize =\n options?.pageSize ?? genieConnectorDefaults.initialPageSize;\n\n const response = await workspaceClient.genie.listConversationMessages({\n space_id: spaceId,\n conversation_id: conversationId,\n page_size: pageSize,\n ...(options?.pageToken ? { page_token: options.pageToken } : {}),\n });\n\n const messages = (response.messages ?? []).reverse().map(toMessageResponse);\n\n return {\n messages,\n nextPageToken: response.next_page_token ?? null,\n };\n }\n\n async getMessageAttachmentQueryResult(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n conversationId: string,\n messageId: string,\n attachmentId: string,\n _signal?: AbortSignal,\n ): Promise<GenieStatementResponse> {\n const response =\n await workspaceClient.genie.getMessageAttachmentQueryResult({\n space_id: spaceId,\n conversation_id: conversationId,\n message_id: messageId,\n attachment_id: attachmentId,\n });\n return response.statement_response as GenieStatementResponse;\n }\n\n async *streamSendMessage(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n content: string,\n conversationId: string | undefined,\n options?: { timeout?: number },\n ): AsyncGenerator<GenieStreamEvent> {\n try {\n const {\n messageWaiter,\n conversationId: resultConversationId,\n messageId: resultMessageId,\n } = await this.startMessage(\n workspaceClient,\n spaceId,\n content,\n conversationId,\n );\n\n yield {\n type: \"message_start\",\n conversationId: resultConversationId,\n messageId: resultMessageId,\n spaceId,\n };\n\n const timeout =\n options?.timeout != null ? options.timeout : this.config.timeout;\n const waitOptions =\n timeout > 0\n ? { timeout: new Time(timeout, TimeUnits.milliseconds) }\n : {};\n\n let completedMessage!: GenieMessage;\n for await (const event of pollWaiter(messageWaiter, waitOptions)) {\n if (event.type === \"progress\" && event.value.status) {\n yield { type: \"status\", status: event.value.status };\n } else if (event.type === \"completed\") {\n completedMessage = event.value;\n }\n }\n\n const messageResponse = toMessageResponse(completedMessage);\n yield { type: \"message_result\", message: messageResponse };\n\n yield* this.emitQueryResults(\n workspaceClient,\n spaceId,\n resultConversationId,\n messageResponse.messageId,\n messageResponse,\n );\n } catch (error) {\n logger.error(\n \"Genie message error (spaceId=%s, conversationId=%s): %O\",\n spaceId,\n conversationId ?? \"new\",\n error,\n );\n yield { type: \"error\", error: classifyGenieError(error) };\n }\n }\n\n private async *emitQueryResults(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n conversationId: string,\n messageId: string,\n messageResponse: GenieMessageResponse,\n ): AsyncGenerator<\n Extract<GenieStreamEvent, { type: \"query_result\" } | { type: \"error\" }>\n > {\n const attachments = messageResponse.attachments ?? [];\n for (const att of attachments) {\n if (!att.query?.statementId || !att.attachmentId) continue;\n try {\n const data = await this.getMessageAttachmentQueryResult(\n workspaceClient,\n spaceId,\n conversationId,\n messageId,\n att.attachmentId,\n );\n yield {\n type: \"query_result\",\n attachmentId: att.attachmentId,\n statementId: att.query.statementId,\n data,\n };\n } catch (error) {\n logger.error(\n \"Failed to fetch query result for attachment %s: %O\",\n att.attachmentId,\n error,\n );\n yield {\n type: \"error\",\n error: `${GenieErrors.QUERY_RESULT_FAILED} for attachment ${att.attachmentId}`,\n };\n }\n }\n }\n\n async *streamConversation(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n conversationId: string,\n options?: {\n includeQueryResults?: boolean;\n pageSize?: number;\n pageToken?: string;\n },\n ): AsyncGenerator<GenieStreamEvent> {\n const includeQueryResults = options?.includeQueryResults !== false;\n\n try {\n const { messages: messageResponses, nextPageToken } =\n await this.listConversationMessages(\n workspaceClient,\n spaceId,\n conversationId,\n { pageSize: options?.pageSize, pageToken: options?.pageToken },\n );\n\n for (const messageResponse of messageResponses) {\n yield { type: \"message_result\", message: messageResponse };\n }\n\n yield {\n type: \"history_info\",\n conversationId,\n spaceId,\n nextPageToken,\n loadedCount: messageResponses.length,\n };\n\n if (includeQueryResults) {\n const queryAttachments: Array<{\n messageId: string;\n attachmentId: string;\n statementId: string;\n }> = [];\n\n for (const msg of messageResponses) {\n for (const att of msg.attachments ?? []) {\n if (att.query?.statementId && att.attachmentId) {\n queryAttachments.push({\n messageId: msg.messageId,\n attachmentId: att.attachmentId,\n statementId: att.query.statementId,\n });\n }\n }\n }\n\n const results = await Promise.allSettled(\n queryAttachments.map(async (att) => {\n const data = await this.getMessageAttachmentQueryResult(\n workspaceClient,\n spaceId,\n conversationId,\n att.messageId,\n att.attachmentId,\n );\n return {\n attachmentId: att.attachmentId,\n statementId: att.statementId,\n data,\n };\n }),\n );\n\n for (const result of results) {\n if (result.status === \"fulfilled\") {\n yield {\n type: \"query_result\",\n attachmentId: result.value.attachmentId,\n statementId: result.value.statementId,\n data: result.value.data,\n };\n } else {\n logger.error(\"Failed to fetch query result: %O\", result.reason);\n yield {\n type: \"error\",\n error:\n result.reason instanceof Error\n ? result.reason.message\n : GenieErrors.QUERY_RESULT_FAILED,\n };\n }\n }\n }\n } catch (error) {\n logger.error(\n \"Genie getConversation error (spaceId=%s, conversationId=%s): %O\",\n spaceId,\n conversationId,\n error,\n );\n yield { type: \"error\", error: classifyGenieError(error) };\n }\n }\n\n async sendMessage(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n content: string,\n conversationId: string | undefined,\n ): Promise<GenieMessageResponse> {\n const { messageWaiter, conversationId: resultConversationId } =\n await this.startMessage(\n workspaceClient,\n spaceId,\n content,\n conversationId,\n );\n const completedMessage = await this.waitForMessage(messageWaiter);\n const messageResponse = toMessageResponse(completedMessage);\n return {\n ...messageResponse,\n conversationId: resultConversationId,\n };\n }\n\n async getConversation(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n conversationId: string,\n ): Promise<GenieConversationHistoryResponse> {\n const allMessages: GenieMessageResponse[] = [];\n let pageToken: string | undefined;\n\n do {\n const { messages, nextPageToken } = await this.listConversationMessages(\n workspaceClient,\n spaceId,\n conversationId,\n {\n pageSize: genieConnectorDefaults.pageSize,\n pageToken,\n },\n );\n allMessages.push(...messages);\n pageToken = nextPageToken ?? undefined;\n } while (pageToken && allMessages.length < this.config.maxMessages);\n\n return {\n conversationId,\n spaceId,\n messages: allMessages.slice(0, this.config.maxMessages),\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../../../src/connectors/genie/client.ts"],"sourcesContent":["import type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport * as SDK from \"@databricks/sdk-experimental\";\nimport type { GenieMessage } from \"@databricks/sdk-experimental/dist/apis/dashboards\";\nimport type { Waiter } from \"@databricks/sdk-experimental/dist/wait\";\nimport { createLogger } from \"../../logging\";\nimport { genieConnectorDefaults } from \"./defaults\";\nimport { pollWaiter } from \"./poll-waiter\";\nimport type {\n GenieAttachmentResponse,\n GenieConversationHistoryResponse,\n GenieMessageResponse,\n GenieStatementResponse,\n GenieStreamEvent,\n} from \"./types\";\n\nconst { TimeUnits } = SDK;\nconst Time = SDK.Time ?? (SDK as any).default.Time;\n\nconst logger = createLogger(\"connectors:genie\");\n\nconst GenieErrors = {\n SPACE_ACCESS_DENIED: \"You don't have access to this Genie Space.\",\n TABLE_PERMISSIONS:\n \"You may not have access to the data tables. Please verify your table permissions.\",\n REQUEST_FAILED: \"Genie request failed\",\n QUERY_RESULT_FAILED: \"Failed to fetch query result\",\n} as const;\n\ntype CreateMessageWaiter = Waiter<GenieMessage, GenieMessage>;\n\ninterface GenieConnectorConfig {\n timeout?: number;\n maxMessages?: number;\n}\n\nfunction mapAttachments(message: GenieMessage): GenieAttachmentResponse[] {\n return (\n message.attachments?.map((att) => ({\n attachmentId: att.attachment_id,\n query: att.query\n ? {\n title: att.query.title,\n description: att.query.description,\n query: att.query.query,\n statementId: att.query.statement_id,\n }\n : undefined,\n text: att.text ? { content: att.text.content } : undefined,\n suggestedQuestions: att.suggested_questions?.questions,\n })) ?? []\n );\n}\n\nfunction toMessageResponse(message: GenieMessage): GenieMessageResponse {\n return {\n messageId: message.message_id,\n conversationId: message.conversation_id,\n spaceId: message.space_id,\n status: message.status ?? \"COMPLETED\",\n content: message.content,\n attachments: mapAttachments(message),\n error: message.error?.error,\n };\n}\n\nfunction classifyGenieError(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n\n if (message.includes(\"RESOURCE_DOES_NOT_EXIST\")) {\n return GenieErrors.SPACE_ACCESS_DENIED;\n }\n\n if (\n message.includes(\"failed to reach COMPLETED state\") &&\n message.includes(\"FAILED\")\n ) {\n return GenieErrors.TABLE_PERMISSIONS;\n }\n\n return message || GenieErrors.REQUEST_FAILED;\n}\n\nexport class GenieConnector {\n private readonly config: Required<GenieConnectorConfig>;\n\n constructor(config: GenieConnectorConfig = {}) {\n this.config = {\n timeout: config.timeout ?? genieConnectorDefaults.timeout,\n maxMessages: config.maxMessages ?? genieConnectorDefaults.maxMessages,\n };\n }\n\n async startMessage(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n content: string,\n conversationId: string | undefined,\n ): Promise<{\n messageWaiter: CreateMessageWaiter;\n conversationId: string;\n messageId: string;\n }> {\n if (conversationId) {\n const waiter = await workspaceClient.genie.createMessage({\n space_id: spaceId,\n conversation_id: conversationId,\n content,\n });\n return {\n messageWaiter: waiter,\n conversationId,\n messageId: waiter.message_id ?? \"\",\n };\n }\n const start = await workspaceClient.genie.startConversation({\n space_id: spaceId,\n content,\n });\n return {\n messageWaiter: start as unknown as CreateMessageWaiter,\n conversationId: start.conversation_id,\n messageId: start.message_id,\n };\n }\n\n async waitForMessage(\n messageWaiter: CreateMessageWaiter,\n options?: { timeout?: number },\n ): Promise<GenieMessage> {\n const timeout = options?.timeout ?? this.config.timeout;\n const waitOptions =\n timeout > 0 ? { timeout: new Time(timeout, TimeUnits.milliseconds) } : {};\n return messageWaiter.wait(waitOptions);\n }\n\n async listConversationMessages(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n conversationId: string,\n options?: { pageSize?: number; pageToken?: string },\n ): Promise<{\n messages: GenieMessageResponse[];\n nextPageToken: string | null;\n }> {\n const pageSize =\n options?.pageSize ?? genieConnectorDefaults.initialPageSize;\n\n const response = await workspaceClient.genie.listConversationMessages({\n space_id: spaceId,\n conversation_id: conversationId,\n page_size: pageSize,\n ...(options?.pageToken ? { page_token: options.pageToken } : {}),\n });\n\n const messages = (response.messages ?? []).reverse().map(toMessageResponse);\n\n return {\n messages,\n nextPageToken: response.next_page_token ?? null,\n };\n }\n\n async getMessageAttachmentQueryResult(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n conversationId: string,\n messageId: string,\n attachmentId: string,\n _signal?: AbortSignal,\n ): Promise<GenieStatementResponse> {\n const response =\n await workspaceClient.genie.getMessageAttachmentQueryResult({\n space_id: spaceId,\n conversation_id: conversationId,\n message_id: messageId,\n attachment_id: attachmentId,\n });\n return response.statement_response as GenieStatementResponse;\n }\n\n async *streamSendMessage(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n content: string,\n conversationId: string | undefined,\n options?: { timeout?: number },\n ): AsyncGenerator<GenieStreamEvent> {\n try {\n const {\n messageWaiter,\n conversationId: resultConversationId,\n messageId: resultMessageId,\n } = await this.startMessage(\n workspaceClient,\n spaceId,\n content,\n conversationId,\n );\n\n yield {\n type: \"message_start\",\n conversationId: resultConversationId,\n messageId: resultMessageId,\n spaceId,\n };\n\n const timeout =\n options?.timeout != null ? options.timeout : this.config.timeout;\n const waitOptions =\n timeout > 0\n ? { timeout: new Time(timeout, TimeUnits.milliseconds) }\n : {};\n\n let completedMessage!: GenieMessage;\n for await (const event of pollWaiter(messageWaiter, waitOptions)) {\n if (event.type === \"progress\" && event.value.status) {\n yield { type: \"status\", status: event.value.status };\n } else if (event.type === \"completed\") {\n completedMessage = event.value;\n }\n }\n\n const messageResponse = toMessageResponse(completedMessage);\n yield { type: \"message_result\", message: messageResponse };\n\n yield* this.emitQueryResults(\n workspaceClient,\n spaceId,\n resultConversationId,\n messageResponse.messageId,\n messageResponse,\n );\n } catch (error) {\n logger.error(\n \"Genie message error (spaceId=%s, conversationId=%s): %O\",\n spaceId,\n conversationId ?? \"new\",\n error,\n );\n yield { type: \"error\", error: classifyGenieError(error) };\n }\n }\n\n private async *emitQueryResults(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n conversationId: string,\n messageId: string,\n messageResponse: GenieMessageResponse,\n ): AsyncGenerator<\n Extract<GenieStreamEvent, { type: \"query_result\" } | { type: \"error\" }>\n > {\n const attachments = messageResponse.attachments ?? [];\n for (const att of attachments) {\n if (!att.query?.statementId || !att.attachmentId) continue;\n try {\n const data = await this.getMessageAttachmentQueryResult(\n workspaceClient,\n spaceId,\n conversationId,\n messageId,\n att.attachmentId,\n );\n yield {\n type: \"query_result\",\n attachmentId: att.attachmentId,\n statementId: att.query.statementId,\n data,\n };\n } catch (error) {\n logger.error(\n \"Failed to fetch query result for attachment %s: %O\",\n att.attachmentId,\n error,\n );\n yield {\n type: \"error\",\n error: `${GenieErrors.QUERY_RESULT_FAILED} for attachment ${att.attachmentId}`,\n };\n }\n }\n }\n\n async *streamConversation(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n conversationId: string,\n options?: {\n includeQueryResults?: boolean;\n pageSize?: number;\n pageToken?: string;\n },\n ): AsyncGenerator<GenieStreamEvent> {\n const includeQueryResults = options?.includeQueryResults !== false;\n\n try {\n const { messages: messageResponses, nextPageToken } =\n await this.listConversationMessages(\n workspaceClient,\n spaceId,\n conversationId,\n { pageSize: options?.pageSize, pageToken: options?.pageToken },\n );\n\n for (const messageResponse of messageResponses) {\n yield { type: \"message_result\", message: messageResponse };\n }\n\n yield {\n type: \"history_info\",\n conversationId,\n spaceId,\n nextPageToken,\n loadedCount: messageResponses.length,\n };\n\n if (includeQueryResults) {\n const queryAttachments: Array<{\n messageId: string;\n attachmentId: string;\n statementId: string;\n }> = [];\n\n for (const msg of messageResponses) {\n for (const att of msg.attachments ?? []) {\n if (att.query?.statementId && att.attachmentId) {\n queryAttachments.push({\n messageId: msg.messageId,\n attachmentId: att.attachmentId,\n statementId: att.query.statementId,\n });\n }\n }\n }\n\n const results = await Promise.allSettled(\n queryAttachments.map(async (att) => {\n const data = await this.getMessageAttachmentQueryResult(\n workspaceClient,\n spaceId,\n conversationId,\n att.messageId,\n att.attachmentId,\n );\n return {\n attachmentId: att.attachmentId,\n statementId: att.statementId,\n data,\n };\n }),\n );\n\n for (const result of results) {\n if (result.status === \"fulfilled\") {\n yield {\n type: \"query_result\",\n attachmentId: result.value.attachmentId,\n statementId: result.value.statementId,\n data: result.value.data,\n };\n } else {\n logger.error(\"Failed to fetch query result: %O\", result.reason);\n yield {\n type: \"error\",\n error:\n result.reason instanceof Error\n ? result.reason.message\n : GenieErrors.QUERY_RESULT_FAILED,\n };\n }\n }\n }\n } catch (error) {\n logger.error(\n \"Genie getConversation error (spaceId=%s, conversationId=%s): %O\",\n spaceId,\n conversationId,\n error,\n );\n yield { type: \"error\", error: classifyGenieError(error) };\n }\n }\n\n async sendMessage(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n content: string,\n conversationId: string | undefined,\n ): Promise<GenieMessageResponse> {\n const { messageWaiter, conversationId: resultConversationId } =\n await this.startMessage(\n workspaceClient,\n spaceId,\n content,\n conversationId,\n );\n const completedMessage = await this.waitForMessage(messageWaiter);\n const messageResponse = toMessageResponse(completedMessage);\n return {\n ...messageResponse,\n conversationId: resultConversationId,\n };\n }\n\n async getConversation(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n conversationId: string,\n ): Promise<GenieConversationHistoryResponse> {\n const allMessages: GenieMessageResponse[] = [];\n let pageToken: string | undefined;\n\n do {\n const { messages, nextPageToken } = await this.listConversationMessages(\n workspaceClient,\n spaceId,\n conversationId,\n {\n pageSize: genieConnectorDefaults.pageSize,\n pageToken,\n },\n );\n allMessages.push(...messages);\n pageToken = nextPageToken ?? undefined;\n } while (pageToken && allMessages.length < this.config.maxMessages);\n\n return {\n conversationId,\n spaceId,\n messages: allMessages.slice(0, this.config.maxMessages),\n };\n }\n}\n"],"mappings":"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"}
|
package/dist/connectors/index.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { RequestedClaimsPermissionSet, createLakebasePool, generateDatabaseCredential, getLakebaseOrmConfig, getLakebasePgConfig, getUsernameWithApiLookup, getWorkspaceClient } from "./lakebase/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import { FILES_MAX_READ_SIZE, SAFE_INLINE_CONTENT_TYPES, contentTypeFromPath, isSafeInlineContentType, isTextContentType, validateCustomContentTypes } from "./files/defaults.js";
|
|
3
3
|
import { FilesConnector } from "./files/client.js";
|
|
4
4
|
import "./files/index.js";
|
|
5
|
-
import { genieConnectorDefaults } from "./genie/defaults.js";
|
|
6
|
-
import { pollWaiter } from "./genie/poll-waiter.js";
|
|
7
5
|
import { GenieConnector } from "./genie/client.js";
|
|
8
6
|
import "./genie/index.js";
|
|
9
7
|
import "./lakebase-v1/index.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["createLakebasePool","createLakebasePoolBase"],"sources":["../../../src/connectors/lakebase/index.ts"],"sourcesContent":["import {\n createLakebasePool as createLakebasePoolBase,\n type LakebasePoolConfig,\n} from \"@databricks/lakebase\";\nimport type { Pool } from \"pg\";\nimport { createLogger } from \"@/logging/logger\";\n\n/**\n * Create a Lakebase pool with appkit's logger integration.\n * Telemetry automatically uses appkit's OpenTelemetry configuration via global registry.\n *\n * @param config - Lakebase pool configuration\n * @returns PostgreSQL pool with appkit integration\n */\nexport function createLakebasePool(config?: Partial<LakebasePoolConfig>): Pool {\n const logger = createLogger(\"connectors:lakebase\");\n\n return createLakebasePoolBase({\n ...config,\n logger,\n });\n}\n\n// Re-export everything else from lakebase\nexport {\n
|
|
1
|
+
{"version":3,"file":"index.js","names":["createLakebasePool","createLakebasePoolBase"],"sources":["../../../src/connectors/lakebase/index.ts"],"sourcesContent":["import {\n createLakebasePool as createLakebasePoolBase,\n type LakebasePoolConfig,\n} from \"@databricks/lakebase\";\nimport type { Pool } from \"pg\";\nimport { createLogger } from \"@/logging/logger\";\n\n/**\n * Create a Lakebase pool with appkit's logger integration.\n * Telemetry automatically uses appkit's OpenTelemetry configuration via global registry.\n *\n * @param config - Lakebase pool configuration\n * @returns PostgreSQL pool with appkit integration\n */\nexport function createLakebasePool(config?: Partial<LakebasePoolConfig>): Pool {\n const logger = createLogger(\"connectors:lakebase\");\n\n return createLakebasePoolBase({\n ...config,\n logger,\n });\n}\n\n// Re-export everything else from lakebase\nexport {\n type DatabaseCredential,\n type GenerateDatabaseCredentialRequest,\n generateDatabaseCredential,\n getLakebaseOrmConfig,\n getLakebasePgConfig,\n getUsernameWithApiLookup,\n getWorkspaceClient,\n type LakebasePoolConfig,\n type RequestedClaims,\n RequestedClaimsPermissionSet,\n type RequestedResource,\n} from \"@databricks/lakebase\";\n"],"mappings":";;;;;;;;;;;AAcA,SAAgBA,qBAAmB,QAA4C;CAC7E,MAAM,SAAS,aAAa,sBAAsB;AAElD,QAAOC,mBAAuB;EAC5B,GAAG;EACH;EACD,CAAC"}
|
|
@@ -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\nexport interface 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":";;;;;;;;;;;;;aAWsB;AActB,MAAM,SAAS,aAAa,2BAA2B;AAOvD,IAAa,wBAAb,MAAmC;CACjC,AAAiB,OAAO;CAExB,AAAQ;CAGR,AAAQ,kBAA+C;CAEvD,AAAiB;CACjB,AAAiB;CAKjB,YAAY,QAA4B;AACtC,OAAK,SAAS;AAEd,OAAK,YAAY,iBAAiB,YAChC,KAAK,MACL,KAAK,OAAO,UACb;AACD,OAAK,mBAAmB;GACtB,YAAY,KAAK,UAAU,UAAU,CAAC,cAAc,eAAe;IACjE,aAAa;IACb,MAAM;IACP,CAAC;GACF,eAAe,KAAK,UACjB,UAAU,CACV,gBAAgB,kBAAkB;IACjC,aAAa;IACb,MAAM;IACP,CAAC;GACL;;;;;;CAOH,IAAY,iBAAuC;AACjD,MAAI,CAAC,KAAK,gBACR,MAAK,kBAAkB,IAAI,qBAAqB;GAC9C,SAAS,KAAK,OAAO,WAAW,yBAAyB;GACzD,wBACE,qBAAqB;GACvB,SAAS,qBAAqB;GAC/B,CAAC;AAEJ,SAAO,KAAK;;CAGd,MAAM,iBACJ,iBACA,OACA,QACA;EACA,MAAM,YAAY,KAAK,KAAK;EAC5B,IAAI,UAAU;AAGd,MAAI,QAAQ,QACV,OAAM,eAAe,UAAU;AAGjC,SAAO,KAAK,UAAU,gBACpB,aACA;GACE,MAAM,SAAS;GACf,YAAY;IACV,aAAa;IACb,mBAAmB,MAAM,gBAAgB;IACzC,cAAc,MAAM,WAAW;IAC/B,aAAa,MAAM,UAAU;IAC7B,gBAAgB,MAAM,WAAW,UAAU,GAAG,IAAI,IAAI;IACtD,qBAAqB,CAAC,CAAC,MAAM;IAC9B;GACF,EACD,OAAO,SAAe;GACpB,IAAI;GACJ,IAAI,YAAY;AAEhB,OAAI,QAAQ;AACV,yBAAqB;AAEnB,SAAI,CAAC,KAAK,aAAa,CAAE;AACzB,iBAAY;AACZ,UAAK,aAAa,aAAa,KAAK;AACpC,UAAK,UAAU;MACb,MAAM,eAAe;MACrB,SAAS;MACV,CAAC;AACF,UAAK,KAAK;;AAEZ,WAAO,iBAAiB,SAAS,cAAc,EAAE,MAAM,MAAM,CAAC;;AAGhE,OAAI;AAEF,QAAI,CAAC,MAAM,UACT,OAAM,gBAAgB,aAAa,YAAY;AAGjD,QAAI,CAAC,MAAM,aACT,OAAM,gBAAgB,aAAa,eAAe;IAGpD,MAAM,OAAoC;KACxC,WAAW,MAAM;KACjB,YAAY,MAAM;KAClB,cAAc,MAAM;KACpB,SAAS,MAAM;KACf,QAAQ,MAAM;KACd,cACE,MAAM,gBAAgB,yBAAyB;KACjD,aACE,MAAM,eAAe,yBAAyB;KAChD,QAAQ,MAAM,UAAU,yBAAyB;KACjD,YAAY,MAAM;KAClB,WAAW,MAAM;KACjB,iBACE,MAAM,mBAAmB,yBAAyB;KACrD;AAED,SAAK,SAAS,wBAAwB,EACpC,mBAAmB,MAAM,cAC1B,CAAC;IAEF,MAAM,WACJ,MAAM,gBAAgB,mBAAmB,iBACvC,MACA,KAAK,eAAe,OAAO,CAC5B;AAEH,QAAI,CAAC,SACH,OAAM,gBAAgB,WAAW,gBAAgB;IAEnD,MAAM,SAAS,SAAS;IACxB,MAAM,cAAc,SAAS;AAE7B,SAAK,aAAa,mBAAmB,YAAY;AACjD,SAAK,SAAS,uBAAuB;KACnC,mBAAmB,SAAS;KAC5B,aAAa,QAAQ;KACtB,CAAC;IAEF,IAAI;AAIJ,YAAQ,QAAQ,OAAhB;KACE,KAAK;KACL,KAAK;AACH,WAAK,SAAS,6BAA6B,EACzC,aAAa,SAAS,QAAQ,OAC/B,CAAC;AACF,eAAS,MAAM,KAAK,wBAClB,iBACA,aACA,KAAK,OAAO,SACZ,OACD;AACD;KACF,KAAK;AACH,eAAS,KAAK,oBAAoB,SAAS;AAC3C;KACF,KAAK,SACH,OAAM,eAAe,gBAAgB,OAAO,OAAO,QAAQ;KAC7D,KAAK,WACH,OAAM,eAAe,UAAU;KACjC,KAAK,SACH,OAAM,eAAe,eAAe;KACtC,QACE,OAAM,eAAe,aACnB,OAAO,QAAQ,SAAS,UAAU,CACnC;;IAGL,MAAM,aAAa,OAAO;IAC1B,MAAM,WACJ,YAAY,MAAM,UAAU,YAAY,YAAY,UAAU;AAEhE,QAAI,WAAW,EACb,MAAK,aAAa,uBAAuB,SAAS;IAGpD,MAAM,WAAW,KAAK,KAAK,GAAG;AAC9B,WAAO,OAAO,EAAE,WAAW,iBAAiB;KAC1C,cAAc,MAAM;KACpB,eAAe;KACf,mBAAmB;KACpB,CAAC;AAEF,cAAU;AAEV,QAAI,CAAC,UACH,MAAK,UAAU,EAAE,MAAM,eAAe,IAAI,CAAC;AAE7C,WAAO;YACA,OAAO;AAEd,QAAI,CAAC,WAAW;AACd,UAAK,gBAAgB,MAAe;AACpC,UAAK,UAAU;MACb,MAAM,eAAe;MACrB,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;MAChE,CAAC;;AAGJ,QAAI,iBAAiB,YACnB,OAAM;AAER,UAAM,eAAe,gBACnB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CACvD;aACO;AAER,QAAI,gBAAgB,OAClB,QAAO,oBAAoB,SAAS,aAAa;IAGnD,MAAM,WAAW,KAAK,KAAK,GAAG;AAG9B,QAAI,CAAC,UACH,MAAK,KAAK;IAGZ,MAAM,aAAa;KACjB,mBAAmB,MAAM;KACzB,cAAc,MAAM,WAAW;KAC/B,aAAa,MAAM,UAAU;KAC7B,gBAAgB,MAAM,WAAW,UAAU,GAAG,IAAI,IAAI;KACtD,SAAS,QAAQ,UAAU;KAC5B;AAED,SAAK,iBAAiB,WAAW,IAAI,GAAG,WAAW;AACnD,SAAK,iBAAiB,cAAc,OAAO,UAAU,WAAW;;KAGpE;GAAE,MAAM,KAAK;GAAM,eAAe;GAAM,CACzC;;CAGH,MAAc,wBACZ,iBACA,aACA,UAAU,yBAAyB,SACnC,QACA;AACA,SAAO,KAAK,UAAU,gBACpB,YACA,EACE,YAAY;GACV,mBAAmB;GACnB,sBAAsB;GACvB,EACF,EACD,OAAO,SAAe;AACpB,OAAI;IACF,MAAM,YAAY,KAAK,KAAK;IAC5B,IAAI,QAAQ;IACZ,MAAM,uBAAuB;IAC7B,IAAI,YAAY;AAEhB,WAAO,MAAM;AACX;AACA,UAAK,aAAa,8BAA8B,UAAU;KAG1D,MAAM,cAAc,KAAK,KAAK,GAAG;AACjC,SAAI,cAAc,SAAS;MACzB,MAAM,QAAQ,eAAe,gBAC3B,kCAAkC,QAAQ,eAAe,YAAY,KACtE;AACD,WAAK,gBAAgB,MAAM;AAC3B,WAAK,UAAU,EAAE,MAAM,eAAe,OAAO,CAAC;AAC9C,YAAM;;AAGR,SAAI,QAAQ,SAAS;MACnB,MAAM,QAAQ,eAAe,UAAU;AACvC,WAAK,gBAAgB,MAAM;AAC3B,WAAK,UAAU,EAAE,MAAM,eAAe,OAAO,CAAC;AAC9C,YAAM;;AAGR,UAAK,SAAS,mBAAmB;MAC/B,gBAAgB;MAChB,iBAAiB;MACjB,mBAAmB;MACpB,CAAC;KAEF,MAAM,WACJ,MAAM,gBAAgB,mBAAmB,aACvC,EACE,cAAc,aACf,EACD,KAAK,eAAe,OAAO,CAC5B;AACH,SAAI,CAAC,SACH,OAAM,gBAAgB,WAAW,gBAAgB;KAGnD,MAAM,SAAS,SAAS;AAExB,UAAK,SAAS,wBAAwB;MACpC,aAAa,QAAQ;MACrB,gBAAgB;MACjB,CAAC;AAEF,aAAQ,QAAQ,OAAhB;MACE,KAAK;MACL,KAAK,UAEH;MACF,KAAK;AACH,YAAK,aAAa,uBAAuB,UAAU;AACnD,YAAK,aAAa,gCAAgC,YAAY;AAC9D,YAAK,SAAS,qBAAqB;QACjC,iBAAiB;QACjB,oBAAoB;QACrB,CAAC;AACF,YAAK,UAAU,EAAE,MAAM,eAAe,IAAI,CAAC;AAC3C,cAAO,KAAK,oBAAoB,SAAS;MAC3C,KAAK,SACH,OAAM,eAAe,gBAAgB,OAAO,OAAO,QAAQ;MAC7D,KAAK,WACH,OAAM,eAAe,UAAU;MACjC,KAAK,SACH,OAAM,eAAe,eAAe;MACtC,QACE,OAAM,eAAe,aACnB,OAAO,QAAQ,SAAS,UAAU,CACnC;;AAIL,WAAM,IAAI,SAAS,YAAY,WAAW,SAAS,MAAM,CAAC;AAC1D,aAAQ,KAAK,IAAI,QAAQ,GAAG,qBAAqB;;YAE5C,OAAO;AACd,SAAK,gBAAgB,MAAe;AACpC,SAAK,UAAU;KACb,MAAM,eAAe;KACrB,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;KAChE,CAAC;AAEF,QAAI,iBAAiB,YACnB,OAAM;AAER,UAAM,eAAe,gBACnB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CACvD;aACO;AACR,SAAK,KAAK;;KAGd;GAAE,MAAM,KAAK;GAAM,eAAe;GAAM,CACzC;;CAGH,AAAQ,oBAAoB,UAAiC;AAC3D,MAAI,SAAS,UAAU,WAAW,eAChC,QAAO,KAAK,sBAAsB,SAAS;AAG7C,MAAI,CAAC,SAAS,QAAQ,cAAc,CAAC,SAAS,UAAU,QAAQ,QAC9D,QAAO;EAGT,MAAM,UAAU,SAAS,SAAS,OAAO;EAEzC,MAAM,kBAAkB,SAAS,OAAO,WAAW,KAAK,QAAQ;GAC9D,MAAM,MAA+B,EAAE;AACvC,OAAI,SAAS,OAAO,UAAU;IAC5B,MAAM,SAAS,QAAQ;IACvB,MAAM,aAAa,QAAQ,QAAQ,UAAU;AAG7C,QACE,QAAQ,cAAc,YACtB,OAAO,UAAU,YACjB,UACC,MAAM,OAAO,OAAO,MAAM,OAAO,KAElC,KAAI;AACF,SAAI,cAAc,KAAK,MAAM,MAAM;YAC7B;AAEN,SAAI,cAAc;;QAGpB,KAAI,cAAc;KAEpB;AACF,UAAO;IACP;EAGF,MAAM,EAAE,YAAY,aAAa,GAAG,eAAe,SAAS;AAC5D,SAAO;GACL,GAAG;GACH,QAAQ;IACN,GAAG;IACH,MAAM;IACP;GACF;;CAGH,AAAQ,sBAAsB,UAE5B;AACA,SAAO,EACL,QAAQ;GACN,cAAc,SAAS;GACvB,QAAQ;IACN,OAAO,SAAS,QAAQ;IACxB,OAAO,SAAS,QAAQ;IACzB;GACF,EACF;;CAGH,MAAM,aACJ,iBACA,OACA,QAC+D;EAC/D,MAAM,YAAY,KAAK,KAAK;AAE5B,SAAO,KAAK,UAAU,gBACpB,iBACA;GACE,MAAM,SAAS;GACf,YAAY;IACV,aAAa;IACb,gBAAgB;IACjB;GACF,EACD,OAAO,SAAe;AACpB,OAAI;IACF,MAAM,WACJ,MAAM,gBAAgB,mBAAmB,aACvC,EAAE,cAAc,OAAO,EACvB,KAAK,eAAe,OAAO,CAC5B;IAEH,MAAM,SAAS,SAAS,QAAQ;IAChC,MAAM,SAAS,SAAS,UAAU;AAElC,QAAI,CAAC,UAAU,CAAC,OACd,OAAM,eAAe,YAAY,mBAAmB;AAGtD,SAAK,aAAa,qBAAqB,OAAO,OAAO;IAErD,MAAM,SAAS,MAAM,KAAK,eAAe,cACvC,QACA,QACA,OACD;AAED,SAAK,aAAa,yBAAyB,OAAO,KAAK,OAAO;AAC9D,SAAK,UAAU,EAAE,MAAM,eAAe,IAAI,CAAC;IAE3C,MAAM,WAAW,KAAK,KAAK,GAAG;AAC9B,SAAK,iBAAiB,cAAc,OAAO,UAAU;KACnD,WAAW;KACX,QAAQ;KACT,CAAC;AAEF,WAAO,OAAO,EAAE,WAAW,iBAAiB;KAC1C,uBAAuB,OAAO,KAAK;KACnC,cAAc;KACf,CAAC;AAEF,WAAO;YACA,OAAO;AACd,SAAK,UAAU;KACb,MAAM,eAAe;KACrB,SAAS,iBAAiB,QAAQ,MAAM,UAAU;KACnD,CAAC;AACF,SAAK,gBAAgB,MAAe;IAEpC,MAAM,WAAW,KAAK,KAAK,GAAG;AAC9B,SAAK,iBAAiB,cAAc,OAAO,UAAU;KACnD,WAAW;KACX,QAAQ;KACT,CAAC;AAEF,WAAO,MAAM,2BAA2B,OAAO,MAAM;AAErD,QAAI,iBAAiB,YACnB,OAAM;AAER,UAAM,eAAe,gBACnB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CACvD;;IAGN;;CAIH,AAAQ,eAAe,QAAsB;AAC3C,SAAO,IAAI,QAAQ,EACjB,mBAAmB;GACjB,yBAAyB,QAAQ,WAAW;GAC5C,0BAA0B,OAAmB;AAC3C,YAAQ,iBAAiB,SAAS,IAAI,EAAE,MAAM,MAAM,CAAC;;GAExD,EACF,CAAC"}
|
|
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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"}
|
package/dist/context/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import "./service-context.js";
|
|
2
|
+
import { UserContext } from "./user-context.js";
|
|
3
3
|
import { getExecutionContext } from "./execution-context.js";
|
package/dist/context/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { __esmMin } from "../_virtual/_rolldown/runtime.js";
|
|
2
2
|
import { ServiceContext, init_service_context } from "./service-context.js";
|
|
3
|
-
import { isUserContext } from "./user-context.js";
|
|
4
3
|
import { getCurrentUserId, getExecutionContext, getWarehouseId, getWorkspaceClient, getWorkspaceId, init_execution_context, isInUserContext, runInUserContext } from "./execution-context.js";
|
|
5
4
|
|
|
6
5
|
//#region src/context/index.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/context/index.ts"],"sourcesContent":["export {\n getCurrentUserId,\n getExecutionContext,\n getWarehouseId,\n getWorkspaceClient,\n getWorkspaceId,\n isInUserContext,\n runInUserContext,\n} from \"./execution-context\";\nexport { ServiceContext
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/context/index.ts"],"sourcesContent":["export {\n getCurrentUserId,\n getExecutionContext,\n getWarehouseId,\n getWorkspaceClient,\n getWorkspaceId,\n isInUserContext,\n runInUserContext,\n} from \"./execution-context\";\nexport { ServiceContext } from \"./service-context\";\nexport type { UserContext } from \"./user-context\";\n"],"mappings":";;;;;;yBAQ6B;uBACsB"}
|