@databricks/appkit 0.5.0 → 0.5.2
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/dist/app/index.d.ts +11 -2
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js +51 -37
- package/dist/app/index.js.map +1 -1
- package/dist/appkit/package.js +1 -1
- package/dist/cli/commands/docs.js +1 -1
- package/dist/cli/commands/docs.js.map +1 -1
- package/dist/cli/commands/generate-types.js +5 -0
- package/dist/cli/commands/generate-types.js.map +1 -1
- package/dist/cli/commands/lint.js.map +1 -1
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/index.js +3 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/context/execution-context.js.map +1 -1
- package/dist/context/index.js +1 -1
- package/dist/context/index.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/plugin/dev-reader.d.ts +1 -0
- package/dist/plugin/dev-reader.d.ts.map +1 -1
- package/dist/plugin/dev-reader.js +39 -1
- package/dist/plugin/dev-reader.js.map +1 -1
- package/dist/plugin/plugin.d.ts +2 -2
- package/dist/plugin/plugin.js +1 -1
- package/dist/{analytics → plugins/analytics}/analytics.d.ts +4 -4
- package/dist/plugins/analytics/analytics.d.ts.map +1 -0
- package/dist/{analytics → plugins/analytics}/analytics.js +9 -9
- package/dist/plugins/analytics/analytics.js.map +1 -0
- package/dist/{analytics → plugins/analytics}/defaults.js +1 -1
- package/dist/plugins/analytics/defaults.js.map +1 -0
- package/dist/{analytics → plugins/analytics}/query.js +6 -6
- package/dist/plugins/analytics/query.js.map +1 -0
- package/dist/plugins/analytics/types.d.ts +9 -0
- package/dist/plugins/analytics/types.d.ts.map +1 -0
- package/dist/plugins/index.js +5 -0
- package/dist/{server → plugins/server}/base-server.js +1 -1
- package/dist/plugins/server/base-server.js.map +1 -0
- package/dist/{server → plugins/server}/index.d.ts +3 -3
- package/dist/plugins/server/index.d.ts.map +1 -0
- package/dist/{server → plugins/server}/index.js +10 -10
- package/dist/plugins/server/index.js.map +1 -0
- package/dist/{server → plugins/server}/remote-tunnel/gate.js +1 -1
- package/dist/plugins/server/remote-tunnel/gate.js.map +1 -0
- package/dist/{server → plugins/server}/remote-tunnel/remote-tunnel-controller.js +2 -2
- package/dist/plugins/server/remote-tunnel/remote-tunnel-controller.js.map +1 -0
- package/dist/{server → plugins/server}/remote-tunnel/remote-tunnel-manager.js +24 -3
- package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js.map +1 -0
- package/dist/{server → plugins/server}/static-server.js +1 -1
- package/dist/plugins/server/static-server.js.map +1 -0
- package/dist/{server → plugins/server}/types.d.ts +3 -3
- package/dist/plugins/server/types.d.ts.map +1 -0
- package/dist/{server → plugins/server}/utils.js +1 -1
- package/dist/plugins/server/utils.js.map +1 -0
- package/dist/{server → plugins/server}/vite-dev-server.js +6 -6
- package/dist/plugins/server/vite-dev-server.js.map +1 -0
- package/dist/telemetry/telemetry-provider.js.map +1 -1
- package/docs/docs/api/appkit/Class.AppKitError/index.html +3 -3
- package/docs/docs/api/appkit/Class.AuthenticationError/index.html +3 -3
- package/docs/docs/api/appkit/Class.ConfigurationError/index.html +3 -3
- package/docs/docs/api/appkit/Class.ConnectionError/index.html +3 -3
- package/docs/docs/api/appkit/Class.ExecutionError/index.html +3 -3
- package/docs/docs/api/appkit/Class.InitializationError/index.html +3 -3
- package/docs/docs/api/appkit/Class.Plugin/index.html +3 -3
- package/docs/docs/api/appkit/Class.ServerError/index.html +3 -3
- package/docs/docs/api/appkit/Class.TunnelError/index.html +3 -3
- package/docs/docs/api/appkit/Class.ValidationError/index.html +3 -3
- package/docs/docs/api/appkit/Function.appKitTypesPlugin/index.html +3 -3
- package/docs/docs/api/appkit/Function.createApp/index.html +3 -3
- package/docs/docs/api/appkit/Function.getExecutionContext/index.html +3 -3
- package/docs/docs/api/appkit/Function.isSQLTypeMarker/index.html +3 -3
- package/docs/docs/api/appkit/Interface.BasePluginConfig/index.html +3 -3
- package/docs/docs/api/appkit/Interface.CacheConfig/index.html +3 -3
- package/docs/docs/api/appkit/Interface.ITelemetry/index.html +3 -3
- package/docs/docs/api/appkit/Interface.StreamExecutionSettings/index.html +3 -3
- package/docs/docs/api/appkit/Interface.TelemetryConfig/index.html +3 -3
- package/docs/docs/api/appkit/TypeAlias.IAppRouter/index.html +3 -3
- package/docs/docs/api/appkit/Variable.sql/index.html +3 -3
- package/docs/docs/api/appkit/index.html +3 -3
- package/docs/docs/api/appkit-ui/data/AreaChart/index.html +3 -3
- package/docs/docs/api/appkit-ui/data/BarChart/index.html +3 -3
- package/docs/docs/api/appkit-ui/data/DataTable/index.html +3 -3
- package/docs/docs/api/appkit-ui/data/DonutChart/index.html +3 -3
- package/docs/docs/api/appkit-ui/data/HeatmapChart/index.html +3 -3
- package/docs/docs/api/appkit-ui/data/LineChart/index.html +3 -3
- package/docs/docs/api/appkit-ui/data/PieChart/index.html +3 -3
- package/docs/docs/api/appkit-ui/data/RadarChart/index.html +3 -3
- package/docs/docs/api/appkit-ui/data/ScatterChart/index.html +3 -3
- package/docs/docs/api/appkit-ui/index.html +3 -3
- package/docs/docs/api/appkit-ui/styling/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Accordion/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Alert/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/AlertDialog/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/AspectRatio/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Avatar/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Badge/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Breadcrumb/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Button/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/ButtonGroup/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Calendar/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Card/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Carousel/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/ChartContainer/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Checkbox/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Collapsible/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Command/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/ContextMenu/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Dialog/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Drawer/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/DropdownMenu/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Empty/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Field/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/FormControl/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/HoverCard/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Input/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/InputGroup/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/InputOTP/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Item/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Kbd/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Label/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Menubar/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/NavigationMenu/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Pagination/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Popover/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Progress/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/RadioGroup/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/ResizableHandle/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/ScrollArea/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Select/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Separator/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Sheet/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Sidebar/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Skeleton/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Slider/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Spinner/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Switch/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Table/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Tabs/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Textarea/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Toaster/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Toggle/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/ToggleGroup/index.html +3 -3
- package/docs/docs/api/appkit-ui/ui/Tooltip/index.html +3 -3
- package/docs/docs/api/index.html +3 -3
- package/docs/docs/app-management/index.html +5 -5
- package/docs/docs/app-management.md +2 -2
- package/docs/docs/architecture/index.html +3 -3
- package/docs/docs/category/development/index.html +3 -3
- package/docs/docs/configuration/index.html +3 -3
- package/docs/docs/core-principles/index.html +3 -3
- package/docs/docs/development/index.html +5 -5
- package/docs/docs/development/llm-guide/index.html +3 -3
- package/docs/docs/development/local-development/index.html +5 -5
- package/docs/docs/development/local-development.md +2 -2
- package/docs/docs/development/project-setup/index.html +3 -3
- package/docs/docs/development/remote-bridge/index.html +5 -5
- package/docs/docs/development/remote-bridge.md +2 -2
- package/docs/docs/development/type-generation/index.html +3 -3
- package/docs/docs/development.md +2 -2
- package/docs/docs/index.html +5 -5
- package/docs/docs/plugins/index.html +3 -3
- package/docs/docs.md +2 -2
- package/package.json +1 -1
- package/dist/analytics/analytics.d.ts.map +0 -1
- package/dist/analytics/analytics.js.map +0 -1
- package/dist/analytics/defaults.js.map +0 -1
- package/dist/analytics/query.js.map +0 -1
- package/dist/analytics/types.d.ts +0 -9
- package/dist/analytics/types.d.ts.map +0 -1
- package/dist/server/base-server.js.map +0 -1
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/index.js.map +0 -1
- package/dist/server/remote-tunnel/gate.js.map +0 -1
- package/dist/server/remote-tunnel/remote-tunnel-controller.js.map +0 -1
- package/dist/server/remote-tunnel/remote-tunnel-manager.js.map +0 -1
- package/dist/server/static-server.js.map +0 -1
- package/dist/server/types.d.ts.map +0 -1
- package/dist/server/utils.js.map +0 -1
- package/dist/server/vite-dev-server.js.map +0 -1
- /package/dist/{analytics → plugins/analytics}/index.js +0 -0
- /package/dist/{server → plugins/server}/remote-tunnel/denied.html +0 -0
- /package/dist/{server → plugins/server}/remote-tunnel/index.html +0 -0
- /package/dist/{server → plugins/server}/remote-tunnel/wait.html +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","names":[],"sources":["../../../src/plugins/analytics/analytics.ts"],"sourcesContent":[],"mappings":";;;;;;;;cA0Ba,eAAA,SAAwB,MAAA;;;EAAxB,iBAAA,WAAgB,EAAA,MAAA;EAAA,UAAA,MAAA,EAKD,gBALC;UAKD,SAAA;UAMN,cAAA;aAWC,CAAA,MAAA,EAXD,gBAWC;cA0BN,CAAA,MAAA,EA1BM,UA0BN,CAAA,EAAA,IAAA;;;;;mBA2CZ,CAAA,GAAA,EA3CI,OAAA,CAAQ,OA2CZ,EAAA,GAAA,EA1CI,OAAA,CAAQ,QA0CZ,CAAA,EAzCA,OAyCA,CAAA,IAAA,CAAA;;;;;mBAkHA,CAAA,GAAA,EApHI,OAAA,CAAQ,OAoHZ,EAAA,GAAA,EAnHI,OAAA,CAAQ,QAmHZ,CAAA,EAlHA,OAkHA,CAAA,IAAA,CAAA;;;;;;;;;;;;;AAqDL;;;OAAsB,CAAA,KAAA,EAAA,MAAA,EAAA,UAAA,CAAA,EAxDL,MAwDK,CAAA,MAAA,EAxDU,aAwDV,GAAA,IAAA,GAAA,SAAA,CAAA,EAAA,gBAAA,CAAA,EAvDC,MAuDD,CAAA,MAAA,EAAA,GAAA,CAAA,EAAA,MAAA,CAAA,EAtDT,WAsDS,CAAA,EArDjB,OAqDiB,CAAA,GAAA,CAAA;;;;0CA5BD,yCAER,cACR,QAAQ;cAIO;;;;;;;;;wCAnCH,eAAe,sDACT,8BACV,gBACR;;;;;;cAqDQ,WAAS,gBAAA,iBAAA"}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { createLogger } from "
|
|
2
|
-
import { getCurrentUserId, getWarehouseId, getWorkspaceClient } from "
|
|
3
|
-
import { init_context } from "
|
|
4
|
-
import { SQLWarehouseConnector } from "
|
|
5
|
-
import "
|
|
6
|
-
import { Plugin } from "
|
|
7
|
-
import { toPlugin } from "
|
|
8
|
-
import "
|
|
1
|
+
import { createLogger } from "../../logging/logger.js";
|
|
2
|
+
import { getCurrentUserId, getWarehouseId, getWorkspaceClient } from "../../context/execution-context.js";
|
|
3
|
+
import { init_context } from "../../context/index.js";
|
|
4
|
+
import { SQLWarehouseConnector } from "../../connectors/sql-warehouse/client.js";
|
|
5
|
+
import "../../connectors/index.js";
|
|
6
|
+
import { Plugin } from "../../plugin/plugin.js";
|
|
7
|
+
import { toPlugin } from "../../plugin/to-plugin.js";
|
|
8
|
+
import "../../plugin/index.js";
|
|
9
9
|
import { queryDefaults } from "./defaults.js";
|
|
10
10
|
import { QueryProcessor } from "./query.js";
|
|
11
11
|
|
|
12
|
-
//#region src/analytics/analytics.ts
|
|
12
|
+
//#region src/plugins/analytics/analytics.ts
|
|
13
13
|
init_context();
|
|
14
14
|
const logger = createLogger("analytics");
|
|
15
15
|
var AnalyticsPlugin = class extends Plugin {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.js","names":[],"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 { queryDefaults } from \"./defaults\";\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 protected envVars: string[] = [];\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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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.js","names":[],"sources":["../../../src/plugins/analytics/defaults.ts"],"sourcesContent":["import type { PluginExecuteConfig } from \"shared\";\n\nexport const queryDefaults: PluginExecuteConfig = {\n cache: {\n enabled: true,\n ttl: 3600,\n },\n retry: {\n enabled: true,\n initialDelay: 1500,\n attempts: 3,\n },\n timeout: 18000,\n};\n"],"mappings":";AAEA,MAAa,gBAAqC;CAChD,OAAO;EACL,SAAS;EACT,KAAK;EACN;CACD,OAAO;EACL,SAAS;EACT,cAAc;EACd,UAAU;EACX;CACD,SAAS;CACV"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { isSQLTypeMarker, sql } from "
|
|
2
|
-
import { ValidationError } from "
|
|
3
|
-
import { init_errors } from "
|
|
4
|
-
import { getWorkspaceId } from "
|
|
5
|
-
import { init_context } from "
|
|
1
|
+
import { isSQLTypeMarker, sql } from "../../shared/src/sql/helpers.js";
|
|
2
|
+
import { ValidationError } from "../../errors/validation.js";
|
|
3
|
+
import { init_errors } from "../../errors/index.js";
|
|
4
|
+
import { getWorkspaceId } from "../../context/execution-context.js";
|
|
5
|
+
import { init_context } from "../../context/index.js";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
|
|
8
|
-
//#region src/analytics/query.ts
|
|
8
|
+
//#region src/plugins/analytics/query.ts
|
|
9
9
|
init_context();
|
|
10
10
|
init_errors();
|
|
11
11
|
var QueryProcessor = class {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","names":["sqlHelpers"],"sources":["../../../src/plugins/analytics/query.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { sql } from \"@databricks/sdk-experimental\";\nimport { isSQLTypeMarker, type SQLTypeMarker, sql as sqlHelpers } from \"shared\";\nimport { getWorkspaceId } from \"../../context\";\nimport { ValidationError } from \"../../errors\";\n\ntype SQLParameterValue = SQLTypeMarker | null | undefined;\n\nexport class QueryProcessor {\n async processQueryParams(\n query: string,\n parameters?: Record<string, SQLParameterValue>,\n ): Promise<Record<string, SQLParameterValue>> {\n const processed = { ...parameters };\n\n // extract all params from the query\n const paramMatches = query.matchAll(/:([a-zA-Z_]\\w*)/g);\n const queryParams = new Set(Array.from(paramMatches, (m) => m[1]));\n\n // auto-inject workspaceId if needed and not provided\n if (queryParams.has(\"workspaceId\") && !processed.workspaceId) {\n const workspaceId = await getWorkspaceId();\n if (workspaceId) {\n processed.workspaceId = sqlHelpers.string(workspaceId);\n }\n }\n\n return processed;\n }\n\n hashQuery(query: string): string {\n return createHash(\"md5\").update(query).digest(\"hex\");\n }\n\n convertToSQLParameters(\n query: string,\n parameters?: Record<string, SQLParameterValue>,\n ): { statement: string; parameters: sql.StatementParameterListItem[] } {\n const sqlParameters: sql.StatementParameterListItem[] = [];\n\n if (parameters) {\n // extract all params from the query\n const queryParamMatches = query.matchAll(/:([a-zA-Z_]\\w*)/g);\n const queryParams = new Set(Array.from(queryParamMatches, (m) => m[1]));\n\n // only allow parameters that exist in the query\n for (const key of Object.keys(parameters)) {\n if (!queryParams.has(key)) {\n const validParams = Array.from(queryParams).join(\", \") || \"none\";\n throw ValidationError.invalidValue(\n key,\n parameters[key],\n `a parameter defined in the query (valid: ${validParams})`,\n );\n }\n }\n\n // convert parameters to SQL parameters\n for (const [key, value] of Object.entries(parameters)) {\n const parameter = this._createParameter(key, value);\n if (parameter) {\n sqlParameters.push(parameter);\n }\n }\n }\n\n return { statement: query, parameters: sqlParameters };\n }\n\n private _createParameter(\n key: string,\n value: SQLParameterValue,\n ): sql.StatementParameterListItem | null {\n if (value === null || value === undefined) {\n return null;\n }\n\n if (!isSQLTypeMarker(value)) {\n throw ValidationError.invalidValue(\n key,\n value,\n \"SQL type (use sql.string(), sql.number(), sql.date(), sql.timestamp(), or sql.boolean())\",\n );\n }\n\n return {\n name: key,\n value: value.value,\n type: value.__sql_type,\n };\n }\n}\n"],"mappings":";;;;;;;;cAG+C;aACA;AAI/C,IAAa,iBAAb,MAA4B;CAC1B,MAAM,mBACJ,OACA,YAC4C;EAC5C,MAAM,YAAY,EAAE,GAAG,YAAY;EAGnC,MAAM,eAAe,MAAM,SAAS,mBAAmB;AAIvD,MAHoB,IAAI,IAAI,MAAM,KAAK,eAAe,MAAM,EAAE,GAAG,CAAC,CAGlD,IAAI,cAAc,IAAI,CAAC,UAAU,aAAa;GAC5D,MAAM,cAAc,MAAM,gBAAgB;AAC1C,OAAI,YACF,WAAU,cAAcA,IAAW,OAAO,YAAY;;AAI1D,SAAO;;CAGT,UAAU,OAAuB;AAC/B,SAAO,WAAW,MAAM,CAAC,OAAO,MAAM,CAAC,OAAO,MAAM;;CAGtD,uBACE,OACA,YACqE;EACrE,MAAM,gBAAkD,EAAE;AAE1D,MAAI,YAAY;GAEd,MAAM,oBAAoB,MAAM,SAAS,mBAAmB;GAC5D,MAAM,cAAc,IAAI,IAAI,MAAM,KAAK,oBAAoB,MAAM,EAAE,GAAG,CAAC;AAGvE,QAAK,MAAM,OAAO,OAAO,KAAK,WAAW,CACvC,KAAI,CAAC,YAAY,IAAI,IAAI,EAAE;IACzB,MAAM,cAAc,MAAM,KAAK,YAAY,CAAC,KAAK,KAAK,IAAI;AAC1D,UAAM,gBAAgB,aACpB,KACA,WAAW,MACX,4CAA4C,YAAY,GACzD;;AAKL,QAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,WAAW,EAAE;IACrD,MAAM,YAAY,KAAK,iBAAiB,KAAK,MAAM;AACnD,QAAI,UACF,eAAc,KAAK,UAAU;;;AAKnC,SAAO;GAAE,WAAW;GAAO,YAAY;GAAe;;CAGxD,AAAQ,iBACN,KACA,OACuC;AACvC,MAAI,UAAU,QAAQ,UAAU,OAC9B,QAAO;AAGT,MAAI,CAAC,gBAAgB,MAAM,CACzB,OAAM,gBAAgB,aACpB,KACA,OACA,2FACD;AAGH,SAAO;GACL,MAAM;GACN,OAAO,MAAM;GACb,MAAM,MAAM;GACb"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BasePluginConfig } from "../../shared/src/plugin.js";
|
|
2
|
+
|
|
3
|
+
//#region src/plugins/analytics/types.d.ts
|
|
4
|
+
interface IAnalyticsConfig extends BasePluginConfig {
|
|
5
|
+
timeout?: number;
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
export { IAnalyticsConfig };
|
|
9
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/plugins/analytics/types.ts"],"sourcesContent":[],"mappings":";;;UAEiB,gBAAA,SAAyB;;AAA1C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-server.js","names":[],"sources":["../../../src/plugins/server/base-server.ts"],"sourcesContent":["import type express from \"express\";\nimport { getConfigScript, type PluginEndpoints } from \"./utils\";\n\n/**\n * Base server for the AppKit.\n *\n * Abstract base class that provides common functionality for serving\n * frontend applications. Subclasses implement specific serving strategies\n * (Vite dev server, static file server, etc.).\n */\nexport abstract class BaseServer {\n protected app: express.Application;\n protected endpoints: PluginEndpoints;\n\n constructor(app: express.Application, endpoints: PluginEndpoints = {}) {\n this.app = app;\n this.endpoints = endpoints;\n }\n\n abstract setup(): void | Promise<void>;\n\n async close(): Promise<void> {}\n\n protected getConfigScript(): string {\n return getConfigScript(this.endpoints);\n }\n}\n"],"mappings":";;;;;;;;;;AAUA,IAAsB,aAAtB,MAAiC;CAC/B,AAAU;CACV,AAAU;CAEV,YAAY,KAA0B,YAA6B,EAAE,EAAE;AACrE,OAAK,MAAM;AACX,OAAK,YAAY;;CAKnB,MAAM,QAAuB;CAE7B,AAAU,kBAA0B;AAClC,SAAO,gBAAgB,KAAK,UAAU"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { PluginPhase, TelemetryOptions, ToPlugin } from "
|
|
2
|
-
import { Plugin } from "
|
|
1
|
+
import { PluginPhase, TelemetryOptions, ToPlugin } from "../../shared/src/plugin.js";
|
|
2
|
+
import { Plugin } from "../../plugin/plugin.js";
|
|
3
3
|
import { ServerConfig } from "./types.js";
|
|
4
4
|
import express from "express";
|
|
5
5
|
import { Server } from "node:http";
|
|
6
6
|
|
|
7
|
-
//#region src/server/index.d.ts
|
|
7
|
+
//#region src/plugins/server/index.d.ts
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Server plugin for the AppKit.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/plugins/server/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AAkCA;;;;;;;;;;;;AAwDiB,cAxDJ,YAAA,SAAqB,MAAA,CAwDjB;SAsPM,cAAQ,EAAA;IAxMhB,SAAA,EAAA,OAAA;IAAU,IAAA,EAAA,MAAA;IAtGS,IAAA,EAAA,MAAA;EAAM,CAAA;EA+T3B,IAAA,EAAA,QAGZ;EAAA,UAAA,OAAA,EAAA,MAAA,EAAA;UAHkB,iBAAA;UAAA,MAAA;UAAA,aAAA;EAAA,QAAA,sBAAA;oBAlTS;;gBAEZ;sBAEM;;WAaT;;;;;;;;;gBAAA;;;;;;;;;;;;WA0BI,QAAQ,OAAA,CAAQ;;;;;;;;;eA8ClB;;;;;;;;mBAmBI,OAAA,CAAQ;;;;;;;;;;;;;;;;;;;;;;;;iBAjEV,QAAQ,OAAA,CAAQ;;qBAsPV,OAAA,CAAQ;;qBAxMhB;;;;;;;;;kBAAU;;;;;;;cAyNZ,QAAM,gBAAA,cAAA"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { instrumentations } from "
|
|
2
|
-
import { createLogger } from "
|
|
3
|
-
import "
|
|
4
|
-
import { ServerError } from "
|
|
5
|
-
import { init_errors } from "
|
|
6
|
-
import { Plugin } from "
|
|
7
|
-
import { toPlugin } from "
|
|
8
|
-
import "
|
|
1
|
+
import { instrumentations } from "../../telemetry/instrumentations.js";
|
|
2
|
+
import { createLogger } from "../../logging/logger.js";
|
|
3
|
+
import "../../telemetry/index.js";
|
|
4
|
+
import { ServerError } from "../../errors/server.js";
|
|
5
|
+
import { init_errors } from "../../errors/index.js";
|
|
6
|
+
import { Plugin } from "../../plugin/plugin.js";
|
|
7
|
+
import { toPlugin } from "../../plugin/to-plugin.js";
|
|
8
|
+
import "../../plugin/index.js";
|
|
9
9
|
import { RemoteTunnelController } from "./remote-tunnel/remote-tunnel-controller.js";
|
|
10
10
|
import { getRoutes } from "./utils.js";
|
|
11
11
|
import { StaticServer } from "./static-server.js";
|
|
@@ -15,7 +15,7 @@ import fs from "node:fs";
|
|
|
15
15
|
import dotenv from "dotenv";
|
|
16
16
|
import express from "express";
|
|
17
17
|
|
|
18
|
-
//#region src/server/index.ts
|
|
18
|
+
//#region src/plugins/server/index.ts
|
|
19
19
|
init_errors();
|
|
20
20
|
dotenv.config({ path: path.resolve(process.cwd(), "./.env") });
|
|
21
21
|
const logger = createLogger("server");
|
|
@@ -240,5 +240,5 @@ const EXCLUDED_PLUGINS = [ServerPlugin.name];
|
|
|
240
240
|
const server = toPlugin(ServerPlugin, "server");
|
|
241
241
|
|
|
242
242
|
//#endregion
|
|
243
|
-
export { server };
|
|
243
|
+
export { ServerPlugin, server };
|
|
244
244
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/plugins/server/index.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport type { Server as HTTPServer } from \"node:http\";\nimport path from \"node:path\";\nimport dotenv from \"dotenv\";\nimport express from \"express\";\nimport type { PluginPhase } from \"shared\";\nimport { ServerError } from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport { instrumentations } from \"../../telemetry\";\nimport { RemoteTunnelController } from \"./remote-tunnel/remote-tunnel-controller\";\nimport { StaticServer } from \"./static-server\";\nimport type { ServerConfig } from \"./types\";\nimport { getRoutes, type PluginEndpoints } from \"./utils\";\nimport { ViteDevServer } from \"./vite-dev-server\";\n\ndotenv.config({ path: path.resolve(process.cwd(), \"./.env\") });\n\nconst logger = createLogger(\"server\");\n\n/**\n * Server plugin for the AppKit.\n *\n * This plugin is responsible for starting the server and serving the static files.\n * It also handles the remote tunneling for development purposes.\n *\n * @example\n * ```ts\n * createApp({\n * plugins: [server(), telemetryExamples(), analytics({})],\n * });\n * ```\n *\n */\nexport class ServerPlugin extends Plugin {\n public static DEFAULT_CONFIG = {\n autoStart: true,\n host: process.env.FLASK_RUN_HOST || \"0.0.0.0\",\n port: Number(process.env.DATABRICKS_APP_PORT) || 8000,\n };\n\n public name = \"server\" as const;\n protected envVars: string[] = [];\n private serverApplication: express.Application;\n private server: HTTPServer | null;\n private viteDevServer?: ViteDevServer;\n private remoteTunnelController?: RemoteTunnelController;\n protected declare config: ServerConfig;\n private serverExtensions: ((app: express.Application) => void)[] = [];\n static phase: PluginPhase = \"deferred\";\n\n constructor(config: ServerConfig) {\n super(config);\n this.config = config;\n this.serverApplication = express();\n this.server = null;\n this.serverExtensions = [];\n this.telemetry.registerInstrumentations([\n instrumentations.http,\n instrumentations.express,\n ]);\n }\n\n /** Setup the server plugin. */\n async setup() {\n if (this.shouldAutoStart()) {\n await this.start();\n }\n }\n\n /** Get the server configuration. */\n getConfig() {\n const { plugins: _plugins, ...config } = this.config;\n\n return config;\n }\n\n /** Check if the server should auto start. */\n shouldAutoStart() {\n return this.config.autoStart;\n }\n\n /**\n * Start the server.\n *\n * This method starts the server and sets up the frontend.\n * It also sets up the remote tunneling if enabled.\n *\n * @returns The express application.\n */\n async start(): Promise<express.Application> {\n this.serverApplication.use(express.json());\n\n const endpoints = await this.extendRoutes();\n\n for (const extension of this.serverExtensions) {\n extension(this.serverApplication);\n }\n\n // register remote tunnel controller (before static/vite)\n this.remoteTunnelController = new RemoteTunnelController(\n this.devFileReader,\n );\n this.serverApplication.use(this.remoteTunnelController.middleware);\n\n await this.setupFrontend(endpoints);\n\n const server = this.serverApplication.listen(\n this.config.port ?? ServerPlugin.DEFAULT_CONFIG.port,\n this.config.host ?? ServerPlugin.DEFAULT_CONFIG.host,\n () => this.logStartupInfo(),\n );\n\n this.server = server;\n\n // attach server to remote tunnel controller\n this.remoteTunnelController.setServer(server);\n\n process.on(\"SIGTERM\", () => this._gracefulShutdown());\n process.on(\"SIGINT\", () => this._gracefulShutdown());\n\n if (process.env.NODE_ENV === \"development\") {\n const allRoutes = getRoutes(this.serverApplication._router.stack);\n console.dir(allRoutes, { depth: null });\n }\n return this.serverApplication;\n }\n\n /**\n * Get the low level node.js http server instance.\n *\n * Only use this method if you need to access the server instance for advanced usage like a custom websocket server, etc.\n *\n * @throws {Error} If the server is not started or autoStart is true.\n * @returns {HTTPServer} The server instance.\n */\n getServer(): HTTPServer {\n if (this.shouldAutoStart()) {\n throw ServerError.autoStartConflict(\"get server\");\n }\n\n if (!this.server) {\n throw ServerError.notStarted();\n }\n\n return this.server;\n }\n\n /**\n * Extend the server with custom routes or middleware.\n *\n * @param fn - A function that receives the express application.\n * @returns The server plugin instance for chaining.\n * @throws {Error} If autoStart is true.\n */\n extend(fn: (app: express.Application) => void) {\n if (this.shouldAutoStart()) {\n throw ServerError.autoStartConflict(\"extend server\");\n }\n\n this.serverExtensions.push(fn);\n return this;\n }\n\n /**\n * Setup the routes with the plugins.\n *\n * This method goes through all the plugins and injects the routes into the server application.\n * Returns a map of plugin names to their registered named endpoints.\n */\n private async extendRoutes(): Promise<PluginEndpoints> {\n const endpoints: PluginEndpoints = {};\n\n if (!this.config.plugins) return endpoints;\n\n this.serverApplication.get(\"/health\", (_, res) => {\n res.status(200).json({ status: \"ok\" });\n });\n this.registerEndpoint(\"health\", \"/health\");\n\n for (const plugin of Object.values(this.config.plugins)) {\n if (EXCLUDED_PLUGINS.includes(plugin.name)) continue;\n\n if (plugin?.injectRoutes && typeof plugin.injectRoutes === \"function\") {\n const router = express.Router();\n\n plugin.injectRoutes(router);\n\n const basePath = `/api/${plugin.name}`;\n this.serverApplication.use(basePath, router);\n\n // Collect named endpoints from the plugin\n endpoints[plugin.name] = plugin.getEndpoints();\n }\n }\n\n return endpoints;\n }\n\n /**\n * Setup frontend serving based on environment:\n * - If staticPath is explicitly provided: use static server\n * - Dev mode (no staticPath): Vite for HMR\n * - Production (no staticPath): Static files auto-detected\n */\n private async setupFrontend(endpoints: PluginEndpoints) {\n const isDev = process.env.NODE_ENV === \"development\";\n const hasExplicitStaticPath = this.config.staticPath !== undefined;\n\n // explict static path provided\n if (hasExplicitStaticPath) {\n const staticServer = new StaticServer(\n this.serverApplication,\n this.config.staticPath as string,\n endpoints,\n );\n staticServer.setup();\n return;\n }\n\n // auto-detection based on environment\n if (isDev) {\n this.viteDevServer = new ViteDevServer(this.serverApplication, endpoints);\n await this.viteDevServer.setup();\n return;\n }\n\n // auto-detection based on static path\n const staticPath = ServerPlugin.findStaticPath();\n if (staticPath) {\n const staticServer = new StaticServer(\n this.serverApplication,\n staticPath,\n endpoints,\n );\n\n staticServer.setup();\n }\n }\n\n private static findStaticPath() {\n const staticPaths = [\"dist\", \"client/dist\", \"build\", \"public\", \"out\"];\n const cwd = process.cwd();\n for (const p of staticPaths) {\n const fullPath = path.resolve(cwd, p);\n if (fs.existsSync(path.resolve(fullPath, \"index.html\"))) {\n logger.debug(\"Static files: serving from %s\", fullPath);\n return fullPath;\n }\n }\n return undefined;\n }\n\n private logStartupInfo() {\n const isDev = process.env.NODE_ENV === \"development\";\n const hasExplicitStaticPath = this.config.staticPath !== undefined;\n const port = this.config.port ?? ServerPlugin.DEFAULT_CONFIG.port;\n const host = this.config.host ?? ServerPlugin.DEFAULT_CONFIG.host;\n\n logger.info(\"Server running on http://%s:%d\", host, port);\n\n if (hasExplicitStaticPath) {\n logger.info(\"Mode: static (%s)\", this.config.staticPath);\n } else if (isDev) {\n logger.info(\"Mode: development (Vite HMR)\");\n } else {\n logger.info(\"Mode: production (static)\");\n }\n\n const remoteServerController = this.remoteTunnelController;\n if (!remoteServerController) {\n logger.debug(\"Remote tunnel: disabled (controller not initialized)\");\n } else {\n logger.debug(\n \"Remote tunnel: %s; %s\",\n remoteServerController.isAllowedByEnv() ? \"allowed\" : \"blocked\",\n remoteServerController.isActive() ? \"active\" : \"inactive\",\n );\n }\n }\n\n private async _gracefulShutdown() {\n logger.info(\"Starting graceful shutdown...\");\n\n if (this.viteDevServer) {\n await this.viteDevServer.close();\n }\n\n if (this.remoteTunnelController) {\n this.remoteTunnelController.cleanup();\n }\n\n // 1. abort active operations from plugins\n if (this.config.plugins) {\n for (const plugin of Object.values(this.config.plugins)) {\n if (plugin.abortActiveOperations) {\n try {\n plugin.abortActiveOperations();\n } catch (err) {\n logger.error(\n \"Error aborting operations for plugin %s: %O\",\n plugin.name,\n err,\n );\n }\n }\n }\n }\n\n // 2. close the server\n if (this.server) {\n this.server.close(() => {\n logger.debug(\"Server closed gracefully\");\n process.exit(0);\n });\n\n // 3. timeout to force shutdown after 15 seconds\n setTimeout(() => {\n logger.debug(\"Force shutdown after timeout\");\n process.exit(1);\n }, 15000);\n } else {\n process.exit(0);\n }\n }\n\n /**\n * Returns the public exports for the server plugin.\n * Exposes server management methods.\n */\n exports() {\n const self = this;\n return {\n /** Start the server */\n start: this.start,\n /** Extend the server with custom routes or middleware */\n extend(fn: (app: express.Application) => void) {\n self.extend(fn);\n return this;\n },\n /** Get the underlying HTTP server instance */\n getServer: this.getServer,\n /** Get the server configuration */\n getConfig: this.getConfig,\n };\n }\n}\n\nconst EXCLUDED_PLUGINS = [ServerPlugin.name];\n\n/**\n * @internal\n */\nexport const server = toPlugin<typeof ServerPlugin, ServerConfig, \"server\">(\n ServerPlugin,\n \"server\",\n);\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.js","names":[],"sources":["../../../../src/plugins/server/remote-tunnel/gate.ts"],"sourcesContent":["import type express from \"express\";\n\n/** Assets prefixes that are served through the remote tunnel */\nexport const REMOTE_TUNNEL_ASSET_PREFIXES = [\n \"/@vite/\",\n \"/@fs/\",\n \"/node_modules/.vite/deps/\",\n \"/node_modules/vite/\",\n \"/src/\",\n \"/@react-refresh\",\n];\n\n/**\n * Check if the server is running in local development mode\n * - NODE_ENV is set to \"development\"\n * @param env - the environment variables\n * @returns true if the server is running in local development mode\n */\nexport function isLocalDev(env: NodeJS.ProcessEnv = process.env): boolean {\n return env.NODE_ENV === \"development\";\n}\n\n/**\n * Check if the environment allows the remote tunnel\n * - not in local development mode\n * - DISABLE_REMOTE_SERVING is not set to \"true\"\n * - DATABRICKS_CLIENT_SECRET is set\n * @param env - the environment variables\n * @returns true if the environment allows the remote tunnel\n */\nexport function isRemoteTunnelAllowedByEnv(\n env: NodeJS.ProcessEnv = process.env,\n): boolean {\n return (\n !isLocalDev(env) &&\n env.DISABLE_REMOTE_SERVING !== \"true\" &&\n Boolean(env.DATABRICKS_CLIENT_SECRET)\n );\n}\n\n/**\n * Check if the request has a dev query parameter\n * @param req - the request\n * @returns true if the request has a dev query parameter\n */\nexport function hasDevQuery(req: express.Request): boolean {\n const queryParams = req.query;\n\n return \"dev\" in queryParams;\n}\n\n/**\n * Check if the request is an asset request\n * @param req - the request\n * @returns true if the request is an asset request\n */\nexport function isRemoteTunnelAssetRequest(req: express.Request): boolean {\n const path = req.originalUrl;\n return REMOTE_TUNNEL_ASSET_PREFIXES.some((prefix) => path.startsWith(prefix));\n}\n"],"mappings":";;AAGA,MAAa,+BAA+B;CAC1C;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;AAQD,SAAgB,WAAW,MAAyB,QAAQ,KAAc;AACxE,QAAO,IAAI,aAAa;;;;;;;;;;AAW1B,SAAgB,2BACd,MAAyB,QAAQ,KACxB;AACT,QACE,CAAC,WAAW,IAAI,IAChB,IAAI,2BAA2B,UAC/B,QAAQ,IAAI,yBAAyB;;;;;;;AASzC,SAAgB,YAAY,KAA+B;AAGzD,QAAO,SAFa,IAAI;;;;;;;AAU1B,SAAgB,2BAA2B,KAA+B;CACxE,MAAM,OAAO,IAAI;AACjB,QAAO,6BAA6B,MAAM,WAAW,KAAK,WAAW,OAAO,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createLogger } from "
|
|
1
|
+
import { createLogger } from "../../../logging/logger.js";
|
|
2
2
|
import { hasDevQuery, isLocalDev, isRemoteTunnelAllowedByEnv, isRemoteTunnelAssetRequest } from "./gate.js";
|
|
3
3
|
|
|
4
|
-
//#region src/server/remote-tunnel/remote-tunnel-controller.ts
|
|
4
|
+
//#region src/plugins/server/remote-tunnel/remote-tunnel-controller.ts
|
|
5
5
|
const logger = createLogger("server:remote-tunnel:controller");
|
|
6
6
|
/**
|
|
7
7
|
* Controller for the remote tunnel
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-tunnel-controller.js","names":[],"sources":["../../../../src/plugins/server/remote-tunnel/remote-tunnel-controller.ts"],"sourcesContent":["import type { Server as HTTPServer } from \"node:http\";\nimport type express from \"express\";\nimport { createLogger } from \"../../../logging/logger\";\nimport type { DevFileReader } from \"../../../plugin/dev-reader\";\nimport {\n hasDevQuery,\n isLocalDev,\n isRemoteTunnelAllowedByEnv,\n isRemoteTunnelAssetRequest,\n} from \"./gate\";\nimport type { RemoteTunnelManager } from \"./remote-tunnel-manager\";\n\nconst logger = createLogger(\"server:remote-tunnel:controller\");\n\n/**\n * Controller for the remote tunnel\n *\n * - Reads files from the dev file reader\n * - Manages the remote tunnel manager\n * - Sets up the web socket\n * - Cleans up the remote tunnel\n */\nexport class RemoteTunnelController {\n private devFileReader: DevFileReader;\n private server?: HTTPServer;\n private manager: RemoteTunnelManager | null;\n private initPromise: Promise<RemoteTunnelManager | null> | null;\n private wsReady: boolean;\n\n constructor(devFileReader: DevFileReader) {\n this.devFileReader = devFileReader;\n this.manager = null;\n this.initPromise = null;\n this.wsReady = false;\n }\n\n /**\n * Set the server instance\n * @param server\n */\n setServer(server: HTTPServer) {\n this.server = server;\n this.maybeSetupWebSocket();\n }\n\n /** Check if the remote tunnel is active */\n isActive(): boolean {\n return this.manager != null;\n }\n\n /** Check if the remote tunnel is allowed by the environment */\n isAllowedByEnv(): boolean {\n return isRemoteTunnelAllowedByEnv();\n }\n\n /**\n * Middleware for the remote tunnel\n * - Hard blocks in local dev\n * - Blocks when not allowed by env\n * - Handles dev query and asset requests\n * @param req - the request\n * @param res - the response\n * @param next - the next function\n * @returns the next function\n */\n middleware: express.RequestHandler = async (req, res, next) => {\n // hard blocker in local dev\n if (isLocalDev()) return next();\n\n // if not allowed by env, block\n if (!this.isAllowedByEnv()) return next();\n\n const wantsDev = hasDevQuery(req);\n const wantsRemoteAssets = isRemoteTunnelAssetRequest(req);\n\n // if not wants dev and not wants remote assets, skip\n if (!wantsDev && !wantsRemoteAssets) return next();\n\n const manager = await this.getOrInitManager();\n // if no manager, skip\n if (!manager) return next();\n\n // dev query present, let manager handle it\n if (wantsDev) {\n return manager.devModeMiddleware()(req, res, next);\n }\n\n // otherwise, handle vite asset fetch paths through the tunnel\n try {\n await manager.assetMiddleware()(req, res);\n } catch (error) {\n next(error);\n }\n };\n\n /** Cleanup the remote tunnel */\n cleanup() {\n try {\n this.manager?.cleanup();\n } finally {\n this.manager = null;\n this.initPromise = null;\n this.wsReady = false;\n }\n }\n\n /**\n * Get or initialize the remote tunnel manager\n * - If the manager is already initialized, return it\n * - If the manager is not initialized, initialize it\n * - If the manager is not allowed by the environment, return null\n * @returns the remote tunnel manager\n */\n private async getOrInitManager(): Promise<RemoteTunnelManager | null> {\n if (this.manager) return this.manager;\n if (this.initPromise) return await this.initPromise;\n\n this.initPromise = (async () => {\n // double check gate\n if (isLocalDev() || !isRemoteTunnelAllowedByEnv()) return null;\n const mod = await import(\"./remote-tunnel-manager\");\n const remoteTunnelManager = new mod.RemoteTunnelManager(\n this.devFileReader,\n );\n this.manager = remoteTunnelManager;\n\n // attach server + ws setup\n this.maybeSetupWebSocket();\n\n logger.debug(\"RemoteTunnel: initialized (on-demand)\");\n return remoteTunnelManager;\n })();\n\n return this.initPromise;\n }\n\n /**\n * Setup the web socket\n * - If the server is not set, return\n * - If the manager is not set, return\n * - If the web socket is already setup, return\n * - Setup the web socket\n */\n private maybeSetupWebSocket() {\n if (!this.server) return;\n if (!this.manager) return;\n if (this.wsReady) return;\n\n this.manager.setServer(this.server);\n this.manager.setupWebSocket();\n this.wsReady = true;\n\n logger.debug(\"RemoteTunnel: web socket setup complete\");\n }\n}\n"],"mappings":";;;;AAYA,MAAM,SAAS,aAAa,kCAAkC;;;;;;;;;AAU9D,IAAa,yBAAb,MAAoC;CAClC,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,YAAY,eAA8B;AACxC,OAAK,gBAAgB;AACrB,OAAK,UAAU;AACf,OAAK,cAAc;AACnB,OAAK,UAAU;;;;;;CAOjB,UAAU,QAAoB;AAC5B,OAAK,SAAS;AACd,OAAK,qBAAqB;;;CAI5B,WAAoB;AAClB,SAAO,KAAK,WAAW;;;CAIzB,iBAA0B;AACxB,SAAO,4BAA4B;;;;;;;;;;;;CAarC,aAAqC,OAAO,KAAK,KAAK,SAAS;AAE7D,MAAI,YAAY,CAAE,QAAO,MAAM;AAG/B,MAAI,CAAC,KAAK,gBAAgB,CAAE,QAAO,MAAM;EAEzC,MAAM,WAAW,YAAY,IAAI;EACjC,MAAM,oBAAoB,2BAA2B,IAAI;AAGzD,MAAI,CAAC,YAAY,CAAC,kBAAmB,QAAO,MAAM;EAElD,MAAM,UAAU,MAAM,KAAK,kBAAkB;AAE7C,MAAI,CAAC,QAAS,QAAO,MAAM;AAG3B,MAAI,SACF,QAAO,QAAQ,mBAAmB,CAAC,KAAK,KAAK,KAAK;AAIpD,MAAI;AACF,SAAM,QAAQ,iBAAiB,CAAC,KAAK,IAAI;WAClC,OAAO;AACd,QAAK,MAAM;;;;CAKf,UAAU;AACR,MAAI;AACF,QAAK,SAAS,SAAS;YACf;AACR,QAAK,UAAU;AACf,QAAK,cAAc;AACnB,QAAK,UAAU;;;;;;;;;;CAWnB,MAAc,mBAAwD;AACpE,MAAI,KAAK,QAAS,QAAO,KAAK;AAC9B,MAAI,KAAK,YAAa,QAAO,MAAM,KAAK;AAExC,OAAK,eAAe,YAAY;AAE9B,OAAI,YAAY,IAAI,CAAC,4BAA4B,CAAE,QAAO;GAE1D,MAAM,sBAAsB,KADhB,OAAM,OAAO,gCACW,oBAClC,KAAK,cACN;AACD,QAAK,UAAU;AAGf,QAAK,qBAAqB;AAE1B,UAAO,MAAM,wCAAwC;AACrD,UAAO;MACL;AAEJ,SAAO,KAAK;;;;;;;;;CAUd,AAAQ,sBAAsB;AAC5B,MAAI,CAAC,KAAK,OAAQ;AAClB,MAAI,CAAC,KAAK,QAAS;AACnB,MAAI,KAAK,QAAS;AAElB,OAAK,QAAQ,UAAU,KAAK,OAAO;AACnC,OAAK,QAAQ,gBAAgB;AAC7B,OAAK,UAAU;AAEf,SAAO,MAAM,0CAA0C"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createLogger } from "
|
|
1
|
+
import { createLogger } from "../../../logging/logger.js";
|
|
2
2
|
import { REMOTE_TUNNEL_ASSET_PREFIXES } from "./gate.js";
|
|
3
3
|
import { generateTunnelIdFromEmail, getConfigScript, parseCookies } from "../utils.js";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
@@ -7,12 +7,19 @@ import fs from "node:fs";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { WebSocketServer } from "ws";
|
|
9
9
|
|
|
10
|
-
//#region src/server/remote-tunnel/remote-tunnel-manager.ts
|
|
10
|
+
//#region src/plugins/server/remote-tunnel/remote-tunnel-manager.ts
|
|
11
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
12
|
const __dirname = path$1.dirname(__filename);
|
|
13
13
|
const MAX_ASSET_FETCH_TIMEOUT = 6e4;
|
|
14
14
|
const logger = createLogger("server:remote-tunnel");
|
|
15
15
|
/**
|
|
16
|
+
* Type guard to validate WebSocket message structure
|
|
17
|
+
*/
|
|
18
|
+
function isWebSocketMessage(data) {
|
|
19
|
+
if (!data || typeof data !== "object") return false;
|
|
20
|
+
return typeof data.type === "string";
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
16
23
|
* Remote tunnel manager for the AppKit.
|
|
17
24
|
*
|
|
18
25
|
* This class is responsible for managing the remote tunnels for the development server.
|
|
@@ -193,6 +200,10 @@ var RemoteTunnelManager = class {
|
|
|
193
200
|
}
|
|
194
201
|
try {
|
|
195
202
|
const data = JSON.parse(msg.toString());
|
|
203
|
+
if (!isWebSocketMessage(data)) {
|
|
204
|
+
logger.error("Invalid WebSocket message format: %O", data);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
196
207
|
if (data.type === "connection:response") {
|
|
197
208
|
if (tunnel && data.viewer) {
|
|
198
209
|
tunnel.pendingRequests.delete(data.viewer);
|
|
@@ -227,7 +238,17 @@ var RemoteTunnelManager = class {
|
|
|
227
238
|
clearTimeout(pending.timeout);
|
|
228
239
|
tunnel.pendingFileReads.delete(data.requestId);
|
|
229
240
|
if (data.error) pending.reject(new Error(data.error));
|
|
230
|
-
else pending.resolve(data.content);
|
|
241
|
+
else if (data.content !== void 0) pending.resolve(data.content);
|
|
242
|
+
else pending.reject(/* @__PURE__ */ new Error("Missing content in file:read:response"));
|
|
243
|
+
}
|
|
244
|
+
} else if (data.type === "dir:list:response") {
|
|
245
|
+
const pending = tunnel.pendingFileReads.get(data.requestId);
|
|
246
|
+
if (pending) {
|
|
247
|
+
clearTimeout(pending.timeout);
|
|
248
|
+
tunnel.pendingFileReads.delete(data.requestId);
|
|
249
|
+
if (data.error) pending.reject(new Error(data.error));
|
|
250
|
+
else if (data.content !== void 0) pending.resolve(data.content);
|
|
251
|
+
else pending.reject(/* @__PURE__ */ new Error("Missing content in dir:list:response"));
|
|
231
252
|
}
|
|
232
253
|
}
|
|
233
254
|
} catch (e) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-tunnel-manager.js","names":["path"],"sources":["../../../../src/plugins/server/remote-tunnel/remote-tunnel-manager.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport type { Server as HTTPServer } from \"node:http\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type express from \"express\";\nimport type { TunnelConnection } from \"shared\";\nimport { WebSocketServer } from \"ws\";\nimport { createLogger } from \"../../../logging/logger\";\nimport {\n generateTunnelIdFromEmail,\n getConfigScript,\n parseCookies,\n} from \"../utils\";\nimport { REMOTE_TUNNEL_ASSET_PREFIXES } from \"./gate\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst MAX_ASSET_FETCH_TIMEOUT = 60_000;\n\nconst logger = createLogger(\"server:remote-tunnel\");\n\ninterface DevFileReader {\n registerTunnelGetter(\n getter: (req: express.Request) => TunnelConnection | null,\n ): void;\n}\n\n/**\n * WebSocket message types for CLI <-> Server communication\n */\ntype WebSocketMessage =\n | {\n type: \"connection:response\";\n viewer: string;\n approved: boolean;\n }\n | {\n type: \"fetch:response:meta\";\n requestId: string;\n status: number;\n headers: Record<string, string>;\n }\n | {\n type: \"file:read:response\";\n requestId: string;\n content?: string;\n error?: string;\n }\n | {\n type: \"dir:list:response\";\n requestId: string;\n content?: string;\n error?: string;\n }\n | {\n type: \"hmr:message\";\n body: string;\n };\n\n/**\n * Type guard to validate WebSocket message structure\n */\nfunction isWebSocketMessage(data: unknown): data is WebSocketMessage {\n if (!data || typeof data !== \"object\") {\n return false;\n }\n\n const msg = data as Record<string, unknown>;\n return typeof msg.type === \"string\";\n}\n\n/**\n * Remote tunnel manager for the AppKit.\n *\n * This class is responsible for managing the remote tunnels for the development server.\n * It also handles the asset fetching and the HMR for the development server.\n *\n * @example\n * ```ts\n * const remoteTunnelManager = new RemoteTunnelManager(devFileReader);\n * remoteTunnelManager.setup(app);\n * ```\n */\nexport class RemoteTunnelManager {\n private tunnels = new Map<string, TunnelConnection>();\n private wss: WebSocketServer;\n private hmrWss: WebSocketServer;\n private server?: HTTPServer;\n private devFileReader: DevFileReader;\n\n constructor(devFileReader: DevFileReader) {\n this.devFileReader = devFileReader;\n this.wss = new WebSocketServer({ noServer: true, path: \"/dev-tunnel\" });\n this.hmrWss = new WebSocketServer({ noServer: true, path: \"/dev-hmr\" });\n\n this.registerTunnelGetter();\n }\n\n setServer(server: HTTPServer) {\n this.server = server;\n }\n\n /** Asset middleware for the development server. */\n assetMiddleware() {\n return async (req: express.Request, res: express.Response) => {\n const email = req.headers[\"x-forwarded-email\"] as string;\n\n // Try cookie first, then generate from email\n let tunnelId: string | undefined;\n const cookieHeader = req.headers.cookie;\n\n if (cookieHeader) {\n // Fast path: extract dev-tunnel-id from cookie without full parse\n const match = cookieHeader.match(/dev-tunnel-id=([^;]+)/);\n if (match) {\n tunnelId = match[1];\n }\n }\n\n if (!tunnelId) {\n tunnelId = generateTunnelIdFromEmail(email);\n }\n\n if (!tunnelId) return res.status(404).send(\"Tunnel not ready\");\n\n const tunnel = this.tunnels.get(tunnelId);\n\n if (!tunnel) return res.status(404).send(\"Tunnel not found\");\n\n const { ws, approvedViewers, pendingFetches } = tunnel;\n\n if (!approvedViewers.has(email)) {\n return res.status(403).send(\"Not approved for this tunnel\");\n }\n\n const path = req.originalUrl;\n const requestId = randomUUID();\n\n const request = { type: \"fetch\", path, method: req.method, requestId };\n\n const response = await new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n pendingFetches.delete(requestId);\n reject(new Error(\"Asset fetch timeout\"));\n }, MAX_ASSET_FETCH_TIMEOUT);\n\n pendingFetches.set(requestId, { resolve, reject, timeout });\n\n ws.send(JSON.stringify(request));\n }).catch((err) => {\n logger.error(\"Failed to fetch %s: %s\", path, err.message);\n return { status: 504, body: Buffer.from(\"\"), headers: {} };\n });\n\n const r = response as any;\n\n res\n .status(r.status)\n .set(r.headers)\n .send(r.body || Buffer.from(\"\"));\n };\n }\n\n /** Dev mode middleware for the development server. */\n devModeMiddleware() {\n return async (\n req: express.Request,\n res: express.Response,\n next: express.NextFunction,\n ) => {\n const dev = req.query?.dev;\n\n if (dev === undefined) {\n return next();\n }\n\n if (\n req.path.startsWith(\"/api\") ||\n req.path.startsWith(\"/query\") ||\n req.path.match(/\\.(js|css|png|jpg|jpeg|svg|ico|json|woff|woff2|ttf)$/)\n ) {\n return next();\n }\n\n const viewerEmail = req.headers[\"x-forwarded-email\"] as string;\n const isOwnerMode = dev === \"\" || dev === \"true\";\n\n const tunnelId = isOwnerMode\n ? generateTunnelIdFromEmail(viewerEmail)\n : dev.toString();\n\n if (!tunnelId) {\n return res.status(400).send(\"Invalid tunnel ID\");\n }\n\n if (!isOwnerMode) {\n const approvalResponse = this.handleViewerApproval(\n tunnelId,\n viewerEmail,\n req.query.retry === \"true\",\n res,\n );\n\n if (approvalResponse) {\n return approvalResponse;\n }\n }\n\n res.cookie(\"dev-tunnel-id\", tunnelId, {\n httpOnly: false,\n sameSite: \"lax\",\n });\n\n const indexPath = path.join(__dirname, \"index.html\");\n let html = fs.readFileSync(indexPath, \"utf-8\");\n html = html.replace(\"<body>\", `<body>${getConfigScript()}`);\n\n res.send(html);\n };\n }\n\n /** Setup the dev mode middleware. */\n setup(app: express.Application) {\n app.use(this.devModeMiddleware());\n app.use(REMOTE_TUNNEL_ASSET_PREFIXES, this.assetMiddleware());\n }\n\n static isRemoteServerEnabled() {\n return (\n process.env.NODE_ENV !== \"production\" &&\n process.env.DISABLE_REMOTE_SERVING !== \"true\" &&\n // DATABRICKS_CLIENT_SECRET is set in the .env file for deployed environments\n Boolean(process.env.DATABRICKS_CLIENT_SECRET)\n );\n }\n\n private loadHtmlTemplate(\n filename: string,\n replacements: Record<string, string>,\n ): string {\n const filePath = path.join(__dirname, filename);\n let content = fs.readFileSync(filePath, \"utf-8\");\n\n for (const [key, value] of Object.entries(replacements)) {\n content = content.replaceAll(`{{${key}}}`, value);\n }\n\n return content;\n }\n\n private handleViewerApproval(\n tunnelId: string,\n viewerEmail: string,\n retry: boolean,\n res: express.Response,\n ): express.Response | null {\n const tunnel = this.tunnels.get(tunnelId);\n\n if (!tunnel) {\n return res.status(404).send(\"Tunnel not found\");\n }\n\n if (viewerEmail === tunnel.owner) {\n return null;\n }\n\n if (retry) {\n tunnel.rejectedViewers.delete(viewerEmail);\n }\n\n if (tunnel.rejectedViewers.has(viewerEmail)) {\n const html = this.loadHtmlTemplate(\"denied.html\", { tunnelId });\n return res.status(403).send(html);\n }\n\n if (tunnel.approvedViewers.has(viewerEmail)) {\n return null;\n }\n\n if (!tunnel.pendingRequests.has(viewerEmail)) {\n const requestId = randomUUID();\n tunnel.pendingRequests.add(viewerEmail);\n tunnel.ws.send(\n JSON.stringify({\n type: \"connection:request\",\n requestId,\n viewer: viewerEmail,\n }),\n );\n }\n\n const html = this.loadHtmlTemplate(\"wait.html\", { tunnelId });\n return res.status(200).send(html);\n }\n\n setupWebSocket() {\n this.wss.on(\"connection\", (ws, req) => {\n const email = req.headers[\"x-forwarded-email\"] as string;\n const tunnelId = generateTunnelIdFromEmail(email);\n\n if (!tunnelId) return ws.close();\n\n this.tunnels.set(tunnelId, {\n ws,\n owner: email,\n approvedViewers: new Set([email]),\n pendingRequests: new Set(),\n rejectedViewers: new Set(),\n pendingFetches: new Map(),\n pendingFileReads: new Map(),\n waitingForBinaryBody: null,\n });\n\n ws.on(\"message\", (msg, isBinary) => {\n const tunnel = this.tunnels.get(tunnelId);\n if (!tunnel) return;\n\n if (isBinary) {\n if (!tunnel.waitingForBinaryBody) {\n logger.debug(\n \"Received binary message but no requestId is waiting for body\",\n );\n return;\n }\n\n const requestId = tunnel.waitingForBinaryBody;\n const pending = tunnel.pendingFetches.get(requestId);\n\n if (!pending || !pending.metadata) {\n logger.debug(\"Received binary message but pending fetch not found\");\n tunnel.waitingForBinaryBody = null;\n return;\n }\n\n tunnel.waitingForBinaryBody = null;\n clearTimeout(pending.timeout);\n tunnel.pendingFetches.delete(requestId);\n\n pending.resolve({\n status: pending.metadata.status,\n headers: pending.metadata.headers,\n body: msg as Buffer,\n });\n return;\n }\n\n try {\n const data = JSON.parse(msg.toString());\n\n // Validate message structure\n if (!isWebSocketMessage(data)) {\n logger.error(\"Invalid WebSocket message format: %O\", data);\n return;\n }\n\n if (data.type === \"connection:response\") {\n if (tunnel && data.viewer) {\n tunnel.pendingRequests.delete(data.viewer);\n\n if (data.approved) {\n tunnel.approvedViewers.add(data.viewer);\n logger.debug(\n \"✅ Approved %s for tunnel %s\",\n data.viewer,\n tunnelId,\n );\n } else {\n tunnel.rejectedViewers.add(data.viewer);\n logger.debug(\n \"❌ Denied %s for tunnel %s\",\n data.viewer,\n tunnelId,\n );\n }\n }\n } else if (data.type === \"fetch:response:meta\") {\n const pending = tunnel.pendingFetches.get(data.requestId);\n if (pending) {\n pending.metadata = {\n status: data.status,\n headers: data.headers,\n };\n if (\n data.status === 304 ||\n data.status === 204 ||\n (data.status >= 300 && data.status < 400)\n ) {\n clearTimeout(pending.timeout);\n tunnel.pendingFetches.delete(data.requestId);\n pending.resolve({\n status: data.status,\n headers: data.headers,\n body: Buffer.from(\"\"),\n });\n } else {\n tunnel.waitingForBinaryBody = data.requestId;\n }\n }\n } else if (data.type === \"file:read:response\") {\n const pending = tunnel.pendingFileReads.get(data.requestId);\n if (pending) {\n clearTimeout(pending.timeout);\n tunnel.pendingFileReads.delete(data.requestId);\n\n if (data.error) {\n pending.reject(new Error(data.error));\n } else if (data.content !== undefined) {\n pending.resolve(data.content);\n } else {\n pending.reject(\n new Error(\"Missing content in file:read:response\"),\n );\n }\n }\n } else if (data.type === \"dir:list:response\") {\n const pending = tunnel.pendingFileReads.get(data.requestId);\n if (pending) {\n clearTimeout(pending.timeout);\n tunnel.pendingFileReads.delete(data.requestId);\n\n if (data.error) {\n pending.reject(new Error(data.error));\n } else if (data.content !== undefined) {\n pending.resolve(data.content);\n } else {\n pending.reject(\n new Error(\"Missing content in dir:list:response\"),\n );\n }\n }\n }\n } catch (e) {\n logger.error(\"Failed to parse WebSocket message: %O\", e);\n }\n });\n\n ws.send(JSON.stringify({ type: \"tunnel:ready\", tunnelId }));\n\n ws.on(\"close\", () => {\n const tunnel = this.tunnels.get(tunnelId);\n\n if (tunnel) {\n for (const [_, pending] of tunnel.pendingFetches) {\n clearTimeout(pending.timeout);\n pending.reject(new Error(\"Tunnel closed\"));\n }\n tunnel.pendingFetches.clear();\n }\n\n this.tunnels.delete(tunnelId);\n });\n });\n\n this.hmrWss.on(\"connection\", (browserWs, req) => {\n const cookies = parseCookies(req);\n const email = req.headers[\"x-forwarded-email\"] as string;\n const tunnelId =\n cookies[\"dev-tunnel-id\"] || generateTunnelIdFromEmail(email);\n\n if (!tunnelId) return browserWs.close();\n\n const cliTunnel = this.tunnels.get(tunnelId);\n\n if (!cliTunnel) return browserWs.close();\n\n const { ws: cliWs, approvedViewers } = cliTunnel;\n\n if (!approvedViewers.has(email)) {\n return browserWs.close(1008, \"Not approved\");\n }\n // Browser → CLI\n browserWs.on(\"message\", (msg) => {\n const hmrStart = Date.now();\n logger.debug(\"browser -> cli browserWS message: %s\", msg.toString());\n cliWs.send(\n JSON.stringify({\n type: \"hmr:message\",\n body: msg.toString(),\n timestamp: hmrStart,\n }),\n );\n });\n\n // // CLI → Browser\n const cliHandler = (msg: Buffer | string, isBinary: boolean) => {\n // Ignore binary messages (they're for fetch responses, not HMR)\n if (isBinary) return;\n\n try {\n const data = JSON.parse(msg.toString());\n\n if (data.type === \"hmr:message\") {\n browserWs.send(data.body);\n }\n } catch {\n logger.error(\n \"Failed to parse CLI message for HMR: %s\",\n msg.toString().substring(0, 100),\n );\n }\n };\n cliWs.on(\"message\", cliHandler);\n\n browserWs.on(\"close\", () => {\n cliWs.off(\"message\", cliHandler);\n });\n });\n\n // // Browser HMR connection\n this.server?.on(\"upgrade\", (req, socket, head) => {\n const url = req.url ?? \"\";\n\n if (url.startsWith(\"/dev-tunnel\")) {\n this.wss.handleUpgrade(req, socket, head, (ws) => {\n this.wss.emit(\"connection\", ws, req);\n });\n } else if (url.startsWith(\"/dev-hmr\")) {\n this.hmrWss.handleUpgrade(req, socket, head, (browserWs) => {\n this.hmrWss.emit(\"connection\", browserWs, req);\n });\n }\n });\n }\n\n registerTunnelGetter() {\n this.devFileReader.registerTunnelGetter(\n this.getTunnelForRequest.bind(this),\n );\n }\n\n getTunnelForRequest(req: express.Request) {\n const email = req.headers[\"x-forwarded-email\"] as string;\n const cookieHeader = req.headers.cookie;\n\n let tunnelId: string | undefined;\n\n if (cookieHeader) {\n const match = cookieHeader.match(/dev-tunnel-id=([^;]+)/);\n if (match) {\n tunnelId = match[1];\n }\n }\n\n if (!tunnelId) {\n tunnelId = generateTunnelIdFromEmail(email);\n }\n\n return tunnelId ? this.tunnels.get(tunnelId) || null : null;\n }\n\n cleanup() {\n for (const [, tunnel] of this.tunnels) {\n for (const [_, pending] of tunnel.pendingFetches) {\n clearTimeout(pending.timeout);\n pending.reject(new Error(\"Server shutting down\"));\n }\n tunnel.pendingFetches.clear();\n tunnel.ws.close();\n }\n this.tunnels.clear();\n\n if (this.wss) {\n this.wss.close();\n }\n if (this.hmrWss) {\n this.hmrWss.close();\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static-server.js","names":["expressStatic"],"sources":["../../../src/plugins/server/static-server.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type express from \"express\";\nimport expressStatic from \"express\";\nimport { BaseServer } from \"./base-server\";\nimport type { PluginEndpoints } from \"./utils\";\n\n/**\n * Static server for the AppKit.\n *\n * Serves pre-built static files in production mode. Handles SPA routing\n * by serving index.html for non-API routes and injects runtime configuration.\n *\n * @example\n * ```ts\n * const staticServer = new StaticServer(app, staticPath, endpoints);\n * staticServer.setup();\n * ```\n */\nexport class StaticServer extends BaseServer {\n private staticPath: string;\n\n constructor(\n app: express.Application,\n staticPath: string,\n endpoints: PluginEndpoints = {},\n ) {\n super(app, endpoints);\n this.staticPath = staticPath;\n }\n\n /** Setup the static server. */\n setup() {\n this.app.use(\n expressStatic.static(this.staticPath, {\n index: false,\n }),\n );\n\n this.app.get(\"*\", (req, res, next) => {\n if (req.path.startsWith(\"/api\") || req.path.startsWith(\"/query\")) {\n return next();\n }\n this.serveIndex(res);\n });\n }\n\n /** Serve the index.html file. */\n private serveIndex(res: express.Response) {\n const indexPath = path.join(this.staticPath, \"index.html\");\n\n if (!fs.existsSync(indexPath)) {\n res.status(404).send(\"index.html not found\");\n return;\n }\n\n let html = fs.readFileSync(indexPath, \"utf-8\");\n html = html.replace(\"<body>\", `<body>${this.getConfigScript()}`);\n res.send(html);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmBA,IAAa,eAAb,cAAkC,WAAW;CAC3C,AAAQ;CAER,YACE,KACA,YACA,YAA6B,EAAE,EAC/B;AACA,QAAM,KAAK,UAAU;AACrB,OAAK,aAAa;;;CAIpB,QAAQ;AACN,OAAK,IAAI,IACPA,QAAc,OAAO,KAAK,YAAY,EACpC,OAAO,OACR,CAAC,CACH;AAED,OAAK,IAAI,IAAI,MAAM,KAAK,KAAK,SAAS;AACpC,OAAI,IAAI,KAAK,WAAW,OAAO,IAAI,IAAI,KAAK,WAAW,SAAS,CAC9D,QAAO,MAAM;AAEf,QAAK,WAAW,IAAI;IACpB;;;CAIJ,AAAQ,WAAW,KAAuB;EACxC,MAAM,YAAY,KAAK,KAAK,KAAK,YAAY,aAAa;AAE1D,MAAI,CAAC,GAAG,WAAW,UAAU,EAAE;AAC7B,OAAI,OAAO,IAAI,CAAC,KAAK,uBAAuB;AAC5C;;EAGF,IAAI,OAAO,GAAG,aAAa,WAAW,QAAQ;AAC9C,SAAO,KAAK,QAAQ,UAAU,SAAS,KAAK,iBAAiB,GAAG;AAChE,MAAI,KAAK,KAAK"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { BasePluginConfig } from "
|
|
2
|
-
import { Plugin } from "
|
|
1
|
+
import { BasePluginConfig } from "../../shared/src/plugin.js";
|
|
2
|
+
import { Plugin } from "../../plugin/plugin.js";
|
|
3
3
|
|
|
4
|
-
//#region src/server/types.d.ts
|
|
4
|
+
//#region src/plugins/server/types.d.ts
|
|
5
5
|
interface ServerConfig extends BasePluginConfig {
|
|
6
6
|
port?: number;
|
|
7
7
|
plugins?: Record<string, Plugin>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/plugins/server/types.ts"],"sourcesContent":[],"mappings":";;;;UAGiB,YAAA,SAAqB;;EAArB,OAAA,CAAA,EAEL,MAFkB,CAAA,MAAA,EAEH,MAFG,CAAA;EAAA,UAAA,CAAA,EAAA,MAAA;WAEH,CAAA,EAAA,OAAA;MAAf,CAAA,EAAA,MAAA"}
|
|
@@ -2,7 +2,7 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import path$1 from "node:path";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
|
|
5
|
-
//#region src/server/utils.ts
|
|
5
|
+
//#region src/plugins/server/utils.ts
|
|
6
6
|
function parseCookies(req) {
|
|
7
7
|
const cookieHeader = req.headers.cookie;
|
|
8
8
|
if (!cookieHeader) return {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","names":["path"],"sources":["../../../src/plugins/server/utils.ts"],"sourcesContent":["import crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport type http from \"node:http\";\nimport path from \"node:path\";\n\nexport function parseCookies(\n req: http.IncomingMessage,\n): Record<string, string> {\n const cookieHeader = req.headers.cookie;\n if (!cookieHeader) return {};\n\n // Fast path: if there's no semicolon, there's only one cookie\n const semicolonIndex = cookieHeader.indexOf(\";\");\n if (semicolonIndex === -1) {\n const eqIndex = cookieHeader.indexOf(\"=\");\n if (eqIndex === -1) return {};\n return {\n [cookieHeader.slice(0, eqIndex).trim()]: cookieHeader.slice(eqIndex + 1),\n };\n }\n\n // Multiple cookies: parse them all\n const cookies: Record<string, string> = {};\n const parts = cookieHeader.split(\";\");\n for (let i = 0; i < parts.length; i++) {\n const eqIndex = parts[i].indexOf(\"=\");\n if (eqIndex !== -1) {\n const key = parts[i].slice(0, eqIndex).trim();\n const value = parts[i].slice(eqIndex + 1);\n cookies[key] = value;\n }\n }\n return cookies;\n}\n\nexport function generateTunnelIdFromEmail(email?: string): string | undefined {\n if (!email) return undefined;\n\n const tunnelId = crypto\n .createHash(\"sha256\")\n .update(email)\n .digest(\"base64url\")\n .slice(0, 8);\n\n return tunnelId;\n}\n\nexport function getRoutes(stack: unknown[], basePath = \"\") {\n const routes: Array<{ path: string; methods: string[] }> = [];\n\n stack.forEach((layer: any) => {\n if (layer.route) {\n // normal route\n const path = basePath + layer.route.path;\n const methods = Object.keys(layer.route.methods).map((m) =>\n m.toUpperCase(),\n );\n routes.push({ path, methods });\n } else if (layer.name === \"router\" && layer.handle.stack) {\n // nested router\n const nestedBase =\n basePath +\n layer.regexp.source\n .replace(\"^\\\\\", \"\")\n .replace(\"\\\\/?(?=\\\\/|$)\", \"\")\n .replace(/\\\\\\//g, \"/\") // convert escaped slashes\n .replace(/\\$$/, \"\") || \"\";\n routes.push(...getRoutes(layer.handle.stack, nestedBase));\n }\n });\n\n return routes;\n}\n\nexport function getQueries(configFolder: string) {\n const queriesFolder = path.join(configFolder, \"queries\");\n\n if (!fs.existsSync(queriesFolder)) {\n return {};\n }\n\n return Object.fromEntries(\n fs\n .readdirSync(queriesFolder)\n .filter((f) => path.extname(f) === \".sql\")\n .map((f) => [path.basename(f, \".sql\"), path.basename(f, \".sql\")]),\n );\n}\n\nimport type { PluginEndpoints } from \"shared\";\n\nexport type { PluginEndpoints };\n\nexport interface RuntimeConfig {\n appName: string;\n queries: Record<string, string>;\n endpoints: PluginEndpoints;\n}\n\nexport function getRuntimeConfig(\n endpoints: PluginEndpoints = {},\n): RuntimeConfig {\n const configFolder = path.join(process.cwd(), \"config\");\n\n return {\n appName: process.env.DATABRICKS_APP_NAME || \"\",\n queries: getQueries(configFolder),\n endpoints,\n };\n}\n\nexport function getConfigScript(endpoints: PluginEndpoints = {}): string {\n const config = getRuntimeConfig(endpoints);\n\n return `\n <script>\n window.__CONFIG__ = ${JSON.stringify(config)};\n </script>\n `;\n}\n"],"mappings":";;;;;AAKA,SAAgB,aACd,KACwB;CACxB,MAAM,eAAe,IAAI,QAAQ;AACjC,KAAI,CAAC,aAAc,QAAO,EAAE;AAI5B,KADuB,aAAa,QAAQ,IAAI,KACzB,IAAI;EACzB,MAAM,UAAU,aAAa,QAAQ,IAAI;AACzC,MAAI,YAAY,GAAI,QAAO,EAAE;AAC7B,SAAO,GACJ,aAAa,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,aAAa,MAAM,UAAU,EAAE,EACzE;;CAIH,MAAM,UAAkC,EAAE;CAC1C,MAAM,QAAQ,aAAa,MAAM,IAAI;AACrC,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,UAAU,MAAM,GAAG,QAAQ,IAAI;AACrC,MAAI,YAAY,IAAI;GAClB,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC,MAAM;AAE7C,WAAQ,OADM,MAAM,GAAG,MAAM,UAAU,EAAE;;;AAI7C,QAAO;;AAGT,SAAgB,0BAA0B,OAAoC;AAC5E,KAAI,CAAC,MAAO,QAAO;AAQnB,QANiB,OACd,WAAW,SAAS,CACpB,OAAO,MAAM,CACb,OAAO,YAAY,CACnB,MAAM,GAAG,EAAE;;AAKhB,SAAgB,UAAU,OAAkB,WAAW,IAAI;CACzD,MAAM,SAAqD,EAAE;AAE7D,OAAM,SAAS,UAAe;AAC5B,MAAI,MAAM,OAAO;GAEf,MAAM,OAAO,WAAW,MAAM,MAAM;GACpC,MAAM,UAAU,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC,KAAK,MACpD,EAAE,aAAa,CAChB;AACD,UAAO,KAAK;IAAE;IAAM;IAAS,CAAC;aACrB,MAAM,SAAS,YAAY,MAAM,OAAO,OAAO;GAExD,MAAM,aACJ,WACE,MAAM,OAAO,OACV,QAAQ,OAAO,GAAG,CAClB,QAAQ,iBAAiB,GAAG,CAC5B,QAAQ,SAAS,IAAI,CACrB,QAAQ,OAAO,GAAG,IAAI;AAC7B,UAAO,KAAK,GAAG,UAAU,MAAM,OAAO,OAAO,WAAW,CAAC;;GAE3D;AAEF,QAAO;;AAGT,SAAgB,WAAW,cAAsB;CAC/C,MAAM,gBAAgBA,OAAK,KAAK,cAAc,UAAU;AAExD,KAAI,CAAC,GAAG,WAAW,cAAc,CAC/B,QAAO,EAAE;AAGX,QAAO,OAAO,YACZ,GACG,YAAY,cAAc,CAC1B,QAAQ,MAAMA,OAAK,QAAQ,EAAE,KAAK,OAAO,CACzC,KAAK,MAAM,CAACA,OAAK,SAAS,GAAG,OAAO,EAAEA,OAAK,SAAS,GAAG,OAAO,CAAC,CAAC,CACpE;;AAaH,SAAgB,iBACd,YAA6B,EAAE,EAChB;CACf,MAAM,eAAeA,OAAK,KAAK,QAAQ,KAAK,EAAE,SAAS;AAEvD,QAAO;EACL,SAAS,QAAQ,IAAI,uBAAuB;EAC5C,SAAS,WAAW,aAAa;EACjC;EACD;;AAGH,SAAgB,gBAAgB,YAA6B,EAAE,EAAU;CACvE,MAAM,SAAS,iBAAiB,UAAU;AAE1C,QAAO;;4BAEmB,KAAK,UAAU,OAAO,CAAC"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { createLogger } from "
|
|
2
|
-
import { ServerError } from "
|
|
3
|
-
import { init_errors } from "
|
|
4
|
-
import { mergeConfigDedup } from "
|
|
1
|
+
import { createLogger } from "../../logging/logger.js";
|
|
2
|
+
import { ServerError } from "../../errors/server.js";
|
|
3
|
+
import { init_errors } from "../../errors/index.js";
|
|
4
|
+
import { mergeConfigDedup } from "../../utils/vite-config-merge.js";
|
|
5
5
|
import { BaseServer } from "./base-server.js";
|
|
6
|
-
import { appKitTypesPlugin } from "
|
|
6
|
+
import { appKitTypesPlugin } from "../../type-generator/vite-plugin.js";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import fs from "node:fs";
|
|
9
9
|
|
|
10
|
-
//#region src/server/vite-dev-server.ts
|
|
10
|
+
//#region src/plugins/server/vite-dev-server.ts
|
|
11
11
|
init_errors();
|
|
12
12
|
const logger = createLogger("server:vite");
|
|
13
13
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-dev-server.js","names":[],"sources":["../../../src/plugins/server/vite-dev-server.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type express from \"express\";\nimport type { ViteDevServer as ViteDevServerType } from \"vite\";\nimport { mergeConfigDedup } from \"@/utils\";\nimport { ServerError } from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport { appKitTypesPlugin } from \"../../type-generator/vite-plugin\";\nimport { BaseServer } from \"./base-server\";\nimport type { PluginEndpoints } from \"./utils\";\n\nconst logger = createLogger(\"server:vite\");\n\n/**\n * Vite dev server for the AppKit.\n *\n * This class is responsible for serving the Vite dev server for the development server.\n * It also handles the index.html file for the development server.\n *\n * @example\n * ```ts\n * const viteDevServer = new ViteDevServer(app, endpoints);\n * await viteDevServer.setup();\n * ```\n */\nexport class ViteDevServer extends BaseServer {\n private vite: ViteDevServerType | null;\n\n constructor(app: express.Application, endpoints: PluginEndpoints = {}) {\n super(app, endpoints);\n this.vite = null;\n }\n\n /**\n * Setup the Vite dev server.\n *\n * This method sets up the Vite dev server and the index.html file for the development server.\n *\n * @returns\n */\n async setup() {\n const {\n createServer: createViteServer,\n loadConfigFromFile,\n mergeConfig,\n } = await import(\"vite\");\n const react = await import(\"@vitejs/plugin-react\");\n\n const clientRoot = this.findClientRoot();\n\n const loadedConfig = await loadConfigFromFile(\n {\n mode: \"development\",\n command: \"serve\",\n },\n undefined,\n clientRoot,\n );\n\n const userConfig = loadedConfig?.config ?? {};\n const coreConfig = {\n configFile: false,\n root: clientRoot,\n server: {\n middlewareMode: true,\n watch: {\n useFsEvents: true,\n ignored: [\"**/node_modules/**\", \"!**/node_modules/@databricks/**\"],\n },\n },\n plugins: [react.default(), appKitTypesPlugin()],\n appType: \"custom\",\n };\n\n const mergedConfigs = mergeConfigDedup(userConfig, coreConfig, mergeConfig);\n this.vite = await createViteServer(mergedConfigs);\n\n this.app.use(this.vite.middlewares);\n\n this.app.use(\"*\", async (req, res, next) => {\n if (\n req.originalUrl.startsWith(\"/api\") ||\n req.originalUrl.startsWith(\"/query\")\n ) {\n return next();\n }\n const vite = this.vite;\n this.validateVite(vite);\n\n try {\n const indexPath = path.resolve(clientRoot, \"index.html\");\n let html = fs.readFileSync(indexPath, \"utf-8\");\n html = html.replace(\"<body>\", `<body>${this.getConfigScript()}`);\n html = await vite.transformIndexHtml(req.originalUrl, html);\n res.status(200).set({ \"Content-Type\": \"text/html\" }).end(html);\n } catch (e) {\n vite.ssrFixStacktrace(e as Error);\n next(e);\n }\n });\n }\n\n /** Close the Vite dev server. */\n async close() {\n await this.vite?.close();\n }\n\n /** Find the client root. */\n private findClientRoot(): string {\n const cwd = process.cwd();\n const candidates = [\"client\", \"src\", \"app\", \"frontend\", \".\"];\n\n for (const dir of candidates) {\n const fullPath = path.resolve(cwd, dir);\n const hasViteConfig =\n fs.existsSync(path.join(fullPath, \"vite.config.ts\")) ||\n fs.existsSync(path.join(fullPath, \"vite.config.js\"));\n const hasIndexHtml = fs.existsSync(path.join(fullPath, \"index.html\"));\n\n if (hasViteConfig && hasIndexHtml) {\n logger.debug(\"Vite dev server: using client root %s\", fullPath);\n return fullPath;\n }\n }\n\n throw ServerError.clientDirectoryNotFound(candidates);\n }\n\n // type assertion to ensure vite is not null\n private validateVite(\n vite: ViteDevServerType | null,\n ): asserts vite is ViteDevServerType {\n if (!vite) {\n throw ServerError.viteNotInitialized();\n }\n }\n}\n"],"mappings":";;;;;;;;;;aAK2C;AAM3C,MAAM,SAAS,aAAa,cAAc;;;;;;;;;;;;;AAc1C,IAAa,gBAAb,cAAmC,WAAW;CAC5C,AAAQ;CAER,YAAY,KAA0B,YAA6B,EAAE,EAAE;AACrE,QAAM,KAAK,UAAU;AACrB,OAAK,OAAO;;;;;;;;;CAUd,MAAM,QAAQ;EACZ,MAAM,EACJ,cAAc,kBACd,oBACA,gBACE,MAAM,OAAO;EACjB,MAAM,QAAQ,MAAM,OAAO;EAE3B,MAAM,aAAa,KAAK,gBAAgB;AA2BxC,OAAK,OAAO,MAAM,iBADI,kBAxBD,MAAM,mBACzB;GACE,MAAM;GACN,SAAS;GACV,EACD,QACA,WACD,GAEgC,UAAU,EAAE,EAC1B;GACjB,YAAY;GACZ,MAAM;GACN,QAAQ;IACN,gBAAgB;IAChB,OAAO;KACL,aAAa;KACb,SAAS,CAAC,sBAAsB,kCAAkC;KACnE;IACF;GACD,SAAS,CAAC,MAAM,SAAS,EAAE,mBAAmB,CAAC;GAC/C,SAAS;GACV,EAE8D,YAAY,CAC1B;AAEjD,OAAK,IAAI,IAAI,KAAK,KAAK,YAAY;AAEnC,OAAK,IAAI,IAAI,KAAK,OAAO,KAAK,KAAK,SAAS;AAC1C,OACE,IAAI,YAAY,WAAW,OAAO,IAClC,IAAI,YAAY,WAAW,SAAS,CAEpC,QAAO,MAAM;GAEf,MAAM,OAAO,KAAK;AAClB,QAAK,aAAa,KAAK;AAEvB,OAAI;IACF,MAAM,YAAY,KAAK,QAAQ,YAAY,aAAa;IACxD,IAAI,OAAO,GAAG,aAAa,WAAW,QAAQ;AAC9C,WAAO,KAAK,QAAQ,UAAU,SAAS,KAAK,iBAAiB,GAAG;AAChE,WAAO,MAAM,KAAK,mBAAmB,IAAI,aAAa,KAAK;AAC3D,QAAI,OAAO,IAAI,CAAC,IAAI,EAAE,gBAAgB,aAAa,CAAC,CAAC,IAAI,KAAK;YACvD,GAAG;AACV,SAAK,iBAAiB,EAAW;AACjC,SAAK,EAAE;;IAET;;;CAIJ,MAAM,QAAQ;AACZ,QAAM,KAAK,MAAM,OAAO;;;CAI1B,AAAQ,iBAAyB;EAC/B,MAAM,MAAM,QAAQ,KAAK;EACzB,MAAM,aAAa;GAAC;GAAU;GAAO;GAAO;GAAY;GAAI;AAE5D,OAAK,MAAM,OAAO,YAAY;GAC5B,MAAM,WAAW,KAAK,QAAQ,KAAK,IAAI;GACvC,MAAM,gBACJ,GAAG,WAAW,KAAK,KAAK,UAAU,iBAAiB,CAAC,IACpD,GAAG,WAAW,KAAK,KAAK,UAAU,iBAAiB,CAAC;GACtD,MAAM,eAAe,GAAG,WAAW,KAAK,KAAK,UAAU,aAAa,CAAC;AAErE,OAAI,iBAAiB,cAAc;AACjC,WAAO,MAAM,yCAAyC,SAAS;AAC/D,WAAO;;;AAIX,QAAM,YAAY,wBAAwB,WAAW;;CAIvD,AAAQ,aACN,MACmC;AACnC,MAAI,CAAC,KACH,OAAM,YAAY,oBAAoB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry-provider.js","names":[],"sources":["../../src/telemetry/telemetry-provider.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"telemetry-provider.js","names":[],"sources":["../../src/telemetry/telemetry-provider.ts"],"sourcesContent":["import type { Meter, Span, SpanOptions, Tracer } from \"@opentelemetry/api\";\nimport { metrics, trace } from \"@opentelemetry/api\";\nimport { type Logger, type LogRecord, logs } from \"@opentelemetry/api-logs\";\nimport type { Instrumentation } from \"@opentelemetry/instrumentation\";\nimport type { TelemetryOptions } from \"shared\";\nimport {\n normalizeTelemetryOptions,\n type TelemetryProviderConfig,\n} from \"./config\";\nimport { NOOP_LOGGER, NOOP_METER, NOOP_TRACER } from \"./noop\";\nimport type { TelemetryManager } from \"./telemetry-manager\";\nimport type { InstrumentConfig, ITelemetry } from \"./types\";\n\n/**\n * Scoped telemetry instance for specific plugins and other classes.\n * Automatically uses the plugin name as the default tracer/meter name.\n */\nexport class TelemetryProvider implements ITelemetry {\n private readonly pluginName: string;\n private readonly globalManager: TelemetryManager;\n private readonly config: TelemetryProviderConfig;\n\n constructor(\n pluginName: string,\n globalManager: TelemetryManager,\n telemetryConfig?: TelemetryOptions,\n ) {\n this.pluginName = pluginName;\n this.globalManager = globalManager;\n this.config = normalizeTelemetryOptions(telemetryConfig);\n }\n\n /**\n * Gets a tracer for creating spans.\n * @param options - Optional tracer configuration.\n */\n getTracer(options?: InstrumentConfig): Tracer {\n if (!this.config.traces) {\n return NOOP_TRACER;\n }\n\n const tracerName = this.getInstrumentName(options);\n return trace.getTracer(tracerName);\n }\n\n /**\n * Gets a meter for recording metrics.\n * @param config - Optional meter configuration.\n */\n getMeter(config?: InstrumentConfig): Meter {\n if (!this.config.metrics) {\n return NOOP_METER;\n }\n\n const meterName = this.getInstrumentName(config);\n return metrics.getMeter(meterName);\n }\n\n /**\n * Gets a logger for emitting log records.\n * @param config - Optional logger configuration.\n */\n getLogger(config?: InstrumentConfig): Logger {\n if (!this.config.logs) {\n return NOOP_LOGGER;\n }\n\n const loggerName = this.getInstrumentName(config);\n return logs.getLogger(loggerName);\n }\n\n /**\n * Emits a log record using the default logger.\n * Convenience method for logging without explicitly getting a logger.\n * @param logRecord - The log record to emit\n */\n emit(logRecord: LogRecord): void {\n const logger = this.getLogger();\n logger.emit(logRecord);\n }\n\n /**\n * Register OpenTelemetry instrumentations.\n * Can be called at any time, but recommended to call in plugin constructor.\n * @param instrumentations - Array of OpenTelemetry instrumentations to register\n */\n registerInstrumentations(instrumentations: Instrumentation[]): void {\n if (!this.config.traces) {\n return;\n }\n\n this.globalManager.registerInstrumentations(instrumentations);\n }\n\n /**\n * Starts an active span and executes a callback function within its context.\n * Uses the plugin's default tracer unless custom tracer options are provided.\n * @param name - The name of the span\n * @param options - Span options including attributes, kind, etc.\n * @param fn - Callback function to execute within the span context\n * @param tracerOptions - Optional tracer configuration\n * @returns Promise resolving to the callback's return value\n */\n startActiveSpan<T>(\n name: string,\n options: SpanOptions,\n fn: (span: Span) => Promise<T>,\n tracerOptions?: InstrumentConfig,\n ): Promise<T> {\n const tracer = this.getTracer(tracerOptions);\n return tracer.startActiveSpan(name, options, fn);\n }\n\n private getInstrumentName(options?: InstrumentConfig): string {\n const prefix = this.pluginName;\n if (!options || !options.name) {\n return this.pluginName;\n }\n\n return options.includePrefix ? `${prefix}-${options.name}` : options.name;\n }\n}\n"],"mappings":";;;;;;;;;;AAiBA,IAAa,oBAAb,MAAqD;CACnD,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CAEjB,YACE,YACA,eACA,iBACA;AACA,OAAK,aAAa;AAClB,OAAK,gBAAgB;AACrB,OAAK,SAAS,0BAA0B,gBAAgB;;;;;;CAO1D,UAAU,SAAoC;AAC5C,MAAI,CAAC,KAAK,OAAO,OACf,QAAO;EAGT,MAAM,aAAa,KAAK,kBAAkB,QAAQ;AAClD,SAAO,MAAM,UAAU,WAAW;;;;;;CAOpC,SAAS,QAAkC;AACzC,MAAI,CAAC,KAAK,OAAO,QACf,QAAO;EAGT,MAAM,YAAY,KAAK,kBAAkB,OAAO;AAChD,SAAO,QAAQ,SAAS,UAAU;;;;;;CAOpC,UAAU,QAAmC;AAC3C,MAAI,CAAC,KAAK,OAAO,KACf,QAAO;EAGT,MAAM,aAAa,KAAK,kBAAkB,OAAO;AACjD,SAAO,KAAK,UAAU,WAAW;;;;;;;CAQnC,KAAK,WAA4B;AAE/B,EADe,KAAK,WAAW,CACxB,KAAK,UAAU;;;;;;;CAQxB,yBAAyB,kBAA2C;AAClE,MAAI,CAAC,KAAK,OAAO,OACf;AAGF,OAAK,cAAc,yBAAyB,iBAAiB;;;;;;;;;;;CAY/D,gBACE,MACA,SACA,IACA,eACY;AAEZ,SADe,KAAK,UAAU,cAAc,CAC9B,gBAAgB,MAAM,SAAS,GAAG;;CAGlD,AAAQ,kBAAkB,SAAoC;EAC5D,MAAM,SAAS,KAAK;AACpB,MAAI,CAAC,WAAW,CAAC,QAAQ,KACvB,QAAO,KAAK;AAGd,SAAO,QAAQ,gBAAgB,GAAG,OAAO,GAAG,QAAQ,SAAS,QAAQ"}
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="generator" content="Docusaurus v3.9.2">
|
|
6
|
-
<title data-rh="true">Abstract Class: AppKitError | AppKit</title><meta data-rh="true" name="viewport" content="width=device-width,initial-scale=1"><meta data-rh="true" name="twitter:card" content="summary_large_image"><meta data-rh="true" property="og:url" content="https://databricks.github.io/appkit/docs/api/appkit/Class.AppKitError"><meta data-rh="true" property="og:locale" content="en"><meta data-rh="true" name="docusaurus_locale" content="en"><meta data-rh="true" name="docsearch:language" content="en"><meta data-rh="true" name="keywords" content="Databricks Apps, Node.js, React.js, SDK, TypeScript, SQL, Databricks, AI, full-stack, development"><meta data-rh="true" name="docusaurus_version" content="current"><meta data-rh="true" name="docusaurus_tag" content="docs-default-current"><meta data-rh="true" name="docsearch:version" content="current"><meta data-rh="true" name="docsearch:docusaurus_tag" content="docs-default-current"><meta data-rh="true" property="og:title" content="Abstract Class: AppKitError | AppKit"><meta data-rh="true" name="description" content="Base error class for all AppKit errors."><meta data-rh="true" property="og:description" content="Base error class for all AppKit errors."><link data-rh="true" rel="icon" href="/appkit/img/favicon.ico"><link data-rh="true" rel="canonical" href="https://databricks.github.io/appkit/docs/api/appkit/Class.AppKitError"><link data-rh="true" rel="alternate" href="https://databricks.github.io/appkit/docs/api/appkit/Class.AppKitError" hreflang="en"><link data-rh="true" rel="alternate" href="https://databricks.github.io/appkit/docs/api/appkit/Class.AppKitError" hreflang="x-default"><script data-rh="true" type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"API reference","item":"https://databricks.github.io/appkit/docs/api/"},{"@type":"ListItem","position":2,"name":"@databricks/appkit","item":"https://databricks.github.io/appkit/docs/api/appkit/"},{"@type":"ListItem","position":3,"name":"AppKitError","item":"https://databricks.github.io/appkit/docs/api/appkit/Class.AppKitError"}]}</script><link rel="stylesheet" href="/appkit/assets/css/styles.
|
|
7
|
-
<script src="/appkit/assets/js/runtime~main.
|
|
8
|
-
<script src="/appkit/assets/js/main.
|
|
6
|
+
<title data-rh="true">Abstract Class: AppKitError | AppKit</title><meta data-rh="true" name="viewport" content="width=device-width,initial-scale=1"><meta data-rh="true" name="twitter:card" content="summary_large_image"><meta data-rh="true" property="og:url" content="https://databricks.github.io/appkit/docs/api/appkit/Class.AppKitError"><meta data-rh="true" property="og:locale" content="en"><meta data-rh="true" name="docusaurus_locale" content="en"><meta data-rh="true" name="docsearch:language" content="en"><meta data-rh="true" name="keywords" content="Databricks Apps, Node.js, React.js, SDK, TypeScript, SQL, Databricks, AI, full-stack, development"><meta data-rh="true" name="docusaurus_version" content="current"><meta data-rh="true" name="docusaurus_tag" content="docs-default-current"><meta data-rh="true" name="docsearch:version" content="current"><meta data-rh="true" name="docsearch:docusaurus_tag" content="docs-default-current"><meta data-rh="true" property="og:title" content="Abstract Class: AppKitError | AppKit"><meta data-rh="true" name="description" content="Base error class for all AppKit errors."><meta data-rh="true" property="og:description" content="Base error class for all AppKit errors."><link data-rh="true" rel="icon" href="/appkit/img/favicon.ico"><link data-rh="true" rel="canonical" href="https://databricks.github.io/appkit/docs/api/appkit/Class.AppKitError"><link data-rh="true" rel="alternate" href="https://databricks.github.io/appkit/docs/api/appkit/Class.AppKitError" hreflang="en"><link data-rh="true" rel="alternate" href="https://databricks.github.io/appkit/docs/api/appkit/Class.AppKitError" hreflang="x-default"><script data-rh="true" type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"API reference","item":"https://databricks.github.io/appkit/docs/api/"},{"@type":"ListItem","position":2,"name":"@databricks/appkit","item":"https://databricks.github.io/appkit/docs/api/appkit/"},{"@type":"ListItem","position":3,"name":"AppKitError","item":"https://databricks.github.io/appkit/docs/api/appkit/Class.AppKitError"}]}</script><link rel="stylesheet" href="/appkit/assets/css/styles.80363e45.css">
|
|
7
|
+
<script src="/appkit/assets/js/runtime~main.68b5333d.js" defer="defer"></script>
|
|
8
|
+
<script src="/appkit/assets/js/main.40f75854.js" defer="defer"></script>
|
|
9
9
|
</head>
|
|
10
10
|
<body class="navigation-with-keyboard">
|
|
11
11
|
<svg style="display: none;"><defs>
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="generator" content="Docusaurus v3.9.2">
|
|
6
|
-
<title data-rh="true">Class: AuthenticationError | AppKit</title><meta data-rh="true" name="viewport" content="width=device-width,initial-scale=1"><meta data-rh="true" name="twitter:card" content="summary_large_image"><meta data-rh="true" property="og:url" content="https://databricks.github.io/appkit/docs/api/appkit/Class.AuthenticationError"><meta data-rh="true" property="og:locale" content="en"><meta data-rh="true" name="docusaurus_locale" content="en"><meta data-rh="true" name="docsearch:language" content="en"><meta data-rh="true" name="keywords" content="Databricks Apps, Node.js, React.js, SDK, TypeScript, SQL, Databricks, AI, full-stack, development"><meta data-rh="true" name="docusaurus_version" content="current"><meta data-rh="true" name="docusaurus_tag" content="docs-default-current"><meta data-rh="true" name="docsearch:version" content="current"><meta data-rh="true" name="docsearch:docusaurus_tag" content="docs-default-current"><meta data-rh="true" property="og:title" content="Class: AuthenticationError | AppKit"><meta data-rh="true" name="description" content="Error thrown when authentication fails."><meta data-rh="true" property="og:description" content="Error thrown when authentication fails."><link data-rh="true" rel="icon" href="/appkit/img/favicon.ico"><link data-rh="true" rel="canonical" href="https://databricks.github.io/appkit/docs/api/appkit/Class.AuthenticationError"><link data-rh="true" rel="alternate" href="https://databricks.github.io/appkit/docs/api/appkit/Class.AuthenticationError" hreflang="en"><link data-rh="true" rel="alternate" href="https://databricks.github.io/appkit/docs/api/appkit/Class.AuthenticationError" hreflang="x-default"><script data-rh="true" type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"API reference","item":"https://databricks.github.io/appkit/docs/api/"},{"@type":"ListItem","position":2,"name":"@databricks/appkit","item":"https://databricks.github.io/appkit/docs/api/appkit/"},{"@type":"ListItem","position":3,"name":"AuthenticationError","item":"https://databricks.github.io/appkit/docs/api/appkit/Class.AuthenticationError"}]}</script><link rel="stylesheet" href="/appkit/assets/css/styles.
|
|
7
|
-
<script src="/appkit/assets/js/runtime~main.
|
|
8
|
-
<script src="/appkit/assets/js/main.
|
|
6
|
+
<title data-rh="true">Class: AuthenticationError | AppKit</title><meta data-rh="true" name="viewport" content="width=device-width,initial-scale=1"><meta data-rh="true" name="twitter:card" content="summary_large_image"><meta data-rh="true" property="og:url" content="https://databricks.github.io/appkit/docs/api/appkit/Class.AuthenticationError"><meta data-rh="true" property="og:locale" content="en"><meta data-rh="true" name="docusaurus_locale" content="en"><meta data-rh="true" name="docsearch:language" content="en"><meta data-rh="true" name="keywords" content="Databricks Apps, Node.js, React.js, SDK, TypeScript, SQL, Databricks, AI, full-stack, development"><meta data-rh="true" name="docusaurus_version" content="current"><meta data-rh="true" name="docusaurus_tag" content="docs-default-current"><meta data-rh="true" name="docsearch:version" content="current"><meta data-rh="true" name="docsearch:docusaurus_tag" content="docs-default-current"><meta data-rh="true" property="og:title" content="Class: AuthenticationError | AppKit"><meta data-rh="true" name="description" content="Error thrown when authentication fails."><meta data-rh="true" property="og:description" content="Error thrown when authentication fails."><link data-rh="true" rel="icon" href="/appkit/img/favicon.ico"><link data-rh="true" rel="canonical" href="https://databricks.github.io/appkit/docs/api/appkit/Class.AuthenticationError"><link data-rh="true" rel="alternate" href="https://databricks.github.io/appkit/docs/api/appkit/Class.AuthenticationError" hreflang="en"><link data-rh="true" rel="alternate" href="https://databricks.github.io/appkit/docs/api/appkit/Class.AuthenticationError" hreflang="x-default"><script data-rh="true" type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"API reference","item":"https://databricks.github.io/appkit/docs/api/"},{"@type":"ListItem","position":2,"name":"@databricks/appkit","item":"https://databricks.github.io/appkit/docs/api/appkit/"},{"@type":"ListItem","position":3,"name":"AuthenticationError","item":"https://databricks.github.io/appkit/docs/api/appkit/Class.AuthenticationError"}]}</script><link rel="stylesheet" href="/appkit/assets/css/styles.80363e45.css">
|
|
7
|
+
<script src="/appkit/assets/js/runtime~main.68b5333d.js" defer="defer"></script>
|
|
8
|
+
<script src="/appkit/assets/js/main.40f75854.js" defer="defer"></script>
|
|
9
9
|
</head>
|
|
10
10
|
<body class="navigation-with-keyboard">
|
|
11
11
|
<svg style="display: none;"><defs>
|