@databricks/appkit 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +8 -1
- package/dist/appkit/package.js +1 -1
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +2 -2
- package/dist/cache/index.js.map +1 -1
- package/dist/cli/commands/plugin/create/scaffold.js +2 -8
- package/dist/cli/commands/plugin/create/scaffold.js.map +1 -1
- package/dist/connectors/files/client.js +223 -0
- package/dist/connectors/files/client.js.map +1 -0
- package/dist/connectors/files/defaults.js +131 -0
- package/dist/connectors/files/defaults.js.map +1 -0
- package/dist/connectors/files/index.js +4 -0
- package/dist/connectors/index.js +3 -0
- package/dist/context/execution-context.js +7 -1
- package/dist/context/execution-context.js.map +1 -1
- package/dist/context/index.js +1 -1
- package/dist/core/appkit.d.ts.map +1 -1
- package/dist/core/appkit.js +24 -4
- package/dist/core/appkit.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin/plugin.d.ts +24 -5
- package/dist/plugin/plugin.d.ts.map +1 -1
- package/dist/plugin/plugin.js +43 -10
- package/dist/plugin/plugin.js.map +1 -1
- package/dist/plugin/to-plugin.d.ts +5 -2
- package/dist/plugin/to-plugin.d.ts.map +1 -1
- package/dist/plugin/to-plugin.js +5 -2
- package/dist/plugin/to-plugin.js.map +1 -1
- package/dist/plugins/analytics/analytics.d.ts +1 -2
- package/dist/plugins/analytics/analytics.d.ts.map +1 -1
- package/dist/plugins/analytics/analytics.js +1 -2
- package/dist/plugins/analytics/analytics.js.map +1 -1
- package/dist/plugins/files/defaults.d.ts +1 -0
- package/dist/plugins/files/defaults.js +56 -0
- package/dist/plugins/files/defaults.js.map +1 -0
- package/dist/plugins/files/helpers.js +30 -0
- package/dist/plugins/files/helpers.js.map +1 -0
- package/dist/plugins/files/index.d.ts +3 -0
- package/dist/plugins/files/index.js +5 -0
- package/dist/plugins/files/manifest.js +40 -0
- package/dist/plugins/files/manifest.js.map +1 -0
- package/dist/plugins/files/plugin.d.ts +105 -0
- package/dist/plugins/files/plugin.d.ts.map +1 -0
- package/dist/plugins/files/plugin.js +714 -0
- package/dist/plugins/files/plugin.js.map +1 -0
- package/dist/plugins/files/types.d.ts +105 -0
- package/dist/plugins/files/types.d.ts.map +1 -0
- package/dist/plugins/genie/genie.d.ts +1 -2
- package/dist/plugins/genie/genie.d.ts.map +1 -1
- package/dist/plugins/genie/genie.js +1 -2
- package/dist/plugins/genie/genie.js.map +1 -1
- package/dist/plugins/index.d.ts +3 -0
- package/dist/plugins/index.js +4 -0
- package/dist/plugins/lakebase/lakebase.d.ts +1 -2
- package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
- package/dist/plugins/lakebase/lakebase.js +1 -2
- package/dist/plugins/lakebase/lakebase.js.map +1 -1
- package/dist/plugins/server/index.d.ts +2 -2
- package/dist/plugins/server/index.d.ts.map +1 -1
- package/dist/plugins/server/index.js +9 -4
- package/dist/plugins/server/index.js.map +1 -1
- package/dist/registry/manifest-loader.js +1 -1
- package/dist/registry/manifest-loader.js.map +1 -1
- package/dist/registry/types.d.ts +3 -3
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry/types.js.map +1 -1
- package/dist/shared/src/plugin.d.ts +12 -4
- package/dist/shared/src/plugin.d.ts.map +1 -1
- package/docs/api/appkit/Class.Plugin.md +60 -12
- package/docs/api/appkit/Class.ResourceRegistry.md +3 -3
- package/docs/api/appkit/Function.createApp.md +3 -3
- package/docs/api/appkit/Interface.PluginManifest.md +9 -3
- package/docs/api/appkit/TypeAlias.PluginData.md +45 -0
- package/docs/api/appkit/TypeAlias.ToPlugin.md +1 -1
- package/docs/api/appkit-ui/files/DirectoryList.md +36 -0
- package/docs/api/appkit-ui/files/FileBreadcrumb.md +27 -0
- package/docs/api/appkit-ui/files/FileEntry.md +27 -0
- package/docs/api/appkit-ui/files/FilePreviewPanel.md +32 -0
- package/docs/api/appkit-ui/files/NewFolderInput.md +30 -0
- package/docs/api/appkit.md +1 -0
- package/docs/configuration.md +15 -0
- package/docs/plugins/custom-plugins.md +4 -13
- package/docs/plugins/files.md +350 -0
- package/docs/plugins.md +2 -1
- package/llms.txt +8 -1
- package/package.json +1 -1
- package/dist/plugins/server/remote-tunnel/denied.html/denied.html +0 -68
- package/dist/plugins/server/remote-tunnel/index.html/index.html +0 -165
- package/dist/plugins/server/remote-tunnel/wait.html/wait.html +0 -158
|
@@ -11,9 +11,8 @@ import express from "express";
|
|
|
11
11
|
|
|
12
12
|
//#region src/plugins/analytics/analytics.d.ts
|
|
13
13
|
declare class AnalyticsPlugin extends Plugin {
|
|
14
|
-
name: string;
|
|
15
14
|
/** Plugin manifest declaring metadata and resource requirements */
|
|
16
|
-
static manifest: PluginManifest
|
|
15
|
+
static manifest: PluginManifest<"analytics">;
|
|
17
16
|
protected static description: string;
|
|
18
17
|
protected config: IAnalyticsConfig;
|
|
19
18
|
private SQLClient;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.d.ts","names":[],"sources":["../../../src/plugins/analytics/analytics.ts"],"mappings":";;;;;;;;;;;;cA4Ba,eAAA,SAAwB,MAAA
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","names":[],"sources":["../../../src/plugins/analytics/analytics.ts"],"mappings":";;;;;;;;;;;;cA4Ba,eAAA,SAAwB,MAAA;;SAE5B,QAAA,EAAuB,cAAA;EAAA,iBAEb,WAAA;EAAA,UACC,MAAA,EAAQ,gBAAA;EAAA,QAGlB,SAAA;EAAA,QACA,cAAA;cAEI,MAAA,EAAQ,gBAAA;EAWpB,YAAA,CAAa,MAAA,EAAQ,UAAA;EApBS;;;;EA6CxB,iBAAA,CACJ,GAAA,EAAK,OAAA,CAAQ,OAAA,EACb,GAAA,EAAK,OAAA,CAAQ,QAAA,GACZ,OAAA;EADI;;;;EAuCD,iBAAA,CACJ,GAAA,EAAK,OAAA,CAAQ,OAAA,EACb,GAAA,EAAK,OAAA,CAAQ,QAAA,GACZ,OAAA;EA+G2B;;;;;;;;;;;;;;;EAFxB,KAAA,CACJ,KAAA,UACA,UAAA,GAAa,MAAA,SAAe,aAAA,sBAC5B,gBAAA,GAAmB,MAAA,eACnB,MAAA,GAAS,WAAA,GACR,OAAA;EA7MsC;;;EAAA,UAqOzB,YAAA,CACd,eAAA,EAAiB,eAAA,EACjB,KAAA,UACA,MAAA,GAAS,WAAA,GACR,OAAA,CAAQ,UAAA,aAAuB,SAAA,CAAU,YAAA;EAItC,QAAA,CAAA,GAAY,OAAA;EAzOD;;;;EAiPjB,OAAA,CAAA;;;;2BA5Ce,UAAA,GACA,MAAA,SAAe,aAAA,sBAAiC,gBAAA,GAC1C,MAAA,eAAmB,MAAA,GAC7B,WAAA,KACR,OAAA;EAAA;AAAA;;;;cAqDQ,SAAA,EAAS,QAAA,QAAA,eAAA,EAAA,gBAAA"}
|
|
@@ -14,7 +14,6 @@ import { QueryProcessor } from "./query.js";
|
|
|
14
14
|
init_context();
|
|
15
15
|
const logger = createLogger("analytics");
|
|
16
16
|
var AnalyticsPlugin = class extends Plugin {
|
|
17
|
-
name = "analytics";
|
|
18
17
|
/** Plugin manifest declaring metadata and resource requirements */
|
|
19
18
|
static manifest = manifest_default;
|
|
20
19
|
static description = "Analytics plugin for data analysis";
|
|
@@ -178,7 +177,7 @@ var AnalyticsPlugin = class extends Plugin {
|
|
|
178
177
|
/**
|
|
179
178
|
* @internal
|
|
180
179
|
*/
|
|
181
|
-
const analytics = toPlugin(AnalyticsPlugin
|
|
180
|
+
const analytics = toPlugin(AnalyticsPlugin);
|
|
182
181
|
|
|
183
182
|
//#endregion
|
|
184
183
|
export { AnalyticsPlugin, analytics };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.js","names":["manifest"],"sources":["../../../src/plugins/analytics/analytics.ts"],"sourcesContent":["import type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport type express from \"express\";\nimport type {\n IAppRouter,\n PluginExecuteConfig,\n SQLTypeMarker,\n StreamExecutionSettings,\n} from \"shared\";\nimport { SQLWarehouseConnector } from \"../../connectors\";\nimport {\n getCurrentUserId,\n getWarehouseId,\n getWorkspaceClient,\n} from \"../../context\";\nimport { createLogger } from \"../../logging/logger\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport type { PluginManifest } from \"../../registry\";\nimport { queryDefaults } from \"./defaults\";\nimport manifest from \"./manifest.json\";\nimport { QueryProcessor } from \"./query\";\nimport type {\n AnalyticsQueryResponse,\n IAnalyticsConfig,\n IAnalyticsQueryRequest,\n} from \"./types\";\n\nconst logger = createLogger(\"analytics\");\n\nexport class AnalyticsPlugin extends Plugin {\n name = \"analytics\";\n\n /** Plugin manifest declaring metadata and resource requirements */\n static manifest = manifest as PluginManifest;\n\n protected static description = \"Analytics plugin for data analysis\";\n protected declare config: IAnalyticsConfig;\n\n // analytics services\n private SQLClient: SQLWarehouseConnector;\n private queryProcessor: QueryProcessor;\n\n constructor(config: IAnalyticsConfig) {\n super(config);\n this.config = config;\n this.queryProcessor = new QueryProcessor();\n\n this.SQLClient = new SQLWarehouseConnector({\n timeout: config.timeout,\n telemetry: config.telemetry,\n });\n }\n\n injectRoutes(router: IAppRouter) {\n // Service principal endpoints\n this.route(router, {\n name: \"arrow\",\n method: \"get\",\n path: \"/arrow-result/:jobId\",\n handler: async (req: express.Request, res: express.Response) => {\n await this._handleArrowRoute(req, res);\n },\n });\n\n this.route<AnalyticsQueryResponse>(router, {\n name: \"query\",\n method: \"post\",\n path: \"/query/:query_key\",\n handler: async (req: express.Request, res: express.Response) => {\n await this._handleQueryRoute(req, res);\n },\n });\n }\n\n /**\n * Handle Arrow data download requests.\n * When called via asUser(req), uses the user's Databricks credentials.\n */\n async _handleArrowRoute(\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n try {\n const { jobId } = req.params;\n const workspaceClient = getWorkspaceClient();\n\n logger.debug(\"Processing Arrow job request for jobId=%s\", jobId);\n\n const event = logger.event(req);\n event?.setComponent(\"analytics\", \"getArrowData\").setContext(\"analytics\", {\n job_id: jobId,\n plugin: this.name,\n });\n\n const result = await this.getArrowData(workspaceClient, jobId);\n\n res.setHeader(\"Content-Type\", \"application/octet-stream\");\n res.setHeader(\"Content-Length\", result.data.length.toString());\n res.setHeader(\"Cache-Control\", \"public, max-age=3600\");\n\n logger.debug(\n \"Sending Arrow buffer: %d bytes for job %s\",\n result.data.length,\n jobId,\n );\n res.send(Buffer.from(result.data));\n } catch (error) {\n logger.error(\"Arrow job error: %O\", error);\n res.status(404).json({\n error: error instanceof Error ? error.message : \"Arrow job not found\",\n plugin: this.name,\n });\n }\n }\n\n /**\n * Handle SQL query execution requests.\n * When called via asUser(req), uses the user's Databricks credentials.\n */\n async _handleQueryRoute(\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n const { query_key } = req.params;\n const { parameters, format = \"JSON\" } = req.body as IAnalyticsQueryRequest;\n\n // Request-scoped logging with WideEvent tracking\n logger.debug(req, \"Executing query: %s (format=%s)\", query_key, format);\n\n const event = logger.event(req);\n event?.setComponent(\"analytics\", \"executeQuery\").setContext(\"analytics\", {\n query_key,\n format,\n parameter_count: parameters ? Object.keys(parameters).length : 0,\n plugin: this.name,\n });\n\n if (!query_key) {\n res.status(400).json({ error: \"query_key is required\" });\n return;\n }\n\n const queryResult = await this.app.getAppQuery(\n query_key,\n req,\n this.devFileReader,\n );\n\n if (!queryResult) {\n res.status(404).json({ error: \"Query not found\" });\n return;\n }\n\n const { query, isAsUser } = queryResult;\n\n // get execution context - user-scoped if .obo.sql, otherwise service principal\n const executor = isAsUser ? this.asUser(req) : this;\n const userKey = getCurrentUserId();\n const executorKey = isAsUser ? userKey : \"global\";\n\n const queryParameters =\n format === \"ARROW\"\n ? {\n formatParameters: {\n disposition: \"EXTERNAL_LINKS\",\n format: \"ARROW_STREAM\",\n },\n type: \"arrow\",\n }\n : {\n type: \"result\",\n };\n\n const hashedQuery = this.queryProcessor.hashQuery(query);\n\n const defaultConfig: PluginExecuteConfig = {\n ...queryDefaults,\n cache: {\n ...queryDefaults.cache,\n cacheKey: [\n \"analytics:query\",\n query_key,\n JSON.stringify(parameters),\n JSON.stringify(format),\n hashedQuery,\n executorKey,\n ],\n },\n };\n\n const streamExecutionSettings: StreamExecutionSettings = {\n default: defaultConfig,\n };\n\n await executor.executeStream(\n res,\n async (signal) => {\n const processedParams = await this.queryProcessor.processQueryParams(\n query,\n parameters,\n );\n\n const result = await executor.query(\n query,\n processedParams,\n queryParameters.formatParameters,\n signal,\n );\n\n return { type: queryParameters.type, ...result };\n },\n streamExecutionSettings,\n executorKey,\n );\n }\n\n /**\n * Execute a SQL query using the current execution context.\n *\n * When called directly: uses service principal credentials.\n * When called via asUser(req).query(...): uses user's credentials.\n *\n * @example\n * ```typescript\n * // Service principal execution\n * const result = await analytics.query(\"SELECT * FROM table\")\n *\n * // User context execution (in route handler)\n * const result = await this.asUser(req).query(\"SELECT * FROM table\")\n * ```\n */\n async query(\n query: string,\n parameters?: Record<string, SQLTypeMarker | null | undefined>,\n formatParameters?: Record<string, any>,\n signal?: AbortSignal,\n ): Promise<any> {\n const workspaceClient = getWorkspaceClient();\n const warehouseId = await getWarehouseId();\n\n const { statement, parameters: sqlParameters } =\n this.queryProcessor.convertToSQLParameters(query, parameters);\n\n const response = await this.SQLClient.executeStatement(\n workspaceClient,\n {\n statement,\n warehouse_id: warehouseId,\n parameters: sqlParameters,\n ...formatParameters,\n },\n signal,\n );\n\n return response.result;\n }\n\n /**\n * Get Arrow-formatted data for a completed query job.\n */\n protected async getArrowData(\n workspaceClient: WorkspaceClient,\n jobId: string,\n signal?: AbortSignal,\n ): Promise<ReturnType<typeof this.SQLClient.getArrowData>> {\n return await this.SQLClient.getArrowData(workspaceClient, jobId, signal);\n }\n\n async shutdown(): Promise<void> {\n this.streamManager.abortAll();\n }\n\n /**\n * Returns the public exports for the analytics plugin.\n * Note: `asUser()` is automatically added by AppKit.\n */\n exports() {\n return {\n /**\n * Execute a SQL query using service principal credentials.\n */\n query: this.query,\n };\n }\n}\n\n/**\n * @internal\n */\nexport const analytics = toPlugin<\n typeof AnalyticsPlugin,\n IAnalyticsConfig,\n \"analytics\"\n>(AnalyticsPlugin, \"analytics\");\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"analytics.js","names":["manifest"],"sources":["../../../src/plugins/analytics/analytics.ts"],"sourcesContent":["import type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport type express from \"express\";\nimport type {\n IAppRouter,\n PluginExecuteConfig,\n SQLTypeMarker,\n StreamExecutionSettings,\n} from \"shared\";\nimport { SQLWarehouseConnector } from \"../../connectors\";\nimport {\n getCurrentUserId,\n getWarehouseId,\n getWorkspaceClient,\n} from \"../../context\";\nimport { createLogger } from \"../../logging/logger\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport type { PluginManifest } from \"../../registry\";\nimport { queryDefaults } from \"./defaults\";\nimport manifest from \"./manifest.json\";\nimport { QueryProcessor } from \"./query\";\nimport type {\n AnalyticsQueryResponse,\n IAnalyticsConfig,\n IAnalyticsQueryRequest,\n} from \"./types\";\n\nconst logger = createLogger(\"analytics\");\n\nexport class AnalyticsPlugin extends Plugin {\n /** Plugin manifest declaring metadata and resource requirements */\n static manifest = manifest as PluginManifest<\"analytics\">;\n\n protected static description = \"Analytics plugin for data analysis\";\n protected declare config: IAnalyticsConfig;\n\n // analytics services\n private SQLClient: SQLWarehouseConnector;\n private queryProcessor: QueryProcessor;\n\n constructor(config: IAnalyticsConfig) {\n super(config);\n this.config = config;\n this.queryProcessor = new QueryProcessor();\n\n this.SQLClient = new SQLWarehouseConnector({\n timeout: config.timeout,\n telemetry: config.telemetry,\n });\n }\n\n injectRoutes(router: IAppRouter) {\n // Service principal endpoints\n this.route(router, {\n name: \"arrow\",\n method: \"get\",\n path: \"/arrow-result/:jobId\",\n handler: async (req: express.Request, res: express.Response) => {\n await this._handleArrowRoute(req, res);\n },\n });\n\n this.route<AnalyticsQueryResponse>(router, {\n name: \"query\",\n method: \"post\",\n path: \"/query/:query_key\",\n handler: async (req: express.Request, res: express.Response) => {\n await this._handleQueryRoute(req, res);\n },\n });\n }\n\n /**\n * Handle Arrow data download requests.\n * When called via asUser(req), uses the user's Databricks credentials.\n */\n async _handleArrowRoute(\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n try {\n const { jobId } = req.params;\n const workspaceClient = getWorkspaceClient();\n\n logger.debug(\"Processing Arrow job request for jobId=%s\", jobId);\n\n const event = logger.event(req);\n event?.setComponent(\"analytics\", \"getArrowData\").setContext(\"analytics\", {\n job_id: jobId,\n plugin: this.name,\n });\n\n const result = await this.getArrowData(workspaceClient, jobId);\n\n res.setHeader(\"Content-Type\", \"application/octet-stream\");\n res.setHeader(\"Content-Length\", result.data.length.toString());\n res.setHeader(\"Cache-Control\", \"public, max-age=3600\");\n\n logger.debug(\n \"Sending Arrow buffer: %d bytes for job %s\",\n result.data.length,\n jobId,\n );\n res.send(Buffer.from(result.data));\n } catch (error) {\n logger.error(\"Arrow job error: %O\", error);\n res.status(404).json({\n error: error instanceof Error ? error.message : \"Arrow job not found\",\n plugin: this.name,\n });\n }\n }\n\n /**\n * Handle SQL query execution requests.\n * When called via asUser(req), uses the user's Databricks credentials.\n */\n async _handleQueryRoute(\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n const { query_key } = req.params;\n const { parameters, format = \"JSON\" } = req.body as IAnalyticsQueryRequest;\n\n // Request-scoped logging with WideEvent tracking\n logger.debug(req, \"Executing query: %s (format=%s)\", query_key, format);\n\n const event = logger.event(req);\n event?.setComponent(\"analytics\", \"executeQuery\").setContext(\"analytics\", {\n query_key,\n format,\n parameter_count: parameters ? Object.keys(parameters).length : 0,\n plugin: this.name,\n });\n\n if (!query_key) {\n res.status(400).json({ error: \"query_key is required\" });\n return;\n }\n\n const queryResult = await this.app.getAppQuery(\n query_key,\n req,\n this.devFileReader,\n );\n\n if (!queryResult) {\n res.status(404).json({ error: \"Query not found\" });\n return;\n }\n\n const { query, isAsUser } = queryResult;\n\n // get execution context - user-scoped if .obo.sql, otherwise service principal\n const executor = isAsUser ? this.asUser(req) : this;\n const userKey = getCurrentUserId();\n const executorKey = isAsUser ? userKey : \"global\";\n\n const queryParameters =\n format === \"ARROW\"\n ? {\n formatParameters: {\n disposition: \"EXTERNAL_LINKS\",\n format: \"ARROW_STREAM\",\n },\n type: \"arrow\",\n }\n : {\n type: \"result\",\n };\n\n const hashedQuery = this.queryProcessor.hashQuery(query);\n\n const defaultConfig: PluginExecuteConfig = {\n ...queryDefaults,\n cache: {\n ...queryDefaults.cache,\n cacheKey: [\n \"analytics:query\",\n query_key,\n JSON.stringify(parameters),\n JSON.stringify(format),\n hashedQuery,\n executorKey,\n ],\n },\n };\n\n const streamExecutionSettings: StreamExecutionSettings = {\n default: defaultConfig,\n };\n\n await executor.executeStream(\n res,\n async (signal) => {\n const processedParams = await this.queryProcessor.processQueryParams(\n query,\n parameters,\n );\n\n const result = await executor.query(\n query,\n processedParams,\n queryParameters.formatParameters,\n signal,\n );\n\n return { type: queryParameters.type, ...result };\n },\n streamExecutionSettings,\n executorKey,\n );\n }\n\n /**\n * Execute a SQL query using the current execution context.\n *\n * When called directly: uses service principal credentials.\n * When called via asUser(req).query(...): uses user's credentials.\n *\n * @example\n * ```typescript\n * // Service principal execution\n * const result = await analytics.query(\"SELECT * FROM table\")\n *\n * // User context execution (in route handler)\n * const result = await this.asUser(req).query(\"SELECT * FROM table\")\n * ```\n */\n async query(\n query: string,\n parameters?: Record<string, SQLTypeMarker | null | undefined>,\n formatParameters?: Record<string, any>,\n signal?: AbortSignal,\n ): Promise<any> {\n const workspaceClient = getWorkspaceClient();\n const warehouseId = await getWarehouseId();\n\n const { statement, parameters: sqlParameters } =\n this.queryProcessor.convertToSQLParameters(query, parameters);\n\n const response = await this.SQLClient.executeStatement(\n workspaceClient,\n {\n statement,\n warehouse_id: warehouseId,\n parameters: sqlParameters,\n ...formatParameters,\n },\n signal,\n );\n\n return response.result;\n }\n\n /**\n * Get Arrow-formatted data for a completed query job.\n */\n protected async getArrowData(\n workspaceClient: WorkspaceClient,\n jobId: string,\n signal?: AbortSignal,\n ): Promise<ReturnType<typeof this.SQLClient.getArrowData>> {\n return await this.SQLClient.getArrowData(workspaceClient, jobId, signal);\n }\n\n async shutdown(): Promise<void> {\n this.streamManager.abortAll();\n }\n\n /**\n * Returns the public exports for the analytics plugin.\n * Note: `asUser()` is automatically added by AppKit.\n */\n exports() {\n return {\n /**\n * Execute a SQL query using service principal credentials.\n */\n query: this.query,\n };\n }\n}\n\n/**\n * @internal\n */\nexport const analytics = toPlugin(AnalyticsPlugin);\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "../../shared/src/index.js";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { EXTENSION_CONTENT_TYPES, FILES_MAX_READ_SIZE } from "../../connectors/files/defaults.js";
|
|
2
|
+
|
|
3
|
+
//#region src/plugins/files/defaults.ts
|
|
4
|
+
/**
|
|
5
|
+
* Execution defaults for read-tier operations (list, read, exists, metadata, preview).
|
|
6
|
+
* Cache 60s (ttl in seconds)
|
|
7
|
+
* Retry 3x with 1s backoff
|
|
8
|
+
* Timeout 30s
|
|
9
|
+
**/
|
|
10
|
+
const FILES_READ_DEFAULTS = {
|
|
11
|
+
cache: {
|
|
12
|
+
enabled: true,
|
|
13
|
+
ttl: 60
|
|
14
|
+
},
|
|
15
|
+
retry: {
|
|
16
|
+
enabled: true,
|
|
17
|
+
initialDelay: 1e3,
|
|
18
|
+
attempts: 3
|
|
19
|
+
},
|
|
20
|
+
timeout: 3e4
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Execution defaults for download-tier operations (download, raw).
|
|
24
|
+
* No cache
|
|
25
|
+
* Retry 3x with 1s backoff
|
|
26
|
+
* Timeout 30s (stream start only)
|
|
27
|
+
**/
|
|
28
|
+
const FILES_DOWNLOAD_DEFAULTS = {
|
|
29
|
+
cache: { enabled: false },
|
|
30
|
+
retry: {
|
|
31
|
+
enabled: true,
|
|
32
|
+
initialDelay: 1e3,
|
|
33
|
+
attempts: 3
|
|
34
|
+
},
|
|
35
|
+
timeout: 3e4
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Execution defaults for write-tier operations (upload, mkdir, delete).
|
|
39
|
+
* No cache
|
|
40
|
+
* No retry
|
|
41
|
+
* Timeout 600s.
|
|
42
|
+
**/
|
|
43
|
+
const FILES_WRITE_DEFAULTS = {
|
|
44
|
+
cache: { enabled: false },
|
|
45
|
+
retry: { enabled: false },
|
|
46
|
+
timeout: 6e5
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Default maximum upload size in bytes (5 GB).
|
|
50
|
+
* This matches the Databricks Files API v2 per-file limit.
|
|
51
|
+
*/
|
|
52
|
+
const FILES_MAX_UPLOAD_SIZE = 5 * 1024 * 1024 * 1024;
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
export { FILES_DOWNLOAD_DEFAULTS, FILES_MAX_UPLOAD_SIZE, FILES_READ_DEFAULTS, FILES_WRITE_DEFAULTS };
|
|
56
|
+
//# sourceMappingURL=defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.js","names":[],"sources":["../../../src/plugins/files/defaults.ts"],"sourcesContent":["import type { PluginExecuteConfig } from \"shared\";\n\n/**\n * Execution defaults for read-tier operations (list, read, exists, metadata, preview).\n * Cache 60s (ttl in seconds)\n * Retry 3x with 1s backoff\n * Timeout 30s\n **/\nexport const FILES_READ_DEFAULTS: PluginExecuteConfig = {\n cache: {\n enabled: true,\n ttl: 60,\n },\n retry: {\n enabled: true,\n initialDelay: 1000,\n attempts: 3,\n },\n timeout: 30_000,\n};\n\n/**\n * Execution defaults for download-tier operations (download, raw).\n * No cache\n * Retry 3x with 1s backoff\n * Timeout 30s (stream start only)\n **/\nexport const FILES_DOWNLOAD_DEFAULTS: PluginExecuteConfig = {\n cache: {\n enabled: false,\n },\n retry: {\n enabled: true,\n initialDelay: 1000,\n attempts: 3,\n },\n /**\n * @info this timeout is for the stream to start, not for the full download.\n */\n timeout: 30_000,\n};\n\n/**\n * Execution defaults for write-tier operations (upload, mkdir, delete).\n * No cache\n * No retry\n * Timeout 600s.\n **/\nexport const FILES_WRITE_DEFAULTS: PluginExecuteConfig = {\n cache: {\n enabled: false,\n },\n retry: {\n enabled: false,\n },\n timeout: 600_000,\n};\n\n/**\n * Default maximum upload size in bytes (5 GB).\n * This matches the Databricks Files API v2 per-file limit.\n */\nexport const FILES_MAX_UPLOAD_SIZE = 5 * 1024 * 1024 * 1024; // 5 GB\n\nexport {\n EXTENSION_CONTENT_TYPES,\n FILES_MAX_READ_SIZE,\n} from \"../../connectors/files/defaults\";\n"],"mappings":";;;;;;;;;AAQA,MAAa,sBAA2C;CACtD,OAAO;EACL,SAAS;EACT,KAAK;EACN;CACD,OAAO;EACL,SAAS;EACT,cAAc;EACd,UAAU;EACX;CACD,SAAS;CACV;;;;;;;AAQD,MAAa,0BAA+C;CAC1D,OAAO,EACL,SAAS,OACV;CACD,OAAO;EACL,SAAS;EACT,cAAc;EACd,UAAU;EACX;CAID,SAAS;CACV;;;;;;;AAQD,MAAa,uBAA4C;CACvD,OAAO,EACL,SAAS,OACV;CACD,OAAO,EACL,SAAS,OACV;CACD,SAAS;CACV;;;;;AAMD,MAAa,wBAAwB,IAAI,OAAO,OAAO"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { contentTypeFromPath, isTextContentType } from "../../connectors/files/defaults.js";
|
|
2
|
+
|
|
3
|
+
//#region src/plugins/files/helpers.ts
|
|
4
|
+
/**
|
|
5
|
+
* Extract the parent directory from a file or directory path.
|
|
6
|
+
*
|
|
7
|
+
* Handles edge cases such as root-level files (`"/file.txt"` → `"/"`),
|
|
8
|
+
* paths without slashes (`"file.txt"` → `""`), and trailing slashes.
|
|
9
|
+
*/
|
|
10
|
+
function parentDirectory(path) {
|
|
11
|
+
const normalized = path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
|
|
12
|
+
const lastSlash = normalized.lastIndexOf("/");
|
|
13
|
+
if (lastSlash > 0) return normalized.substring(0, lastSlash);
|
|
14
|
+
if (normalized.startsWith("/")) return "/";
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Sanitize a filename for use in a `Content-Disposition` HTTP header.
|
|
19
|
+
*
|
|
20
|
+
* Redundancy check – Unity Catalog is unlikely to allow filenames with
|
|
21
|
+
* quotes or control characters, but we sanitize defensively to prevent
|
|
22
|
+
* HTTP header injection if upstream constraints ever change.
|
|
23
|
+
*/
|
|
24
|
+
function sanitizeFilename(raw) {
|
|
25
|
+
return raw.replace(/["\\]/g, "\\$&").replace(/[\x00-\x1f]/g, "");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { parentDirectory, sanitizeFilename };
|
|
30
|
+
//# sourceMappingURL=helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.js","names":[],"sources":["../../../src/plugins/files/helpers.ts"],"sourcesContent":["export {\n contentTypeFromPath,\n isTextContentType,\n} from \"../../connectors/files/defaults\";\n\n/**\n * Extract the parent directory from a file or directory path.\n *\n * Handles edge cases such as root-level files (`\"/file.txt\"` → `\"/\"`),\n * paths without slashes (`\"file.txt\"` → `\"\"`), and trailing slashes.\n */\nexport function parentDirectory(path: string): string {\n const normalized =\n path.length > 1 && path.endsWith(\"/\") ? path.slice(0, -1) : path;\n const lastSlash = normalized.lastIndexOf(\"/\");\n\n if (lastSlash > 0) return normalized.substring(0, lastSlash);\n if (normalized.startsWith(\"/\")) return \"/\";\n return \"\";\n}\n\n/**\n * Sanitize a filename for use in a `Content-Disposition` HTTP header.\n *\n * Redundancy check – Unity Catalog is unlikely to allow filenames with\n * quotes or control characters, but we sanitize defensively to prevent\n * HTTP header injection if upstream constraints ever change.\n */\nexport function sanitizeFilename(raw: string): string {\n // biome-ignore lint/suspicious/noControlCharactersInRegex: intentionally stripping control chars for security\n return raw.replace(/[\"\\\\]/g, \"\\\\$&\").replace(/[\\x00-\\x1f]/g, \"\");\n}\n"],"mappings":";;;;;;;;;AAWA,SAAgB,gBAAgB,MAAsB;CACpD,MAAM,aACJ,KAAK,SAAS,KAAK,KAAK,SAAS,IAAI,GAAG,KAAK,MAAM,GAAG,GAAG,GAAG;CAC9D,MAAM,YAAY,WAAW,YAAY,IAAI;AAE7C,KAAI,YAAY,EAAG,QAAO,WAAW,UAAU,GAAG,UAAU;AAC5D,KAAI,WAAW,WAAW,IAAI,CAAE,QAAO;AACvC,QAAO;;;;;;;;;AAUT,SAAgB,iBAAiB,KAAqB;AAEpD,QAAO,IAAI,QAAQ,UAAU,OAAO,CAAC,QAAQ,gBAAgB,GAAG"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { EXTENSION_CONTENT_TYPES, FILES_MAX_READ_SIZE } from "../../connectors/files/defaults.js";
|
|
2
|
+
import { FILES_DOWNLOAD_DEFAULTS, FILES_MAX_UPLOAD_SIZE, FILES_READ_DEFAULTS, FILES_WRITE_DEFAULTS } from "./defaults.js";
|
|
3
|
+
import { FilesPlugin, files } from "./plugin.js";
|
|
4
|
+
|
|
5
|
+
export { };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/plugins/files/manifest.json
|
|
2
|
+
var manifest_default = {
|
|
3
|
+
$schema: "https://databricks.github.io/appkit/schemas/plugin-manifest.schema.json",
|
|
4
|
+
name: "files",
|
|
5
|
+
displayName: "Files Plugin",
|
|
6
|
+
description: "File operations against Databricks Volumes and Unity Catalog",
|
|
7
|
+
resources: {
|
|
8
|
+
"required": [{
|
|
9
|
+
"type": "volume",
|
|
10
|
+
"alias": "Files",
|
|
11
|
+
"resourceKey": "files",
|
|
12
|
+
"description": "Permission to write to volumes",
|
|
13
|
+
"permission": "WRITE_VOLUME",
|
|
14
|
+
"fields": { "path": {
|
|
15
|
+
"env": "DATABRICKS_VOLUME_FILES",
|
|
16
|
+
"description": "Volume path for file storage (e.g. /Volumes/catalog/schema/volume_name)"
|
|
17
|
+
} }
|
|
18
|
+
}],
|
|
19
|
+
"optional": []
|
|
20
|
+
},
|
|
21
|
+
config: { "schema": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"properties": {
|
|
24
|
+
"timeout": {
|
|
25
|
+
"type": "number",
|
|
26
|
+
"default": 3e4,
|
|
27
|
+
"description": "File operation timeout in milliseconds"
|
|
28
|
+
},
|
|
29
|
+
"maxUploadSize": {
|
|
30
|
+
"type": "number",
|
|
31
|
+
"default": 5368709120,
|
|
32
|
+
"description": "Maximum upload size in bytes. Defaults to 5 GB (Databricks Files API v2 limit)."
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} }
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { manifest_default as default };
|
|
40
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","names":[],"sources":["../../../src/plugins/files/manifest.json"],"sourcesContent":[""],"mappings":""}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { IAppRouter, ToPlugin } from "../../shared/src/plugin.js";
|
|
2
|
+
import "../../shared/src/index.js";
|
|
3
|
+
import { Plugin } from "../../plugin/plugin.js";
|
|
4
|
+
import "../../plugin/index.js";
|
|
5
|
+
import { PluginManifest, ResourceRequirement } from "../../registry/types.js";
|
|
6
|
+
import "../../registry/index.js";
|
|
7
|
+
import { FilesExport, IFilesConfig, VolumeAPI, VolumeConfig } from "./types.js";
|
|
8
|
+
|
|
9
|
+
//#region src/plugins/files/plugin.d.ts
|
|
10
|
+
declare class FilesPlugin extends Plugin {
|
|
11
|
+
name: string;
|
|
12
|
+
/** Plugin manifest declaring metadata and resource requirements. */
|
|
13
|
+
static manifest: PluginManifest;
|
|
14
|
+
protected static description: string;
|
|
15
|
+
protected config: IFilesConfig;
|
|
16
|
+
private volumeConnectors;
|
|
17
|
+
private volumeConfigs;
|
|
18
|
+
private volumeKeys;
|
|
19
|
+
/**
|
|
20
|
+
* Scans `process.env` for `DATABRICKS_VOLUME_*` keys and merges them with
|
|
21
|
+
* any explicitly configured volumes. Explicit config wins for per-volume
|
|
22
|
+
* overrides; auto-discovered volumes get default `{}` config.
|
|
23
|
+
*/
|
|
24
|
+
static discoverVolumes(config: IFilesConfig): Record<string, VolumeConfig>;
|
|
25
|
+
/**
|
|
26
|
+
* Generates resource requirements dynamically from discovered + configured volumes.
|
|
27
|
+
* Each volume key maps to a `DATABRICKS_VOLUME_{KEY_UPPERCASE}` env var.
|
|
28
|
+
*/
|
|
29
|
+
static getResourceRequirements(config: IFilesConfig): ResourceRequirement[];
|
|
30
|
+
/**
|
|
31
|
+
* Warns when a method is called without a user context (i.e. as service principal).
|
|
32
|
+
* OBO access via `asUser(req)` is strongly recommended.
|
|
33
|
+
*/
|
|
34
|
+
private warnIfNoUserContext;
|
|
35
|
+
/**
|
|
36
|
+
* Throws when a method is called without a user context (i.e. as service principal).
|
|
37
|
+
* OBO access via `asUser(req)` is enforced for now.
|
|
38
|
+
*/
|
|
39
|
+
private throwIfNoUserContext;
|
|
40
|
+
constructor(config: IFilesConfig);
|
|
41
|
+
/**
|
|
42
|
+
* Creates a VolumeAPI for a specific volume key.
|
|
43
|
+
* Each method warns if called outside a user context (service principal).
|
|
44
|
+
*/
|
|
45
|
+
protected createVolumeAPI(volumeKey: string): VolumeAPI;
|
|
46
|
+
injectRoutes(router: IAppRouter): void;
|
|
47
|
+
/**
|
|
48
|
+
* Resolve `:volumeKey` from the request. Returns the connector and key,
|
|
49
|
+
* or sends a 404 and returns `{ connector: undefined }`.
|
|
50
|
+
*/
|
|
51
|
+
private _resolveVolume;
|
|
52
|
+
/**
|
|
53
|
+
* Validate a file/directory path from user input.
|
|
54
|
+
* Returns `true` if valid, or an error message string if invalid.
|
|
55
|
+
*/
|
|
56
|
+
private _isValidPath;
|
|
57
|
+
private _readSettings;
|
|
58
|
+
/**
|
|
59
|
+
* Invalidate cached list entries for a directory after a write operation.
|
|
60
|
+
* Uses the same cache-key format as `_handleList`: resolved path for
|
|
61
|
+
* subdirectories, `"__root__"` for the volume root.
|
|
62
|
+
*/
|
|
63
|
+
private _invalidateListCache;
|
|
64
|
+
private _handleApiError;
|
|
65
|
+
private _handleList;
|
|
66
|
+
private _handleRead;
|
|
67
|
+
private _handleDownload;
|
|
68
|
+
private _handleRaw;
|
|
69
|
+
/**
|
|
70
|
+
* Shared handler for `/download` and `/raw` endpoints.
|
|
71
|
+
* - `download`: always forces `Content-Disposition: attachment`.
|
|
72
|
+
* - `raw`: adds CSP sandbox; forces attachment only for unsafe content types.
|
|
73
|
+
*/
|
|
74
|
+
private _serveFile;
|
|
75
|
+
private _handleExists;
|
|
76
|
+
private _handleMetadata;
|
|
77
|
+
private _handlePreview;
|
|
78
|
+
private _handleUpload;
|
|
79
|
+
private _handleMkdir;
|
|
80
|
+
private _handleDelete;
|
|
81
|
+
private inflightWrites;
|
|
82
|
+
private trackWrite;
|
|
83
|
+
shutdown(): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Returns the programmatic API for the Files plugin.
|
|
86
|
+
* Callable with a volume key to get a volume-scoped handle.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* // OBO access (recommended)
|
|
91
|
+
* appKit.files("uploads").asUser(req).list()
|
|
92
|
+
*
|
|
93
|
+
* // Service principal access (logs a warning)
|
|
94
|
+
* appKit.files("uploads").list()
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
exports(): FilesExport;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* @internal
|
|
101
|
+
*/
|
|
102
|
+
declare const files: ToPlugin<typeof FilesPlugin, IFilesConfig, string>;
|
|
103
|
+
//#endregion
|
|
104
|
+
export { FilesPlugin, files };
|
|
105
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","names":[],"sources":["../../../src/plugins/files/plugin.ts"],"mappings":";;;;;;;;;cAmCa,WAAA,SAAoB,MAAA;EAC/B,IAAA;;SAGO,QAAA,EAAuB,cAAA;EAAA,iBACb,WAAA;EAAA,UACC,MAAA,EAAQ,YAAA;EAAA,QAElB,gBAAA;EAAA,QACA,aAAA;EAAA,QACA,UAAA;EAJkB;;;;;EAAA,OAWnB,eAAA,CAAgB,MAAA,EAAQ,YAAA,GAAe,MAAA,SAAe,YAAA;EAkEzC;;;;EAAA,OA3Cb,uBAAA,CAAwB,MAAA,EAAQ,YAAA,GAAe,mBAAA;EAxCvB;;;;EAAA,QA8DvB,mBAAA;EA1DD;;;;EAAA,QAuEC,oBAAA;cAQI,MAAA,EAAQ,YAAA;EA1EZ;;;;EAAA,UA+GE,eAAA,CAAgB,SAAA,WAAoB,SAAA;EAmD9C,YAAA,CAAa,MAAA,EAAQ,UAAA;EA1JwC;;;;EAAA,QAwRrD,cAAA;EA3OA;;;;EAAA,QAkQA,YAAA;EAAA,QAQA,aAAA;EAhNkB;;;;;EAAA,QAgOlB,oBAAA;EAAA,QAiBA,eAAA;EAAA,QA8BM,WAAA;EAAA,QA+BA,WAAA;EAAA,QAoCA,eAAA;EAAA,QAWA,UAAA;EA/CA;;;;;EAAA,QA+DA,UAAA;EAAA,QAgFA,aAAA;EAAA,QAsCA,eAAA;EAAA,QAsCA,cAAA;EAAA,QAoCA,aAAA;EAAA,QA2GA,YAAA;EAAA,QA+CA,aAAA;EAAA,QA6CN,cAAA;EAAA,QAEA,UAAA;EAOF,QAAA,CAAA,GAAY,OAAA;EAgCP;;;AA+Bb;;;;;;;;;;EA/BE,OAAA,CAAA,GAAW,WAAA;AAAA;;;;cA+BA,KAAA,EAAK,QAAA,QAAA,WAAA,EAAA,YAAA"}
|