@databricks/appkit 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +10 -0
- package/NOTICE.md +1 -0
- package/dist/appkit/package.js +1 -1
- package/dist/cli/commands/generate-types.js +15 -13
- package/dist/cli/commands/generate-types.js.map +1 -1
- package/dist/connectors/serving/client.js +47 -0
- package/dist/connectors/serving/client.js.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin/execution-result.d.ts +26 -0
- package/dist/plugin/execution-result.d.ts.map +1 -0
- package/dist/plugin/index.d.ts +1 -0
- package/dist/plugin/interceptors/retry.js +1 -1
- package/dist/plugin/interceptors/retry.js.map +1 -1
- package/dist/plugin/plugin.d.ts +7 -4
- package/dist/plugin/plugin.d.ts.map +1 -1
- package/dist/plugin/plugin.js +36 -5
- package/dist/plugin/plugin.js.map +1 -1
- package/dist/plugins/analytics/analytics.d.ts.map +1 -1
- package/dist/plugins/analytics/analytics.js +2 -3
- package/dist/plugins/analytics/analytics.js.map +1 -1
- package/dist/plugins/files/plugin.d.ts +1 -0
- package/dist/plugins/files/plugin.d.ts.map +1 -1
- package/dist/plugins/files/plugin.js +36 -59
- package/dist/plugins/files/plugin.js.map +1 -1
- package/dist/plugins/index.d.ts +4 -1
- package/dist/plugins/index.js +2 -0
- package/dist/plugins/server/index.d.ts +1 -1
- package/dist/plugins/server/vite-dev-server.js +6 -1
- package/dist/plugins/server/vite-dev-server.js.map +1 -1
- package/dist/plugins/serving/defaults.js +10 -0
- package/dist/plugins/serving/defaults.js.map +1 -0
- package/dist/plugins/serving/index.d.ts +2 -0
- package/dist/plugins/serving/index.js +3 -0
- package/dist/plugins/serving/manifest.js +53 -0
- package/dist/plugins/serving/manifest.js.map +1 -0
- package/dist/plugins/serving/schema-filter.js +52 -0
- package/dist/plugins/serving/schema-filter.js.map +1 -0
- package/dist/plugins/serving/serving.d.ts +38 -0
- package/dist/plugins/serving/serving.d.ts.map +1 -0
- package/dist/plugins/serving/serving.js +213 -0
- package/dist/plugins/serving/serving.js.map +1 -0
- package/dist/plugins/serving/types.d.ts +58 -0
- package/dist/plugins/serving/types.d.ts.map +1 -0
- package/dist/shared/src/execute.d.ts +1 -1
- package/dist/stream/stream-manager.js +1 -0
- package/dist/stream/stream-manager.js.map +1 -1
- package/dist/stream/types.js +2 -1
- package/dist/stream/types.js.map +1 -1
- package/dist/type-generator/cache.js +1 -1
- package/dist/type-generator/cache.js.map +1 -1
- package/dist/type-generator/index.js +3 -1
- package/dist/type-generator/index.js.map +1 -1
- package/dist/type-generator/query-registry.js +77 -4
- package/dist/type-generator/query-registry.js.map +1 -1
- package/dist/type-generator/serving/cache.js +38 -0
- package/dist/type-generator/serving/cache.js.map +1 -0
- package/dist/type-generator/serving/converter.js +108 -0
- package/dist/type-generator/serving/converter.js.map +1 -0
- package/dist/type-generator/serving/fetcher.js +54 -0
- package/dist/type-generator/serving/fetcher.js.map +1 -0
- package/dist/type-generator/serving/generator.js +185 -0
- package/dist/type-generator/serving/generator.js.map +1 -0
- package/dist/type-generator/serving/server-file-extractor.d.ts +22 -0
- package/dist/type-generator/serving/server-file-extractor.d.ts.map +1 -0
- package/dist/type-generator/serving/server-file-extractor.js +131 -0
- package/dist/type-generator/serving/server-file-extractor.js.map +1 -0
- package/dist/type-generator/serving/vite-plugin.d.ts +24 -0
- package/dist/type-generator/serving/vite-plugin.d.ts.map +1 -0
- package/dist/type-generator/serving/vite-plugin.js +60 -0
- package/dist/type-generator/serving/vite-plugin.js.map +1 -0
- package/docs/api/appkit/Class.Plugin.md +8 -3
- package/docs/api/appkit/Function.appKitServingTypesPlugin.md +24 -0
- package/docs/api/appkit/Function.extractServingEndpoints.md +22 -0
- package/docs/api/appkit/Function.findServerFile.md +20 -0
- package/docs/api/appkit/Interface.EndpointConfig.md +23 -0
- package/docs/api/appkit/Interface.ServingEndpointEntry.md +30 -0
- package/docs/api/appkit/Interface.ServingEndpointRegistry.md +3 -0
- package/docs/api/appkit/TypeAlias.ExecutionResult.md +36 -0
- package/docs/api/appkit/TypeAlias.ServingFactory.md +15 -0
- package/docs/api/appkit.md +39 -31
- package/docs/faq.md +66 -0
- package/docs/plugins/serving.md +223 -0
- package/llms.txt +10 -0
- package/package.json +2 -2
- package/sbom.cdx.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -27,6 +27,7 @@ npx @databricks/appkit docs <query>
|
|
|
27
27
|
- [Configuration](./docs/configuration.md): This guide covers environment variables and configuration options for AppKit applications.
|
|
28
28
|
- [Core principles](./docs/core-principles.md): Learn about the fundamental concepts and principles behind AppKit.
|
|
29
29
|
- [Development](./docs/development.md): AppKit provides multiple development workflows to suit different needs: local development with hot reload, AI-assisted development with Agent Skills, and remote tunneling to deployed backends.
|
|
30
|
+
- [FAQ](./docs/faq.md): Integrations
|
|
30
31
|
- [Plugins](./docs/plugins.md): Plugins are modular extensions that add capabilities to your AppKit application. They follow a defined lifecycle and have access to shared services like caching, telemetry, and streaming.
|
|
31
32
|
|
|
32
33
|
## Development
|
|
@@ -50,6 +51,7 @@ npx @databricks/appkit docs <query>
|
|
|
50
51
|
- [Lakebase plugin](./docs/plugins/lakebase.md): Provides a PostgreSQL connection pool for Databricks Lakebase Autoscaling with automatic OAuth token refresh.
|
|
51
52
|
- [Plugin management](./docs/plugins/plugin-management.md): AppKit includes a CLI for managing plugins. All commands are available under npx @databricks/appkit plugin.
|
|
52
53
|
- [Server plugin](./docs/plugins/server.md): Provides HTTP server capabilities with development and production modes.
|
|
54
|
+
- [Serving plugin](./docs/plugins/serving.md): Provides an authenticated proxy to Databricks Model Serving endpoints, with invoke and streaming support.
|
|
53
55
|
|
|
54
56
|
## appkit API reference [collapsed]
|
|
55
57
|
|
|
@@ -67,9 +69,12 @@ npx @databricks/appkit docs <query>
|
|
|
67
69
|
- [Class: ValidationError](./docs/api/appkit/Class.ValidationError.md): Error thrown when input validation fails.
|
|
68
70
|
- [Enumeration: RequestedClaimsPermissionSet](./docs/api/appkit/Enumeration.RequestedClaimsPermissionSet.md): Permission set for Unity Catalog table access
|
|
69
71
|
- [Enumeration: ResourceType](./docs/api/appkit/Enumeration.ResourceType.md): Resource types from schema $defs.resourceType.enum
|
|
72
|
+
- [Function: appKitServingTypesPlugin()](./docs/api/appkit/Function.appKitServingTypesPlugin.md): Vite plugin to generate TypeScript types for AppKit serving endpoints.
|
|
70
73
|
- [Function: appKitTypesPlugin()](./docs/api/appkit/Function.appKitTypesPlugin.md): Vite plugin to generate types for AppKit queries.
|
|
71
74
|
- [Function: createApp()](./docs/api/appkit/Function.createApp.md): Bootstraps AppKit with the provided configuration.
|
|
72
75
|
- [Function: createLakebasePool()](./docs/api/appkit/Function.createLakebasePool.md): Create a Lakebase pool with appkit's logger integration.
|
|
76
|
+
- [Function: extractServingEndpoints()](./docs/api/appkit/Function.extractServingEndpoints.md): Extract serving endpoint config from a server file by AST-parsing it.
|
|
77
|
+
- [Function: findServerFile()](./docs/api/appkit/Function.findServerFile.md): Find the server entry file by checking candidate paths in order.
|
|
73
78
|
- [Function: generateDatabaseCredential()](./docs/api/appkit/Function.generateDatabaseCredential.md): Generate OAuth credentials for Postgres database connection using the proper Postgres API.
|
|
74
79
|
- [Function: getExecutionContext()](./docs/api/appkit/Function.getExecutionContext.md): Get the current execution context.
|
|
75
80
|
- [Function: getLakebaseOrmConfig()](./docs/api/appkit/Function.getLakebaseOrmConfig.md): Get Lakebase connection configuration for ORMs that don't accept pg.Pool directly.
|
|
@@ -82,6 +87,7 @@ npx @databricks/appkit docs <query>
|
|
|
82
87
|
- [Interface: BasePluginConfig](./docs/api/appkit/Interface.BasePluginConfig.md): Base configuration interface for AppKit plugins
|
|
83
88
|
- [Interface: CacheConfig](./docs/api/appkit/Interface.CacheConfig.md): Configuration for the CacheInterceptor. Controls TTL, size limits, storage backend, and probabilistic cleanup.
|
|
84
89
|
- [Interface: DatabaseCredential](./docs/api/appkit/Interface.DatabaseCredential.md): Database credentials with OAuth token for Postgres connection
|
|
90
|
+
- [Interface: EndpointConfig](./docs/api/appkit/Interface.EndpointConfig.md): Properties
|
|
85
91
|
- [Interface: GenerateDatabaseCredentialRequest](./docs/api/appkit/Interface.GenerateDatabaseCredentialRequest.md): Request parameters for generating database OAuth credentials
|
|
86
92
|
- [Interface: ITelemetry](./docs/api/appkit/Interface.ITelemetry.md): Plugin-facing interface for OpenTelemetry instrumentation.
|
|
87
93
|
- [Interface: LakebasePoolConfig](./docs/api/appkit/Interface.LakebasePoolConfig.md): Configuration for creating a Lakebase connection pool
|
|
@@ -91,13 +97,17 @@ npx @databricks/appkit docs <query>
|
|
|
91
97
|
- [Interface: ResourceEntry](./docs/api/appkit/Interface.ResourceEntry.md): Internal representation of a resource in the registry.
|
|
92
98
|
- [Interface: ResourceFieldEntry](./docs/api/appkit/Interface.ResourceFieldEntry.md): Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instancename, databasename or scope, key).
|
|
93
99
|
- [Interface: ResourceRequirement](./docs/api/appkit/Interface.ResourceRequirement.md): Declares a resource requirement for a plugin.
|
|
100
|
+
- [Interface: ServingEndpointEntry](./docs/api/appkit/Interface.ServingEndpointEntry.md): Shape of a single registry entry.
|
|
101
|
+
- [Interface: ServingEndpointRegistry](./docs/api/appkit/Interface.ServingEndpointRegistry.md): Registry interface for serving endpoint type generation.
|
|
94
102
|
- [Interface: StreamExecutionSettings](./docs/api/appkit/Interface.StreamExecutionSettings.md): Execution settings for streaming endpoints. Extends PluginExecutionSettings with SSE stream configuration.
|
|
95
103
|
- [Interface: TelemetryConfig](./docs/api/appkit/Interface.TelemetryConfig.md): OpenTelemetry configuration for AppKit applications
|
|
96
104
|
- [Interface: ValidationResult](./docs/api/appkit/Interface.ValidationResult.md): Result of validating all registered resources against the environment.
|
|
97
105
|
- [Type Alias: ConfigSchema](./docs/api/appkit/TypeAlias.ConfigSchema.md): Configuration schema definition for plugin config.
|
|
106
|
+
- [Type Alias: ExecutionResult<T>](./docs/api/appkit/TypeAlias.ExecutionResult.md): Discriminated union for plugin execution results.
|
|
98
107
|
- [Type Alias: IAppRouter](./docs/api/appkit/TypeAlias.IAppRouter.md): Express router type for plugin route registration
|
|
99
108
|
- [Type Alias: PluginData<T, U, N>](./docs/api/appkit/TypeAlias.PluginData.md): Tuple of plugin class, config, and name. Created by toPlugin() and passed to createApp().
|
|
100
109
|
- [Type Alias: ResourcePermission](./docs/api/appkit/TypeAlias.ResourcePermission.md): Union of all possible permission levels across all resource types.
|
|
110
|
+
- [Type Alias: ServingFactory](./docs/api/appkit/TypeAlias.ServingFactory.md): Factory function returned by AppKit.serving.
|
|
101
111
|
- [Type Alias: ToPlugin()<T, U, N>](./docs/api/appkit/TypeAlias.ToPlugin.md): Factory function type returned by toPlugin(). Accepts optional config and returns a PluginData tuple.
|
|
102
112
|
- [Variable: sql](./docs/api/appkit/Variable.sql.md): SQL helper namespace
|
|
103
113
|
|
package/NOTICE.md
CHANGED
|
@@ -58,6 +58,7 @@ This Software contains code from the following open source projects:
|
|
|
58
58
|
| [clsx](https://www.npmjs.com/package/clsx) | 2.1.1 | MIT | https://github.com/lukeed/clsx#readme |
|
|
59
59
|
| [cmdk](https://www.npmjs.com/package/cmdk) | 1.1.1 | MIT | https://github.com/pacocoursey/cmdk#readme |
|
|
60
60
|
| [commander](https://www.npmjs.com/package/commander) | 2.20.3, 5.1.0, 7.2.0, 8.3.0, 10.0.1, 12.1.0 | MIT | https://github.com/tj/commander.js#readme |
|
|
61
|
+
| [dompurify](https://www.npmjs.com/package/dompurify) | 3.3.3 | (MPL-2.0 OR Apache-2.0) | https://github.com/cure53/DOMPurify |
|
|
61
62
|
| [dotenv](https://www.npmjs.com/package/dotenv) | 16.6.1 | BSD-2-Clause | https://github.com/motdotla/dotenv#readme |
|
|
62
63
|
| [echarts](https://www.npmjs.com/package/echarts) | 6.0.0 | Apache-2.0 | https://echarts.apache.org |
|
|
63
64
|
| [echarts-for-react](https://www.npmjs.com/package/echarts-for-react) | 3.0.5 | MIT | https://github.com/hustcc/echarts-for-react |
|
package/dist/appkit/package.js
CHANGED
|
@@ -8,21 +8,23 @@ import { Command } from "commander";
|
|
|
8
8
|
*/
|
|
9
9
|
async function runGenerateTypes(rootDir, outFile, warehouseId, options) {
|
|
10
10
|
try {
|
|
11
|
-
const resolvedWarehouseId = warehouseId || process.env.DATABRICKS_WAREHOUSE_ID;
|
|
12
|
-
if (!resolvedWarehouseId) process.exit(0);
|
|
13
|
-
const { generateFromEntryPoint } = await import("@databricks/appkit/type-generator");
|
|
14
11
|
const resolvedRootDir = rootDir || process.cwd();
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
const noCache = options?.noCache || false;
|
|
13
|
+
const typeGen = await import("@databricks/appkit/type-generator");
|
|
14
|
+
const resolvedWarehouseId = warehouseId || process.env.DATABRICKS_WAREHOUSE_ID;
|
|
15
|
+
if (resolvedWarehouseId) {
|
|
16
|
+
const resolvedOutFile = outFile || path.join(process.cwd(), "client/src/appKitTypes.d.ts");
|
|
17
|
+
const queryFolder = path.join(resolvedRootDir, "config/queries");
|
|
18
|
+
if (fs.existsSync(queryFolder)) await typeGen.generateFromEntryPoint({
|
|
19
|
+
queryFolder,
|
|
20
|
+
outFile: resolvedOutFile,
|
|
21
|
+
warehouseId: resolvedWarehouseId,
|
|
22
|
+
noCache
|
|
23
|
+
});
|
|
20
24
|
}
|
|
21
|
-
await
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
warehouseId: resolvedWarehouseId,
|
|
25
|
-
noCache: options?.noCache || false
|
|
25
|
+
await typeGen.generateServingTypes({
|
|
26
|
+
outFile: path.join(process.cwd(), "client/src/appKitServingTypes.d.ts"),
|
|
27
|
+
noCache
|
|
26
28
|
});
|
|
27
29
|
} catch (error) {
|
|
28
30
|
if (error instanceof Error && error.message.includes("Cannot find module")) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-types.js","names":[],"sources":["../../../src/cli/commands/generate-types.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Command } from \"commander\";\n\n/**\n * Generate types command implementation\n */\nasync function runGenerateTypes(\n rootDir?: string,\n outFile?: string,\n warehouseId?: string,\n options?: { noCache?: boolean },\n) {\n try {\n const
|
|
1
|
+
{"version":3,"file":"generate-types.js","names":[],"sources":["../../../src/cli/commands/generate-types.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Command } from \"commander\";\n\n/**\n * Generate types command implementation\n */\nasync function runGenerateTypes(\n rootDir?: string,\n outFile?: string,\n warehouseId?: string,\n options?: { noCache?: boolean },\n) {\n try {\n const resolvedRootDir = rootDir || process.cwd();\n const noCache = options?.noCache || false;\n\n const typeGen = await import(\"@databricks/appkit/type-generator\");\n\n // Generate analytics query types (requires warehouse ID)\n const resolvedWarehouseId =\n warehouseId || process.env.DATABRICKS_WAREHOUSE_ID;\n\n if (resolvedWarehouseId) {\n const resolvedOutFile =\n outFile || path.join(process.cwd(), \"client/src/appKitTypes.d.ts\");\n\n const queryFolder = path.join(resolvedRootDir, \"config/queries\");\n if (fs.existsSync(queryFolder)) {\n await typeGen.generateFromEntryPoint({\n queryFolder,\n outFile: resolvedOutFile,\n warehouseId: resolvedWarehouseId,\n noCache,\n });\n }\n }\n\n // Generate serving endpoint types (no warehouse required)\n await typeGen.generateServingTypes({\n outFile: path.join(process.cwd(), \"client/src/appKitServingTypes.d.ts\"),\n noCache,\n });\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes(\"Cannot find module\")\n ) {\n console.error(\n \"Error: The 'generate-types' command is only available in @databricks/appkit.\",\n );\n console.error(\"Please install @databricks/appkit to use this command.\");\n process.exit(1);\n }\n throw error;\n }\n}\n\nexport const generateTypesCommand = new Command(\"generate-types\")\n .description(\"Generate TypeScript types from SQL queries\")\n .argument(\"[rootDir]\", \"Root directory of the project\", process.cwd())\n .argument(\n \"[outFile]\",\n \"Output file path\",\n path.join(process.cwd(), \"client/src/appKitTypes.d.ts\"),\n )\n .argument(\"[warehouseId]\", \"Databricks warehouse ID\")\n .option(\"--no-cache\", \"Disable caching for type generation\")\n .action(runGenerateTypes);\n"],"mappings":";;;;;;;;AAOA,eAAe,iBACb,SACA,SACA,aACA,SACA;AACA,KAAI;EACF,MAAM,kBAAkB,WAAW,QAAQ,KAAK;EAChD,MAAM,UAAU,SAAS,WAAW;EAEpC,MAAM,UAAU,MAAM,OAAO;EAG7B,MAAM,sBACJ,eAAe,QAAQ,IAAI;AAE7B,MAAI,qBAAqB;GACvB,MAAM,kBACJ,WAAW,KAAK,KAAK,QAAQ,KAAK,EAAE,8BAA8B;GAEpE,MAAM,cAAc,KAAK,KAAK,iBAAiB,iBAAiB;AAChE,OAAI,GAAG,WAAW,YAAY,CAC5B,OAAM,QAAQ,uBAAuB;IACnC;IACA,SAAS;IACT,aAAa;IACb;IACD,CAAC;;AAKN,QAAM,QAAQ,qBAAqB;GACjC,SAAS,KAAK,KAAK,QAAQ,KAAK,EAAE,qCAAqC;GACvE;GACD,CAAC;UACK,OAAO;AACd,MACE,iBAAiB,SACjB,MAAM,QAAQ,SAAS,qBAAqB,EAC5C;AACA,WAAQ,MACN,+EACD;AACD,WAAQ,MAAM,yDAAyD;AACvE,WAAQ,KAAK,EAAE;;AAEjB,QAAM;;;AAIV,MAAa,uBAAuB,IAAI,QAAQ,iBAAiB,CAC9D,YAAY,6CAA6C,CACzD,SAAS,aAAa,iCAAiC,QAAQ,KAAK,CAAC,CACrE,SACC,aACA,oBACA,KAAK,KAAK,QAAQ,KAAK,EAAE,8BAA8B,CACxD,CACA,SAAS,iBAAiB,0BAA0B,CACpD,OAAO,cAAc,sCAAsC,CAC3D,OAAO,iBAAiB"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createLogger } from "../../logging/logger.js";
|
|
2
|
+
|
|
3
|
+
//#region src/connectors/serving/client.ts
|
|
4
|
+
const logger = createLogger("connectors:serving");
|
|
5
|
+
/**
|
|
6
|
+
* Invokes a serving endpoint using the SDK's high-level query API.
|
|
7
|
+
* Returns a typed QueryEndpointResponse.
|
|
8
|
+
*/
|
|
9
|
+
async function invoke(client, endpointName, body) {
|
|
10
|
+
const { stream: _stream, ...cleanBody } = body;
|
|
11
|
+
logger.debug("Invoking endpoint %s", endpointName);
|
|
12
|
+
return client.servingEndpoints.query({
|
|
13
|
+
name: endpointName,
|
|
14
|
+
...cleanBody
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Returns the raw SSE byte stream from a serving endpoint.
|
|
19
|
+
* No parsing is performed — bytes are passed through as-is.
|
|
20
|
+
*
|
|
21
|
+
* Uses the SDK's low-level `apiClient.request({ raw: true })` because
|
|
22
|
+
* the high-level `servingEndpoints.query()` returns `Promise<QueryEndpointResponse>`
|
|
23
|
+
* and does not support SSE streaming.
|
|
24
|
+
*/
|
|
25
|
+
async function stream(client, endpointName, body) {
|
|
26
|
+
const { stream: _stream, ...cleanBody } = body;
|
|
27
|
+
logger.debug("Streaming from endpoint %s", endpointName);
|
|
28
|
+
const response = await client.apiClient.request({
|
|
29
|
+
path: `/serving-endpoints/${encodeURIComponent(endpointName)}/invocations`,
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: new Headers({
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
Accept: "text/event-stream"
|
|
34
|
+
}),
|
|
35
|
+
payload: {
|
|
36
|
+
...cleanBody,
|
|
37
|
+
stream: true
|
|
38
|
+
},
|
|
39
|
+
raw: true
|
|
40
|
+
});
|
|
41
|
+
if (!response.contents) throw new Error("Response body is null — streaming not supported");
|
|
42
|
+
return response.contents;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { invoke, stream };
|
|
47
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../../../src/connectors/serving/client.ts"],"sourcesContent":["import type { serving, WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport { createLogger } from \"../../logging/logger\";\n\nconst logger = createLogger(\"connectors:serving\");\n\n/**\n * Invokes a serving endpoint using the SDK's high-level query API.\n * Returns a typed QueryEndpointResponse.\n */\nexport async function invoke(\n client: WorkspaceClient,\n endpointName: string,\n body: Record<string, unknown>,\n): Promise<serving.QueryEndpointResponse> {\n // Strip `stream` from the body — the connector controls this\n const { stream: _stream, ...cleanBody } = body;\n\n logger.debug(\"Invoking endpoint %s\", endpointName);\n\n return client.servingEndpoints.query({\n name: endpointName,\n ...cleanBody,\n } as serving.QueryEndpointInput);\n}\n\n/**\n * Returns the raw SSE byte stream from a serving endpoint.\n * No parsing is performed — bytes are passed through as-is.\n *\n * Uses the SDK's low-level `apiClient.request({ raw: true })` because\n * the high-level `servingEndpoints.query()` returns `Promise<QueryEndpointResponse>`\n * and does not support SSE streaming.\n */\nexport async function stream(\n client: WorkspaceClient,\n endpointName: string,\n body: Record<string, unknown>,\n): Promise<ReadableStream<Uint8Array>> {\n const { stream: _stream, ...cleanBody } = body;\n\n logger.debug(\"Streaming from endpoint %s\", endpointName);\n\n const response = (await client.apiClient.request({\n path: `/serving-endpoints/${encodeURIComponent(endpointName)}/invocations`,\n method: \"POST\",\n headers: new Headers({\n \"Content-Type\": \"application/json\",\n Accept: \"text/event-stream\",\n }),\n payload: { ...cleanBody, stream: true },\n raw: true,\n })) as { contents: ReadableStream<Uint8Array> };\n\n if (!response.contents) {\n throw new Error(\"Response body is null — streaming not supported\");\n }\n\n return response.contents;\n}\n"],"mappings":";;;AAGA,MAAM,SAAS,aAAa,qBAAqB;;;;;AAMjD,eAAsB,OACpB,QACA,cACA,MACwC;CAExC,MAAM,EAAE,QAAQ,SAAS,GAAG,cAAc;AAE1C,QAAO,MAAM,wBAAwB,aAAa;AAElD,QAAO,OAAO,iBAAiB,MAAM;EACnC,MAAM;EACN,GAAG;EACJ,CAA+B;;;;;;;;;;AAWlC,eAAsB,OACpB,QACA,cACA,MACqC;CACrC,MAAM,EAAE,QAAQ,SAAS,GAAG,cAAc;AAE1C,QAAO,MAAM,8BAA8B,aAAa;CAExD,MAAM,WAAY,MAAM,OAAO,UAAU,QAAQ;EAC/C,MAAM,sBAAsB,mBAAmB,aAAa,CAAC;EAC7D,QAAQ;EACR,SAAS,IAAI,QAAQ;GACnB,gBAAgB;GAChB,QAAQ;GACT,CAAC;EACF,SAAS;GAAE,GAAG;GAAW,QAAQ;GAAM;EACvC,KAAK;EACN,CAAC;AAEF,KAAI,CAAC,SAAS,SACZ,OAAM,IAAI,MAAM,kDAAkD;AAGpE,QAAO,SAAS"}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { InitializationError } from "./errors/initialization.js";
|
|
|
21
21
|
import { ServerError } from "./errors/server.js";
|
|
22
22
|
import { TunnelError } from "./errors/tunnel.js";
|
|
23
23
|
import { ValidationError } from "./errors/validation.js";
|
|
24
|
+
import { ExecutionResult } from "./plugin/execution-result.js";
|
|
24
25
|
import { Plugin } from "./plugin/plugin.js";
|
|
25
26
|
import { toPlugin } from "./plugin/to-plugin.js";
|
|
26
27
|
import "./plugin/index.js";
|
|
@@ -34,6 +35,10 @@ import { files } from "./plugins/files/plugin.js";
|
|
|
34
35
|
import { genie } from "./plugins/genie/genie.js";
|
|
35
36
|
import { lakebase } from "./plugins/lakebase/lakebase.js";
|
|
36
37
|
import { server } from "./plugins/server/index.js";
|
|
38
|
+
import { EndpointConfig, ServingEndpointEntry, ServingEndpointRegistry, ServingFactory } from "./plugins/serving/types.js";
|
|
39
|
+
import { serving } from "./plugins/serving/serving.js";
|
|
37
40
|
import "./plugins/index.js";
|
|
41
|
+
import { extractServingEndpoints, findServerFile } from "./type-generator/serving/server-file-extractor.js";
|
|
42
|
+
import { appKitServingTypesPlugin } from "./type-generator/serving/vite-plugin.js";
|
|
38
43
|
import { appKitTypesPlugin } from "./type-generator/vite-plugin.js";
|
|
39
|
-
export { AppKitError, AuthenticationError, type BasePluginConfig, type CacheConfig, CacheManager, type ConfigSchema, ConfigurationError, ConnectionError, type Counter, type DatabaseCredential, ExecutionError, type GenerateDatabaseCredentialRequest, type Histogram, type IAppRouter, type ITelemetry, InitializationError, type LakebasePoolConfig, Plugin, type PluginData, type PluginManifest, type RequestedClaims, RequestedClaimsPermissionSet, type RequestedResource, type ResourceEntry, type ResourceFieldEntry, type ResourcePermission, ResourceRegistry, type ResourceRequirement, ResourceType, ServerError, SeverityNumber, type Span, SpanStatusCode, type StreamExecutionSettings, type TelemetryConfig, type ToPlugin, TunnelError, ValidationError, type ValidationResult, analytics, appKitTypesPlugin, createApp, createLakebasePool, files, generateDatabaseCredential, genie, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUsernameWithApiLookup, getWorkspaceClient, isSQLTypeMarker, lakebase, server, sql, toPlugin };
|
|
44
|
+
export { AppKitError, AuthenticationError, type BasePluginConfig, type CacheConfig, CacheManager, type ConfigSchema, ConfigurationError, ConnectionError, type Counter, type DatabaseCredential, type EndpointConfig, ExecutionError, type ExecutionResult, type GenerateDatabaseCredentialRequest, type Histogram, type IAppRouter, type ITelemetry, InitializationError, type LakebasePoolConfig, Plugin, type PluginData, type PluginManifest, type RequestedClaims, RequestedClaimsPermissionSet, type RequestedResource, type ResourceEntry, type ResourceFieldEntry, type ResourcePermission, ResourceRegistry, type ResourceRequirement, ResourceType, ServerError, type ServingEndpointEntry, type ServingEndpointRegistry, type ServingFactory, SeverityNumber, type Span, SpanStatusCode, type StreamExecutionSettings, type TelemetryConfig, type ToPlugin, TunnelError, ValidationError, type ValidationResult, analytics, appKitServingTypesPlugin, appKitTypesPlugin, createApp, createLakebasePool, extractServingEndpoints, files, findServerFile, generateDatabaseCredential, genie, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUsernameWithApiLookup, getWorkspaceClient, isSQLTypeMarker, lakebase, server, serving, sql, toPlugin };
|
package/dist/index.js
CHANGED
|
@@ -27,8 +27,11 @@ import { analytics } from "./plugins/analytics/analytics.js";
|
|
|
27
27
|
import { files } from "./plugins/files/plugin.js";
|
|
28
28
|
import { genie } from "./plugins/genie/genie.js";
|
|
29
29
|
import { lakebase } from "./plugins/lakebase/lakebase.js";
|
|
30
|
+
import { extractServingEndpoints, findServerFile } from "./type-generator/serving/server-file-extractor.js";
|
|
31
|
+
import { appKitServingTypesPlugin } from "./type-generator/serving/vite-plugin.js";
|
|
30
32
|
import { appKitTypesPlugin } from "./type-generator/vite-plugin.js";
|
|
31
33
|
import { server } from "./plugins/server/index.js";
|
|
34
|
+
import { serving } from "./plugins/serving/serving.js";
|
|
32
35
|
import "./plugins/index.js";
|
|
33
36
|
|
|
34
37
|
//#region src/index.ts
|
|
@@ -36,5 +39,5 @@ init_context();
|
|
|
36
39
|
init_errors();
|
|
37
40
|
|
|
38
41
|
//#endregion
|
|
39
|
-
export { AppKitError, AuthenticationError, CacheManager, ConfigurationError, ConnectionError, ExecutionError, InitializationError, Plugin, RequestedClaimsPermissionSet, ResourceRegistry, ResourceType, ServerError, SeverityNumber, SpanStatusCode, TunnelError, ValidationError, analytics, appKitTypesPlugin, createApp, createLakebasePool, files, generateDatabaseCredential, genie, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUsernameWithApiLookup, getWorkspaceClient, isSQLTypeMarker, lakebase, server, sql, toPlugin };
|
|
42
|
+
export { AppKitError, AuthenticationError, CacheManager, ConfigurationError, ConnectionError, ExecutionError, InitializationError, Plugin, RequestedClaimsPermissionSet, ResourceRegistry, ResourceType, ServerError, SeverityNumber, SpanStatusCode, TunnelError, ValidationError, analytics, appKitServingTypesPlugin, appKitTypesPlugin, createApp, createLakebasePool, extractServingEndpoints, files, findServerFile, generateDatabaseCredential, genie, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUsernameWithApiLookup, getWorkspaceClient, isSQLTypeMarker, lakebase, server, serving, sql, toPlugin };
|
|
40
43
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @packageDocumentation\n *\n * Core library for building Databricks applications with type-safe SQL queries,\n * plugin architecture, and React integration.\n */\n\n// Types from shared\nexport type {\n BasePluginConfig,\n CacheConfig,\n IAppRouter,\n PluginData,\n StreamExecutionSettings,\n} from \"shared\";\nexport { isSQLTypeMarker, sql } from \"shared\";\nexport { CacheManager } from \"./cache\";\nexport type {\n DatabaseCredential,\n GenerateDatabaseCredentialRequest,\n LakebasePoolConfig,\n RequestedClaims,\n RequestedResource,\n} from \"./connectors/lakebase\";\n// Lakebase Autoscaling connector\nexport {\n createLakebasePool,\n generateDatabaseCredential,\n getLakebaseOrmConfig,\n getLakebasePgConfig,\n getUsernameWithApiLookup,\n getWorkspaceClient,\n RequestedClaimsPermissionSet,\n} from \"./connectors/lakebase\";\nexport { getExecutionContext } from \"./context\";\nexport { createApp } from \"./core\";\n// Errors\nexport {\n AppKitError,\n AuthenticationError,\n ConfigurationError,\n ConnectionError,\n ExecutionError,\n InitializationError,\n ServerError,\n TunnelError,\n ValidationError,\n} from \"./errors\";\n// Plugin authoring\nexport { Plugin
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @packageDocumentation\n *\n * Core library for building Databricks applications with type-safe SQL queries,\n * plugin architecture, and React integration.\n */\n\n// Types from shared\nexport type {\n BasePluginConfig,\n CacheConfig,\n IAppRouter,\n PluginData,\n StreamExecutionSettings,\n} from \"shared\";\nexport { isSQLTypeMarker, sql } from \"shared\";\nexport { CacheManager } from \"./cache\";\nexport type {\n DatabaseCredential,\n GenerateDatabaseCredentialRequest,\n LakebasePoolConfig,\n RequestedClaims,\n RequestedResource,\n} from \"./connectors/lakebase\";\n// Lakebase Autoscaling connector\nexport {\n createLakebasePool,\n generateDatabaseCredential,\n getLakebaseOrmConfig,\n getLakebasePgConfig,\n getUsernameWithApiLookup,\n getWorkspaceClient,\n RequestedClaimsPermissionSet,\n} from \"./connectors/lakebase\";\nexport { getExecutionContext } from \"./context\";\nexport { createApp } from \"./core\";\n// Errors\nexport {\n AppKitError,\n AuthenticationError,\n ConfigurationError,\n ConnectionError,\n ExecutionError,\n InitializationError,\n ServerError,\n TunnelError,\n ValidationError,\n} from \"./errors\";\n// Plugin authoring\nexport {\n type ExecutionResult,\n Plugin,\n type ToPlugin,\n toPlugin,\n} from \"./plugin\";\nexport { analytics, files, genie, lakebase, server, serving } from \"./plugins\";\nexport type {\n EndpointConfig,\n ServingEndpointEntry,\n ServingEndpointRegistry,\n ServingFactory,\n} from \"./plugins/serving/types\";\n// Registry types and utilities for plugin manifests\nexport type {\n ConfigSchema,\n PluginManifest,\n ResourceEntry,\n ResourceFieldEntry,\n ResourcePermission,\n ResourceRequirement,\n ValidationResult,\n} from \"./registry\";\nexport {\n getPluginManifest,\n getResourceRequirements,\n ResourceRegistry,\n ResourceType,\n} from \"./registry\";\n// Telemetry (for advanced custom telemetry)\nexport {\n type Counter,\n type Histogram,\n type ITelemetry,\n SeverityNumber,\n type Span,\n SpanStatusCode,\n type TelemetryConfig,\n} from \"./telemetry\";\nexport {\n extractServingEndpoints,\n findServerFile,\n} from \"./type-generator/serving/server-file-extractor\";\nexport { appKitServingTypesPlugin } from \"./type-generator/serving/vite-plugin\";\n// Vite plugin and type generation\nexport { appKitTypesPlugin } from \"./type-generator/vite-plugin\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkCgD;aAa9B"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/plugin/execution-result.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Discriminated union for plugin execution results.
|
|
4
|
+
*
|
|
5
|
+
* Replaces the previous `T | undefined` return type on `execute()`.
|
|
6
|
+
*
|
|
7
|
+
* On failure, the HTTP status code is preserved from:
|
|
8
|
+
* - `AppKitError` subclasses (via `statusCode`)
|
|
9
|
+
* - Any `Error` with a numeric `statusCode` property (e.g. `ApiError`)
|
|
10
|
+
* - All other errors default to status 500
|
|
11
|
+
*
|
|
12
|
+
* In production, error messages from non-AppKitError sources are handled as:
|
|
13
|
+
* - 4xx errors: original message is preserved (client-facing by design)
|
|
14
|
+
* - 5xx errors: replaced with "Server error" to prevent information leakage
|
|
15
|
+
*/
|
|
16
|
+
type ExecutionResult<T> = {
|
|
17
|
+
ok: true;
|
|
18
|
+
data: T;
|
|
19
|
+
} | {
|
|
20
|
+
ok: false;
|
|
21
|
+
status: number;
|
|
22
|
+
message: string;
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export { ExecutionResult };
|
|
26
|
+
//# sourceMappingURL=execution-result.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execution-result.d.ts","names":[],"sources":["../../src/plugin/execution-result.ts"],"mappings":";;AAcA;;;;;;;;;;;;;KAAY,eAAA;EACN,EAAA;EAAU,IAAA,EAAM,CAAA;AAAA;EAChB,EAAA;EAAW,MAAA;EAAgB,OAAA;AAAA"}
|
package/dist/plugin/index.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ var RetryInterceptor = class {
|
|
|
31
31
|
}
|
|
32
32
|
calculateDelay(attempt) {
|
|
33
33
|
const delay = this.initialDelay * 2 ** (attempt - 1);
|
|
34
|
-
return Math.min(delay, this.maxDelay);
|
|
34
|
+
return Math.min(delay, this.maxDelay) * Math.random();
|
|
35
35
|
}
|
|
36
36
|
sleep(ms) {
|
|
37
37
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.js","names":[],"sources":["../../../src/plugin/interceptors/retry.ts"],"sourcesContent":["import type { RetryConfig } from \"shared\";\nimport { createLogger } from \"../../logging/logger\";\nimport type { ExecutionInterceptor, InterceptorContext } from \"./types\";\n\nconst logger = createLogger(\"interceptors:retry\");\n\n// interceptor to handle retry logic\nexport class RetryInterceptor implements ExecutionInterceptor {\n private attempts: number;\n private initialDelay: number;\n private maxDelay: number;\n\n constructor(config: RetryConfig) {\n this.attempts = config.attempts ?? 3;\n this.initialDelay = config.initialDelay ?? 1000;\n this.maxDelay = config.maxDelay ?? 30000;\n }\n\n async intercept<T>(\n fn: () => Promise<T>,\n context: InterceptorContext,\n ): Promise<T> {\n let lastError: Error | unknown;\n\n for (let attempt = 1; attempt <= this.attempts; attempt++) {\n try {\n const result = await fn();\n\n if (attempt > 1) {\n logger.event()?.setExecution({\n retry_attempts: attempt - 1,\n });\n }\n\n return result;\n } catch (error) {\n lastError = error;\n\n // last attempt, rethrow the error\n if (attempt === this.attempts) {\n logger.event()?.setExecution({\n retry_attempts: attempt - 1,\n });\n throw error;\n }\n\n // don't retry if was already aborted\n if (context.signal?.aborted) {\n throw error;\n }\n\n const delay = this.calculateDelay(attempt);\n await this.sleep(delay);\n }\n }\n\n // type guard\n throw lastError;\n }\n\n private calculateDelay(attempt: number): number {\n
|
|
1
|
+
{"version":3,"file":"retry.js","names":[],"sources":["../../../src/plugin/interceptors/retry.ts"],"sourcesContent":["import type { RetryConfig } from \"shared\";\nimport { createLogger } from \"../../logging/logger\";\nimport type { ExecutionInterceptor, InterceptorContext } from \"./types\";\n\nconst logger = createLogger(\"interceptors:retry\");\n\n// interceptor to handle retry logic\nexport class RetryInterceptor implements ExecutionInterceptor {\n private attempts: number;\n private initialDelay: number;\n private maxDelay: number;\n\n constructor(config: RetryConfig) {\n this.attempts = config.attempts ?? 3;\n this.initialDelay = config.initialDelay ?? 1000;\n this.maxDelay = config.maxDelay ?? 30000;\n }\n\n async intercept<T>(\n fn: () => Promise<T>,\n context: InterceptorContext,\n ): Promise<T> {\n let lastError: Error | unknown;\n\n for (let attempt = 1; attempt <= this.attempts; attempt++) {\n try {\n const result = await fn();\n\n if (attempt > 1) {\n logger.event()?.setExecution({\n retry_attempts: attempt - 1,\n });\n }\n\n return result;\n } catch (error) {\n lastError = error;\n\n // last attempt, rethrow the error\n if (attempt === this.attempts) {\n logger.event()?.setExecution({\n retry_attempts: attempt - 1,\n });\n throw error;\n }\n\n // don't retry if was already aborted\n if (context.signal?.aborted) {\n throw error;\n }\n\n const delay = this.calculateDelay(attempt);\n await this.sleep(delay);\n }\n }\n\n // type guard\n throw lastError;\n }\n\n private calculateDelay(attempt: number): number {\n const delay = this.initialDelay * 2 ** (attempt - 1);\n const capped = Math.min(delay, this.maxDelay);\n\n return capped * Math.random();\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"],"mappings":";;;AAIA,MAAM,SAAS,aAAa,qBAAqB;AAGjD,IAAa,mBAAb,MAA8D;CAC5D,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,YAAY,QAAqB;AAC/B,OAAK,WAAW,OAAO,YAAY;AACnC,OAAK,eAAe,OAAO,gBAAgB;AAC3C,OAAK,WAAW,OAAO,YAAY;;CAGrC,MAAM,UACJ,IACA,SACY;EACZ,IAAI;AAEJ,OAAK,IAAI,UAAU,GAAG,WAAW,KAAK,UAAU,UAC9C,KAAI;GACF,MAAM,SAAS,MAAM,IAAI;AAEzB,OAAI,UAAU,EACZ,QAAO,OAAO,EAAE,aAAa,EAC3B,gBAAgB,UAAU,GAC3B,CAAC;AAGJ,UAAO;WACA,OAAO;AACd,eAAY;AAGZ,OAAI,YAAY,KAAK,UAAU;AAC7B,WAAO,OAAO,EAAE,aAAa,EAC3B,gBAAgB,UAAU,GAC3B,CAAC;AACF,UAAM;;AAIR,OAAI,QAAQ,QAAQ,QAClB,OAAM;GAGR,MAAM,QAAQ,KAAK,eAAe,QAAQ;AAC1C,SAAM,KAAK,MAAM,MAAM;;AAK3B,QAAM;;CAGR,AAAQ,eAAe,SAAyB;EAC9C,MAAM,QAAQ,KAAK,eAAe,MAAM,UAAU;AAGlD,SAFe,KAAK,IAAI,OAAO,KAAK,SAAS,GAE7B,KAAK,QAAQ;;CAG/B,AAAQ,MAAM,IAA2B;AACvC,SAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC"}
|
package/dist/plugin/plugin.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import "../shared/src/index.js";
|
|
|
4
4
|
import { CacheManager } from "../cache/index.js";
|
|
5
5
|
import { ITelemetry } from "../telemetry/types.js";
|
|
6
6
|
import "../telemetry/index.js";
|
|
7
|
+
import { ExecutionResult } from "./execution-result.js";
|
|
7
8
|
import { AppManager } from "../app/index.js";
|
|
8
9
|
import { StreamManager } from "../stream/stream-manager.js";
|
|
9
10
|
import "../stream/index.js";
|
|
@@ -226,11 +227,13 @@ declare abstract class Plugin<TConfig extends BasePluginConfig = BasePluginConfi
|
|
|
226
227
|
/**
|
|
227
228
|
* Execute a function with the plugin's interceptor chain.
|
|
228
229
|
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
230
|
+
* Returns an {@link ExecutionResult} discriminated union:
|
|
231
|
+
* - `{ ok: true, data: T }` on success
|
|
232
|
+
* - `{ ok: false, status: number, message: string }` on failure
|
|
233
|
+
*
|
|
234
|
+
* Errors are never thrown — the method is production-safe.
|
|
232
235
|
*/
|
|
233
|
-
protected execute<T>(fn: (signal?: AbortSignal) => Promise<T>, options: PluginExecutionSettings, userKey?: string): Promise<T
|
|
236
|
+
protected execute<T>(fn: (signal?: AbortSignal) => Promise<T>, options: PluginExecutionSettings, userKey?: string): Promise<ExecutionResult<T>>;
|
|
234
237
|
protected registerEndpoint(name: string, path: string): void;
|
|
235
238
|
protected route<_TResponse>(router: express.Router, config: RouteConfig): void;
|
|
236
239
|
private _buildExecutionConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","names":[],"sources":["../../src/plugin/plugin.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","names":[],"sources":["../../src/plugin/plugin.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;AAgKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAAsB,MAAA,iBACJ,gBAAA,GAAmB,gBAAA,aACxB,UAAA;EAAA,UA4BW,MAAA,EAAQ,OAAA;EAAA,UA1BpB,OAAA;EAAA,UACA,KAAA,EAAO,YAAA;EAAA,UACP,GAAA,EAAK,UAAA;EAAA,UACL,aAAA,EAAe,aAAA;EAAA,UACf,aAAA,EAAe,aAAA;EAAA,UACf,SAAA,EAAW,UAAA;EA2NL;EAAA,QAxNR,mBAAA;EAyND;EAAA,QAtNC,oBAAA;EAuNF;;;;;;EAAA,OA/MC,KAAA,EAAO,WAAA;EAmRE;;;EA9QhB,IAAA;cAEsB,MAAA,EAAQ,OAAA;EAc9B,YAAA,CAAa,CAAA,EAAG,OAAA,CAAQ,MAAA;EAIlB,KAAA,CAAA,GAAK,OAAA;EAEX,YAAA,CAAA,GAAgB,iBAAA;EAIhB,uBAAA,CAAA,GAA2B,WAAA;EAI3B,qBAAA,CAAA;EAoPG;;;;;;;;;;;;;;;;;;;;;;;;EAxNH,OAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiDA,YAAA,CAAA,GAAgB,MAAA;;;;;;;;;;;YAcN,aAAA,CAAc,GAAA,EAAK,OAAA,CAAQ,OAAA;;;;;;;;;;;EAmBrC,MAAA,CAAO,GAAA,EAAK,OAAA,CAAQ,OAAA;;;;;;UAuCZ,uBAAA;EAAA,UAqBQ,aAAA,GAAA,CACd,GAAA,EAAK,YAAA,EACL,EAAA,EAAI,oBAAA,CAAqB,CAAA,GACzB,OAAA,EAAS,uBAAA,EACT,OAAA,YAAgB,OAAA;;;;;;;;;;YAkEF,OAAA,GAAA,CACd,EAAA,GAAK,MAAA,GAAS,WAAA,KAAgB,OAAA,CAAQ,CAAA,GACtC,OAAA,EAAS,uBAAA,EACT,OAAA,YACC,OAAA,CAAQ,eAAA,CAAgB,CAAA;EAAA,UAmDjB,gBAAA,CAAiB,IAAA,UAAc,IAAA;EAAA,UAI/B,KAAA,YAAA,CACR,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAChB,MAAA,EAAQ,WAAA;EAAA,QAeF,qBAAA;EAAA,QAaA,kBAAA;EAAA,QAqCM,wBAAA;EAAA,QAqBN,iBAAA;AAAA"}
|
package/dist/plugin/plugin.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createLogger } from "../logging/logger.js";
|
|
2
|
+
import { AppKitError } from "../errors/base.js";
|
|
2
3
|
import { AuthenticationError } from "../errors/authentication.js";
|
|
3
4
|
import { init_errors } from "../errors/index.js";
|
|
4
5
|
import { normalizeTelemetryOptions } from "../telemetry/config.js";
|
|
@@ -23,6 +24,13 @@ init_context();
|
|
|
23
24
|
init_errors();
|
|
24
25
|
const logger = createLogger("plugin");
|
|
25
26
|
/**
|
|
27
|
+
* Narrow an unknown thrown value to an Error that carries a numeric
|
|
28
|
+
* `statusCode` property (e.g. `ApiError` from `@databricks/sdk-experimental`).
|
|
29
|
+
*/
|
|
30
|
+
function hasHttpStatusCode(error) {
|
|
31
|
+
return error instanceof Error && "statusCode" in error && typeof error.statusCode === "number";
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
26
34
|
* Methods that should not be proxied by asUser().
|
|
27
35
|
* These are lifecycle/internal methods that don't make sense
|
|
28
36
|
* to execute in a user context.
|
|
@@ -321,9 +329,11 @@ var Plugin = class {
|
|
|
321
329
|
/**
|
|
322
330
|
* Execute a function with the plugin's interceptor chain.
|
|
323
331
|
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
332
|
+
* Returns an {@link ExecutionResult} discriminated union:
|
|
333
|
+
* - `{ ok: true, data: T }` on success
|
|
334
|
+
* - `{ ok: false, status: number, message: string }` on failure
|
|
335
|
+
*
|
|
336
|
+
* Errors are never thrown — the method is production-safe.
|
|
327
337
|
*/
|
|
328
338
|
async execute(fn, options, userKey) {
|
|
329
339
|
const executeConfig = this._buildExecutionConfig(options);
|
|
@@ -334,13 +344,34 @@ var Plugin = class {
|
|
|
334
344
|
userKey: effectiveUserKey
|
|
335
345
|
};
|
|
336
346
|
try {
|
|
337
|
-
return
|
|
347
|
+
return {
|
|
348
|
+
ok: true,
|
|
349
|
+
data: await this._executeWithInterceptors(fn, interceptors, context)
|
|
350
|
+
};
|
|
338
351
|
} catch (error) {
|
|
339
352
|
logger.error("Plugin execution failed", {
|
|
340
353
|
error,
|
|
341
354
|
plugin: this.name
|
|
342
355
|
});
|
|
343
|
-
return
|
|
356
|
+
if (error instanceof AppKitError) return {
|
|
357
|
+
ok: false,
|
|
358
|
+
status: error.statusCode,
|
|
359
|
+
message: error.message
|
|
360
|
+
};
|
|
361
|
+
if (hasHttpStatusCode(error)) {
|
|
362
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
363
|
+
const isClientError = error.statusCode >= 400 && error.statusCode < 500;
|
|
364
|
+
return {
|
|
365
|
+
ok: false,
|
|
366
|
+
status: error.statusCode,
|
|
367
|
+
message: isDev || isClientError ? error.message : "Server error"
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
ok: false,
|
|
372
|
+
status: 500,
|
|
373
|
+
message: process.env.NODE_ENV !== "production" && error instanceof Error ? error.message : "Server error"
|
|
374
|
+
};
|
|
344
375
|
}
|
|
345
376
|
}
|
|
346
377
|
registerEndpoint(name, path) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","names":[],"sources":["../../src/plugin/plugin.ts"],"sourcesContent":["import type express from \"express\";\nimport type {\n BasePlugin,\n BasePluginConfig,\n IAppResponse,\n PluginEndpointMap,\n PluginExecuteConfig,\n PluginExecutionSettings,\n PluginPhase,\n RouteConfig,\n StreamExecuteHandler,\n StreamExecutionSettings,\n} from \"shared\";\nimport { AppManager } from \"../app\";\nimport { CacheManager } from \"../cache\";\nimport {\n getCurrentUserId,\n runInUserContext,\n ServiceContext,\n type UserContext,\n} from \"../context\";\nimport { AuthenticationError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\nimport { StreamManager } from \"../stream\";\nimport {\n type ITelemetry,\n normalizeTelemetryOptions,\n TelemetryManager,\n} from \"../telemetry\";\nimport { deepMerge } from \"../utils\";\nimport { DevFileReader } from \"./dev-reader\";\nimport { CacheInterceptor } from \"./interceptors/cache\";\nimport { RetryInterceptor } from \"./interceptors/retry\";\nimport { TelemetryInterceptor } from \"./interceptors/telemetry\";\nimport { TimeoutInterceptor } from \"./interceptors/timeout\";\nimport type {\n ExecutionInterceptor,\n InterceptorContext,\n} from \"./interceptors/types\";\n\nconst logger = createLogger(\"plugin\");\n\n/**\n * Methods that should not be proxied by asUser().\n * These are lifecycle/internal methods that don't make sense\n * to execute in a user context.\n */\nconst EXCLUDED_FROM_PROXY = new Set([\n // Lifecycle methods\n \"setup\",\n \"shutdown\",\n \"injectRoutes\",\n \"getEndpoints\",\n \"getSkipBodyParsingPaths\",\n \"abortActiveOperations\",\n \"clientConfig\",\n // asUser itself - prevent chaining like .asUser().asUser()\n \"asUser\",\n // Internal methods\n \"constructor\",\n]);\n\n/**\n * Base abstract class for creating AppKit plugins.\n *\n * All plugins must declare a static `manifest` property with their metadata\n * and resource requirements. The manifest defines:\n * - `required` resources: Always needed for the plugin to function\n * - `optional` resources: May be needed depending on plugin configuration\n *\n * ## Static vs Runtime Resource Requirements\n *\n * The manifest is static and doesn't know the plugin's runtime configuration.\n * For resources that become required based on config options, plugins can\n * implement a static `getResourceRequirements(config)` method.\n *\n * At runtime, this method is called with the actual config to determine\n * which \"optional\" resources should be treated as \"required\".\n *\n * @example Basic plugin with static requirements\n * ```typescript\n * import { Plugin, toPlugin, PluginManifest, ResourceType } from '@databricks/appkit';\n *\n * const myManifest: PluginManifest = {\n * name: 'myPlugin',\n * displayName: 'My Plugin',\n * description: 'Does something awesome',\n * resources: {\n * required: [\n * { type: ResourceType.SQL_WAREHOUSE, alias: 'warehouse', ... }\n * ],\n * optional: []\n * }\n * };\n *\n * class MyPlugin extends Plugin<MyConfig> {\n * static manifest = myManifest;\n * }\n * ```\n *\n * @example Plugin with config-dependent resources\n * ```typescript\n * interface MyConfig extends BasePluginConfig {\n * enableCaching?: boolean;\n * }\n *\n * const myManifest: PluginManifest = {\n * name: 'myPlugin',\n * resources: {\n * required: [\n * { type: ResourceType.SQL_WAREHOUSE, alias: 'warehouse', ... }\n * ],\n * optional: [\n * // Database is optional in the static manifest\n * { type: ResourceType.DATABASE, alias: 'cache', description: 'Required if caching enabled', ... }\n * ]\n * }\n * };\n *\n * class MyPlugin extends Plugin<MyConfig> {\n * static manifest = myManifest<\"myPlugin\">;\n *\n * // Runtime method: converts optional resources to required based on config\n * static getResourceRequirements(config: MyConfig) {\n * const resources = [];\n * if (config.enableCaching) {\n * // When caching is enabled, Database becomes required\n * resources.push({\n * type: ResourceType.DATABASE,\n * alias: 'cache',\n * resourceKey: 'database',\n * description: 'Cache storage for query results',\n * permission: 'CAN_CONNECT_AND_CREATE',\n * fields: {\n * instance_name: { env: 'DATABRICKS_CACHE_INSTANCE' },\n * database_name: { env: 'DATABRICKS_CACHE_DB' },\n * },\n * required: true // Mark as required at runtime\n * });\n * }\n * return resources;\n * }\n * }\n * ```\n */\nexport abstract class Plugin<\n TConfig extends BasePluginConfig = BasePluginConfig,\n> implements BasePlugin\n{\n protected isReady = false;\n protected cache: CacheManager;\n protected app: AppManager;\n protected devFileReader: DevFileReader;\n protected streamManager: StreamManager;\n protected telemetry: ITelemetry;\n\n /** Registered endpoints for this plugin */\n private registeredEndpoints: PluginEndpointMap = {};\n\n /** Paths that opt out of JSON body parsing (e.g. file upload routes) */\n private skipBodyParsingPaths: Set<string> = new Set();\n\n /**\n * Plugin initialization phase.\n * - 'core': Initialized first (e.g., config plugins)\n * - 'normal': Initialized second (most plugins)\n * - 'deferred': Initialized last (e.g., server plugin)\n */\n static phase: PluginPhase = \"normal\";\n\n /**\n * Plugin name identifier.\n */\n name: string;\n\n constructor(protected config: TConfig) {\n this.name =\n config.name ??\n (this.constructor as { manifest?: { name: string } }).manifest?.name ??\n \"plugin\";\n this.telemetry = TelemetryManager.getProvider(this.name, config.telemetry);\n this.streamManager = new StreamManager();\n this.cache = CacheManager.getInstanceSync();\n this.app = new AppManager();\n this.devFileReader = DevFileReader.getInstance();\n\n this.isReady = true;\n }\n\n injectRoutes(_: express.Router) {\n return;\n }\n\n async setup() {}\n\n getEndpoints(): PluginEndpointMap {\n return this.registeredEndpoints;\n }\n\n getSkipBodyParsingPaths(): ReadonlySet<string> {\n return this.skipBodyParsingPaths;\n }\n\n abortActiveOperations(): void {\n this.streamManager.abortAll();\n }\n\n /**\n * Returns the public exports for this plugin.\n * Override this to define a custom public API.\n * By default, returns an empty object.\n *\n * The returned object becomes the plugin's public API on the AppKit instance\n * (e.g. `appkit.myPlugin.method()`). AppKit automatically binds method context\n * and adds `asUser(req)` for user-scoped execution.\n *\n * @example\n * ```ts\n * class MyPlugin extends Plugin {\n * private getData() { return []; }\n *\n * exports() {\n * return { getData: this.getData };\n * }\n * }\n *\n * // After registration:\n * const appkit = await createApp({ plugins: [myPlugin()] });\n * appkit.myPlugin.getData();\n * ```\n */\n exports(): unknown {\n return {};\n }\n\n /**\n * Returns startup config to expose to the client.\n * Override this to surface server-side values that are safe to publish to the\n * frontend, such as feature flags, resource IDs, or other app boot settings.\n *\n * This runs once when the server starts, so it should not depend on\n * request-scoped or user-specific state.\n *\n * String values that match non-public environment variables are redacted\n * unless you intentionally expose them via a matching `PUBLIC_APPKIT_` env var.\n *\n * Values must be JSON-serializable plain data (no functions, Dates, classes,\n * Maps, Sets, BigInts, or circular references).\n * By default returns an empty object (plugin contributes nothing to client config).\n *\n * On the client, read the config with the `usePluginClientConfig` hook\n * (React) or the `getPluginClientConfig` function (vanilla JS), both\n * from `@databricks/appkit-ui`.\n *\n * @example\n * ```ts\n * // Server — plugin definition\n * class MyPlugin extends Plugin<MyConfig> {\n * clientConfig() {\n * return {\n * warehouseId: this.config.warehouseId,\n * features: { darkMode: true },\n * };\n * }\n * }\n *\n * // Client — React component\n * import { usePluginClientConfig } from \"@databricks/appkit-ui/react\";\n *\n * interface MyPluginConfig { warehouseId: string; features: { darkMode: boolean } }\n *\n * const config = usePluginClientConfig<MyPluginConfig>(\"myPlugin\");\n * config.warehouseId; // \"abc-123\"\n *\n * // Client — vanilla JS\n * import { getPluginClientConfig } from \"@databricks/appkit-ui/js\";\n *\n * const config = getPluginClientConfig<MyPluginConfig>(\"myPlugin\");\n * ```\n */\n clientConfig(): Record<string, unknown> {\n return {};\n }\n\n /**\n * Resolve the effective user ID from a request.\n *\n * Returns the `x-forwarded-user` header when present. In development mode\n * (`NODE_ENV=development`) falls back to the current context user ID so\n * that callers outside an active `runInUserContext` scope still get a\n * consistent value.\n *\n * @throws AuthenticationError in production when no user header is present.\n */\n protected resolveUserId(req: express.Request): string {\n const userId = req.header(\"x-forwarded-user\");\n if (userId) return userId;\n if (process.env.NODE_ENV === \"development\") return getCurrentUserId();\n throw AuthenticationError.missingToken(\n \"Missing x-forwarded-user header. Cannot resolve user ID.\",\n );\n }\n\n /**\n * Execute operations using the user's identity from the request.\n * Returns a proxy of this plugin where all method calls execute\n * with the user's Databricks credentials instead of the service principal.\n *\n * @param req - The Express request containing the user token in headers\n * @returns A proxied plugin instance that executes as the user\n * @throws AuthenticationError if user token is not available in request headers (production only).\n * In development mode (`NODE_ENV=development`), skips user impersonation instead of throwing.\n */\n asUser(req: express.Request): this {\n const token = req.header(\"x-forwarded-access-token\");\n const userId = req.header(\"x-forwarded-user\");\n const isDev = process.env.NODE_ENV === \"development\";\n\n // In local development, skip user impersonation\n // since there's no user token available\n if (!token && isDev) {\n logger.warn(\n \"asUser() called without user token in development mode. Skipping user impersonation.\",\n );\n\n return this;\n }\n\n if (!token) {\n throw AuthenticationError.missingToken(\"user token\");\n }\n\n if (!userId && !isDev) {\n throw AuthenticationError.missingUserId();\n }\n\n const effectiveUserId = userId || \"dev-user\";\n\n const userContext = ServiceContext.createUserContext(\n token,\n effectiveUserId,\n );\n\n // Return a proxy that wraps method calls in user context\n return this._createUserContextProxy(userContext);\n }\n\n /**\n * Creates a proxy that wraps method calls in a user context.\n * This allows all plugin methods to automatically use the user's\n * Databricks credentials.\n */\n private _createUserContextProxy(userContext: UserContext): this {\n return new Proxy(this, {\n get: (target, prop, receiver) => {\n const value = Reflect.get(target, prop, receiver);\n\n if (typeof value !== \"function\") {\n return value;\n }\n\n if (typeof prop === \"string\" && EXCLUDED_FROM_PROXY.has(prop)) {\n return value;\n }\n\n return (...args: unknown[]) => {\n return runInUserContext(userContext, () => value.apply(target, args));\n };\n },\n }) as this;\n }\n\n // streaming execution with interceptors\n protected async executeStream<T>(\n res: IAppResponse,\n fn: StreamExecuteHandler<T>,\n options: StreamExecutionSettings,\n userKey?: string,\n ) {\n // destructure options\n const {\n stream: streamConfig,\n default: defaultConfig,\n user: userConfig,\n } = options;\n\n // build execution options\n const executeConfig = this._buildExecutionConfig({\n default: defaultConfig,\n user: userConfig,\n });\n\n // get user key from context if not provided\n const effectiveUserKey = userKey ?? getCurrentUserId();\n\n const self = this;\n\n // wrapper function to ensure it returns a generator\n const asyncWrapperFn = async function* (streamSignal?: AbortSignal) {\n // build execution context\n const context: InterceptorContext = {\n signal: streamSignal,\n metadata: new Map(),\n userKey: effectiveUserKey,\n };\n\n // build interceptors\n const interceptors = self._buildInterceptors(executeConfig);\n\n // wrap the function to ensure it returns a promise\n const wrappedFn = async () => {\n const result = await fn(context.signal);\n return result;\n };\n\n // execute the function with interceptors\n const result = await self._executeWithInterceptors(\n wrappedFn as (signal?: AbortSignal) => Promise<T>,\n interceptors,\n context,\n );\n\n // check if result is a generator\n if (self._checkIfGenerator(result)) {\n yield* result;\n } else {\n yield result;\n }\n };\n\n // stream the result to the client\n await this.streamManager.stream(res, asyncWrapperFn, streamConfig);\n }\n\n /**\n * Execute a function with the plugin's interceptor chain.\n *\n * All errors are caught and `undefined` is returned (production-safe).\n * Route handlers should check for `undefined` and respond with an\n * appropriate error status.\n */\n protected async execute<T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n options: PluginExecutionSettings,\n userKey?: string,\n ): Promise<T | undefined> {\n const executeConfig = this._buildExecutionConfig(options);\n\n const interceptors = this._buildInterceptors(executeConfig);\n\n // get user key from context if not provided\n const effectiveUserKey = userKey ?? getCurrentUserId();\n\n const context: InterceptorContext = {\n metadata: new Map(),\n userKey: effectiveUserKey,\n };\n\n try {\n return await this._executeWithInterceptors(fn, interceptors, context);\n } catch (error) {\n // production-safe: swallow all errors, don't crash the app\n logger.error(\"Plugin execution failed\", { error, plugin: this.name });\n return undefined;\n }\n }\n\n protected registerEndpoint(name: string, path: string): void {\n this.registeredEndpoints[name] = path;\n }\n\n protected route<_TResponse>(\n router: express.Router,\n config: RouteConfig,\n ): void {\n const { name, method, path, handler } = config;\n\n router[method](path, handler);\n\n const fullPath = `/api/${this.name}${path}`;\n this.registerEndpoint(name, fullPath);\n\n if (config.skipBodyParsing) {\n this.skipBodyParsingPaths.add(fullPath);\n }\n }\n\n // build execution options by merging defaults, plugin config, and user overrides\n private _buildExecutionConfig(\n options: PluginExecutionSettings,\n ): PluginExecuteConfig {\n const { default: methodDefaults, user: userOverride } = options;\n\n // Merge: method defaults <- plugin config <- user override (highest priority)\n return deepMerge(\n deepMerge(methodDefaults, this.config),\n userOverride ?? {},\n ) as PluginExecuteConfig;\n }\n\n // build interceptors based on execute options\n private _buildInterceptors(\n options: PluginExecuteConfig,\n ): ExecutionInterceptor[] {\n const interceptors: ExecutionInterceptor[] = [];\n\n // order matters: telemetry → timeout → retry → cache (innermost to outermost)\n\n const telemetryConfig = normalizeTelemetryOptions(this.config.telemetry);\n if (\n telemetryConfig.traces &&\n (options.telemetryInterceptor?.enabled ?? true)\n ) {\n interceptors.push(\n new TelemetryInterceptor(this.telemetry, options.telemetryInterceptor),\n );\n }\n\n if (options.timeout && options.timeout > 0) {\n interceptors.push(new TimeoutInterceptor(options.timeout));\n }\n\n if (\n options.retry?.enabled &&\n options.retry.attempts &&\n options.retry.attempts > 1\n ) {\n interceptors.push(new RetryInterceptor(options.retry));\n }\n\n if (options.cache?.enabled && options.cache.cacheKey?.length) {\n interceptors.push(new CacheInterceptor(this.cache, options.cache));\n }\n\n return interceptors;\n }\n\n // execute method wrapped with interceptors\n private async _executeWithInterceptors<T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n interceptors: ExecutionInterceptor[],\n context: InterceptorContext,\n ): Promise<T> {\n // no interceptors, execute directly\n if (interceptors.length === 0) {\n return fn(context.signal);\n }\n // build nested execution chain from interceptors\n let wrappedFn = () => fn(context.signal);\n\n // wrap each interceptor around the previous function\n for (const interceptor of interceptors) {\n const previousFn = wrappedFn;\n wrappedFn = () => interceptor.intercept(previousFn, context);\n }\n\n return wrappedFn();\n }\n\n private _checkIfGenerator(\n result: any,\n ): result is AsyncGenerator<any, void, unknown> {\n return (\n result && typeof result === \"object\" && Symbol.asyncIterator in result\n );\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.js","names":[],"sources":["../../src/plugin/plugin.ts"],"sourcesContent":["import type express from \"express\";\nimport type {\n BasePlugin,\n BasePluginConfig,\n IAppResponse,\n PluginEndpointMap,\n PluginExecuteConfig,\n PluginExecutionSettings,\n PluginPhase,\n RouteConfig,\n StreamExecuteHandler,\n StreamExecutionSettings,\n} from \"shared\";\nimport { AppManager } from \"../app\";\nimport { CacheManager } from \"../cache\";\nimport {\n getCurrentUserId,\n runInUserContext,\n ServiceContext,\n type UserContext,\n} from \"../context\";\nimport { AppKitError, AuthenticationError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\nimport { StreamManager } from \"../stream\";\nimport {\n type ITelemetry,\n normalizeTelemetryOptions,\n TelemetryManager,\n} from \"../telemetry\";\nimport { deepMerge } from \"../utils\";\nimport { DevFileReader } from \"./dev-reader\";\nimport type { ExecutionResult } from \"./execution-result\";\nimport { CacheInterceptor } from \"./interceptors/cache\";\nimport { RetryInterceptor } from \"./interceptors/retry\";\nimport { TelemetryInterceptor } from \"./interceptors/telemetry\";\nimport { TimeoutInterceptor } from \"./interceptors/timeout\";\nimport type {\n ExecutionInterceptor,\n InterceptorContext,\n} from \"./interceptors/types\";\n\nconst logger = createLogger(\"plugin\");\n\n/**\n * Narrow an unknown thrown value to an Error that carries a numeric\n * `statusCode` property (e.g. `ApiError` from `@databricks/sdk-experimental`).\n */\nfunction hasHttpStatusCode(\n error: unknown,\n): error is Error & { statusCode: number } {\n return (\n error instanceof Error &&\n \"statusCode\" in error &&\n typeof (error as Record<string, unknown>).statusCode === \"number\"\n );\n}\n\n/**\n * Methods that should not be proxied by asUser().\n * These are lifecycle/internal methods that don't make sense\n * to execute in a user context.\n */\nconst EXCLUDED_FROM_PROXY = new Set([\n // Lifecycle methods\n \"setup\",\n \"shutdown\",\n \"injectRoutes\",\n \"getEndpoints\",\n \"getSkipBodyParsingPaths\",\n \"abortActiveOperations\",\n \"clientConfig\",\n // asUser itself - prevent chaining like .asUser().asUser()\n \"asUser\",\n // Internal methods\n \"constructor\",\n]);\n\n/**\n * Base abstract class for creating AppKit plugins.\n *\n * All plugins must declare a static `manifest` property with their metadata\n * and resource requirements. The manifest defines:\n * - `required` resources: Always needed for the plugin to function\n * - `optional` resources: May be needed depending on plugin configuration\n *\n * ## Static vs Runtime Resource Requirements\n *\n * The manifest is static and doesn't know the plugin's runtime configuration.\n * For resources that become required based on config options, plugins can\n * implement a static `getResourceRequirements(config)` method.\n *\n * At runtime, this method is called with the actual config to determine\n * which \"optional\" resources should be treated as \"required\".\n *\n * @example Basic plugin with static requirements\n * ```typescript\n * import { Plugin, toPlugin, PluginManifest, ResourceType } from '@databricks/appkit';\n *\n * const myManifest: PluginManifest = {\n * name: 'myPlugin',\n * displayName: 'My Plugin',\n * description: 'Does something awesome',\n * resources: {\n * required: [\n * { type: ResourceType.SQL_WAREHOUSE, alias: 'warehouse', ... }\n * ],\n * optional: []\n * }\n * };\n *\n * class MyPlugin extends Plugin<MyConfig> {\n * static manifest = myManifest;\n * }\n * ```\n *\n * @example Plugin with config-dependent resources\n * ```typescript\n * interface MyConfig extends BasePluginConfig {\n * enableCaching?: boolean;\n * }\n *\n * const myManifest: PluginManifest = {\n * name: 'myPlugin',\n * resources: {\n * required: [\n * { type: ResourceType.SQL_WAREHOUSE, alias: 'warehouse', ... }\n * ],\n * optional: [\n * // Database is optional in the static manifest\n * { type: ResourceType.DATABASE, alias: 'cache', description: 'Required if caching enabled', ... }\n * ]\n * }\n * };\n *\n * class MyPlugin extends Plugin<MyConfig> {\n * static manifest = myManifest<\"myPlugin\">;\n *\n * // Runtime method: converts optional resources to required based on config\n * static getResourceRequirements(config: MyConfig) {\n * const resources = [];\n * if (config.enableCaching) {\n * // When caching is enabled, Database becomes required\n * resources.push({\n * type: ResourceType.DATABASE,\n * alias: 'cache',\n * resourceKey: 'database',\n * description: 'Cache storage for query results',\n * permission: 'CAN_CONNECT_AND_CREATE',\n * fields: {\n * instance_name: { env: 'DATABRICKS_CACHE_INSTANCE' },\n * database_name: { env: 'DATABRICKS_CACHE_DB' },\n * },\n * required: true // Mark as required at runtime\n * });\n * }\n * return resources;\n * }\n * }\n * ```\n */\nexport abstract class Plugin<\n TConfig extends BasePluginConfig = BasePluginConfig,\n> implements BasePlugin\n{\n protected isReady = false;\n protected cache: CacheManager;\n protected app: AppManager;\n protected devFileReader: DevFileReader;\n protected streamManager: StreamManager;\n protected telemetry: ITelemetry;\n\n /** Registered endpoints for this plugin */\n private registeredEndpoints: PluginEndpointMap = {};\n\n /** Paths that opt out of JSON body parsing (e.g. file upload routes) */\n private skipBodyParsingPaths: Set<string> = new Set();\n\n /**\n * Plugin initialization phase.\n * - 'core': Initialized first (e.g., config plugins)\n * - 'normal': Initialized second (most plugins)\n * - 'deferred': Initialized last (e.g., server plugin)\n */\n static phase: PluginPhase = \"normal\";\n\n /**\n * Plugin name identifier.\n */\n name: string;\n\n constructor(protected config: TConfig) {\n this.name =\n config.name ??\n (this.constructor as { manifest?: { name: string } }).manifest?.name ??\n \"plugin\";\n this.telemetry = TelemetryManager.getProvider(this.name, config.telemetry);\n this.streamManager = new StreamManager();\n this.cache = CacheManager.getInstanceSync();\n this.app = new AppManager();\n this.devFileReader = DevFileReader.getInstance();\n\n this.isReady = true;\n }\n\n injectRoutes(_: express.Router) {\n return;\n }\n\n async setup() {}\n\n getEndpoints(): PluginEndpointMap {\n return this.registeredEndpoints;\n }\n\n getSkipBodyParsingPaths(): ReadonlySet<string> {\n return this.skipBodyParsingPaths;\n }\n\n abortActiveOperations(): void {\n this.streamManager.abortAll();\n }\n\n /**\n * Returns the public exports for this plugin.\n * Override this to define a custom public API.\n * By default, returns an empty object.\n *\n * The returned object becomes the plugin's public API on the AppKit instance\n * (e.g. `appkit.myPlugin.method()`). AppKit automatically binds method context\n * and adds `asUser(req)` for user-scoped execution.\n *\n * @example\n * ```ts\n * class MyPlugin extends Plugin {\n * private getData() { return []; }\n *\n * exports() {\n * return { getData: this.getData };\n * }\n * }\n *\n * // After registration:\n * const appkit = await createApp({ plugins: [myPlugin()] });\n * appkit.myPlugin.getData();\n * ```\n */\n exports(): unknown {\n return {};\n }\n\n /**\n * Returns startup config to expose to the client.\n * Override this to surface server-side values that are safe to publish to the\n * frontend, such as feature flags, resource IDs, or other app boot settings.\n *\n * This runs once when the server starts, so it should not depend on\n * request-scoped or user-specific state.\n *\n * String values that match non-public environment variables are redacted\n * unless you intentionally expose them via a matching `PUBLIC_APPKIT_` env var.\n *\n * Values must be JSON-serializable plain data (no functions, Dates, classes,\n * Maps, Sets, BigInts, or circular references).\n * By default returns an empty object (plugin contributes nothing to client config).\n *\n * On the client, read the config with the `usePluginClientConfig` hook\n * (React) or the `getPluginClientConfig` function (vanilla JS), both\n * from `@databricks/appkit-ui`.\n *\n * @example\n * ```ts\n * // Server — plugin definition\n * class MyPlugin extends Plugin<MyConfig> {\n * clientConfig() {\n * return {\n * warehouseId: this.config.warehouseId,\n * features: { darkMode: true },\n * };\n * }\n * }\n *\n * // Client — React component\n * import { usePluginClientConfig } from \"@databricks/appkit-ui/react\";\n *\n * interface MyPluginConfig { warehouseId: string; features: { darkMode: boolean } }\n *\n * const config = usePluginClientConfig<MyPluginConfig>(\"myPlugin\");\n * config.warehouseId; // \"abc-123\"\n *\n * // Client — vanilla JS\n * import { getPluginClientConfig } from \"@databricks/appkit-ui/js\";\n *\n * const config = getPluginClientConfig<MyPluginConfig>(\"myPlugin\");\n * ```\n */\n clientConfig(): Record<string, unknown> {\n return {};\n }\n\n /**\n * Resolve the effective user ID from a request.\n *\n * Returns the `x-forwarded-user` header when present. In development mode\n * (`NODE_ENV=development`) falls back to the current context user ID so\n * that callers outside an active `runInUserContext` scope still get a\n * consistent value.\n *\n * @throws AuthenticationError in production when no user header is present.\n */\n protected resolveUserId(req: express.Request): string {\n const userId = req.header(\"x-forwarded-user\");\n if (userId) return userId;\n if (process.env.NODE_ENV === \"development\") return getCurrentUserId();\n throw AuthenticationError.missingToken(\n \"Missing x-forwarded-user header. Cannot resolve user ID.\",\n );\n }\n\n /**\n * Execute operations using the user's identity from the request.\n * Returns a proxy of this plugin where all method calls execute\n * with the user's Databricks credentials instead of the service principal.\n *\n * @param req - The Express request containing the user token in headers\n * @returns A proxied plugin instance that executes as the user\n * @throws AuthenticationError if user token is not available in request headers (production only).\n * In development mode (`NODE_ENV=development`), skips user impersonation instead of throwing.\n */\n asUser(req: express.Request): this {\n const token = req.header(\"x-forwarded-access-token\");\n const userId = req.header(\"x-forwarded-user\");\n const isDev = process.env.NODE_ENV === \"development\";\n\n // In local development, skip user impersonation\n // since there's no user token available\n if (!token && isDev) {\n logger.warn(\n \"asUser() called without user token in development mode. Skipping user impersonation.\",\n );\n\n return this;\n }\n\n if (!token) {\n throw AuthenticationError.missingToken(\"user token\");\n }\n\n if (!userId && !isDev) {\n throw AuthenticationError.missingUserId();\n }\n\n const effectiveUserId = userId || \"dev-user\";\n\n const userContext = ServiceContext.createUserContext(\n token,\n effectiveUserId,\n );\n\n // Return a proxy that wraps method calls in user context\n return this._createUserContextProxy(userContext);\n }\n\n /**\n * Creates a proxy that wraps method calls in a user context.\n * This allows all plugin methods to automatically use the user's\n * Databricks credentials.\n */\n private _createUserContextProxy(userContext: UserContext): this {\n return new Proxy(this, {\n get: (target, prop, receiver) => {\n const value = Reflect.get(target, prop, receiver);\n\n if (typeof value !== \"function\") {\n return value;\n }\n\n if (typeof prop === \"string\" && EXCLUDED_FROM_PROXY.has(prop)) {\n return value;\n }\n\n return (...args: unknown[]) => {\n return runInUserContext(userContext, () => value.apply(target, args));\n };\n },\n }) as this;\n }\n\n // streaming execution with interceptors\n protected async executeStream<T>(\n res: IAppResponse,\n fn: StreamExecuteHandler<T>,\n options: StreamExecutionSettings,\n userKey?: string,\n ) {\n // destructure options\n const {\n stream: streamConfig,\n default: defaultConfig,\n user: userConfig,\n } = options;\n\n // build execution options\n const executeConfig = this._buildExecutionConfig({\n default: defaultConfig,\n user: userConfig,\n });\n\n // get user key from context if not provided\n const effectiveUserKey = userKey ?? getCurrentUserId();\n\n const self = this;\n\n // wrapper function to ensure it returns a generator\n const asyncWrapperFn = async function* (streamSignal?: AbortSignal) {\n // build execution context\n const context: InterceptorContext = {\n signal: streamSignal,\n metadata: new Map(),\n userKey: effectiveUserKey,\n };\n\n // build interceptors\n const interceptors = self._buildInterceptors(executeConfig);\n\n // wrap the function to ensure it returns a promise\n const wrappedFn = async () => {\n const result = await fn(context.signal);\n return result;\n };\n\n // execute the function with interceptors\n const result = await self._executeWithInterceptors(\n wrappedFn as (signal?: AbortSignal) => Promise<T>,\n interceptors,\n context,\n );\n\n // check if result is a generator\n if (self._checkIfGenerator(result)) {\n yield* result;\n } else {\n yield result;\n }\n };\n\n // stream the result to the client\n await this.streamManager.stream(res, asyncWrapperFn, streamConfig);\n }\n\n /**\n * Execute a function with the plugin's interceptor chain.\n *\n * Returns an {@link ExecutionResult} discriminated union:\n * - `{ ok: true, data: T }` on success\n * - `{ ok: false, status: number, message: string }` on failure\n *\n * Errors are never thrown — the method is production-safe.\n */\n protected async execute<T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n options: PluginExecutionSettings,\n userKey?: string,\n ): Promise<ExecutionResult<T>> {\n const executeConfig = this._buildExecutionConfig(options);\n\n const interceptors = this._buildInterceptors(executeConfig);\n\n // get user key from context if not provided\n const effectiveUserKey = userKey ?? getCurrentUserId();\n\n const context: InterceptorContext = {\n metadata: new Map(),\n userKey: effectiveUserKey,\n };\n\n try {\n const data = await this._executeWithInterceptors(\n fn,\n interceptors,\n context,\n );\n return { ok: true, data };\n } catch (error) {\n logger.error(\"Plugin execution failed\", { error, plugin: this.name });\n\n if (error instanceof AppKitError) {\n return {\n ok: false,\n status: error.statusCode,\n message: error.message,\n };\n }\n\n if (hasHttpStatusCode(error)) {\n const isDev = process.env.NODE_ENV !== \"production\";\n const isClientError = error.statusCode >= 400 && error.statusCode < 500;\n return {\n ok: false,\n status: error.statusCode,\n message: isDev || isClientError ? error.message : \"Server error\",\n };\n }\n\n const isDev = process.env.NODE_ENV !== \"production\";\n return {\n ok: false,\n status: 500,\n message:\n isDev && error instanceof Error ? error.message : \"Server error\",\n };\n }\n }\n\n protected registerEndpoint(name: string, path: string): void {\n this.registeredEndpoints[name] = path;\n }\n\n protected route<_TResponse>(\n router: express.Router,\n config: RouteConfig,\n ): void {\n const { name, method, path, handler } = config;\n\n router[method](path, handler);\n\n const fullPath = `/api/${this.name}${path}`;\n this.registerEndpoint(name, fullPath);\n\n if (config.skipBodyParsing) {\n this.skipBodyParsingPaths.add(fullPath);\n }\n }\n\n // build execution options by merging defaults, plugin config, and user overrides\n private _buildExecutionConfig(\n options: PluginExecutionSettings,\n ): PluginExecuteConfig {\n const { default: methodDefaults, user: userOverride } = options;\n\n // Merge: method defaults <- plugin config <- user override (highest priority)\n return deepMerge(\n deepMerge(methodDefaults, this.config),\n userOverride ?? {},\n ) as PluginExecuteConfig;\n }\n\n // build interceptors based on execute options\n private _buildInterceptors(\n options: PluginExecuteConfig,\n ): ExecutionInterceptor[] {\n const interceptors: ExecutionInterceptor[] = [];\n\n // order matters: telemetry → timeout → retry → cache (innermost to outermost)\n\n const telemetryConfig = normalizeTelemetryOptions(this.config.telemetry);\n if (\n telemetryConfig.traces &&\n (options.telemetryInterceptor?.enabled ?? true)\n ) {\n interceptors.push(\n new TelemetryInterceptor(this.telemetry, options.telemetryInterceptor),\n );\n }\n\n if (options.timeout && options.timeout > 0) {\n interceptors.push(new TimeoutInterceptor(options.timeout));\n }\n\n if (\n options.retry?.enabled &&\n options.retry.attempts &&\n options.retry.attempts > 1\n ) {\n interceptors.push(new RetryInterceptor(options.retry));\n }\n\n if (options.cache?.enabled && options.cache.cacheKey?.length) {\n interceptors.push(new CacheInterceptor(this.cache, options.cache));\n }\n\n return interceptors;\n }\n\n // execute method wrapped with interceptors\n private async _executeWithInterceptors<T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n interceptors: ExecutionInterceptor[],\n context: InterceptorContext,\n ): Promise<T> {\n // no interceptors, execute directly\n if (interceptors.length === 0) {\n return fn(context.signal);\n }\n // build nested execution chain from interceptors\n let wrappedFn = () => fn(context.signal);\n\n // wrap each interceptor around the previous function\n for (const interceptor of interceptors) {\n const previousFn = wrappedFn;\n wrappedFn = () => interceptor.intercept(previousFn, context);\n }\n\n return wrappedFn();\n }\n\n private _checkIfGenerator(\n result: any,\n ): result is AsyncGenerator<any, void, unknown> {\n return (\n result && typeof result === \"object\" && Symbol.asyncIterator in result\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.d.ts","names":[],"sources":["../../../src/plugins/analytics/analytics.ts"],"mappings":";;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","names":[],"sources":["../../../src/plugins/analytics/analytics.ts"],"mappings":";;;;;;;;;;;;cAwBa,eAAA,SAAwB,MAAA;;SAE5B,QAAA,EAAuB,cAAA;EAAA,iBAEb,WAAA;EAAA,UACC,MAAA,EAAQ,gBAAA;EAAA,QAGlB,SAAA;EAAA,QACA,cAAA;cAEI,MAAA,EAAQ,gBAAA;EAWpB,YAAA,CAAa,MAAA,EAAQ,UAAA;EApBS;;;;EA6CxB,iBAAA,CACJ,GAAA,EAAK,OAAA,CAAQ,OAAA,EACb,GAAA,EAAK,OAAA,CAAQ,QAAA,GACZ,OAAA;EADI;;;;EAuCD,iBAAA,CACJ,GAAA,EAAK,OAAA,CAAQ,OAAA,EACb,GAAA,EAAK,OAAA,CAAQ,QAAA,GACZ,OAAA;EA8G2B;;;;;;;;;;;;;;;EAFxB,KAAA,CACJ,KAAA,UACA,UAAA,GAAa,MAAA,SAAe,aAAA,sBAC5B,gBAAA,GAAmB,MAAA,eACnB,MAAA,GAAS,WAAA,GACR,OAAA;EA5MsC;;;EAAA,UAoOzB,YAAA,CACd,eAAA,EAAiB,eAAA,EACjB,KAAA,UACA,MAAA,GAAS,WAAA,GACR,OAAA,CAAQ,UAAA,aAAuB,SAAA,CAAU,YAAA;EAItC,QAAA,CAAA,GAAY,OAAA;EAxOD;;;;EAgPjB,OAAA,CAAA;;;;2BA5Ce,UAAA,GACA,MAAA,SAAe,aAAA,sBAAiC,gBAAA,GAC1C,MAAA,eAAmB,MAAA,GAC7B,WAAA,KACR,OAAA;EAAA;AAAA;;;;cAqDQ,SAAA,EAAS,QAAA,QAAA,eAAA,EAAA,gBAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createLogger } from "../../logging/logger.js";
|
|
2
|
-
import {
|
|
2
|
+
import { getWarehouseId, getWorkspaceClient } from "../../context/execution-context.js";
|
|
3
3
|
import { init_context } from "../../context/index.js";
|
|
4
4
|
import { Plugin } from "../../plugin/plugin.js";
|
|
5
5
|
import { toPlugin } from "../../plugin/to-plugin.js";
|
|
@@ -98,8 +98,7 @@ var AnalyticsPlugin = class extends Plugin {
|
|
|
98
98
|
}
|
|
99
99
|
const { query, isAsUser } = queryResult;
|
|
100
100
|
const executor = isAsUser ? this.asUser(req) : this;
|
|
101
|
-
const
|
|
102
|
-
const executorKey = isAsUser ? userKey : "global";
|
|
101
|
+
const executorKey = isAsUser ? this.resolveUserId(req) : "global";
|
|
103
102
|
const queryParameters = format === "ARROW" ? {
|
|
104
103
|
formatParameters: {
|
|
105
104
|
disposition: "EXTERNAL_LINKS",
|