@databricks/appkit 0.44.0 → 0.46.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 +1 -0
- package/dist/agents/databricks.d.ts +25 -1
- package/dist/agents/databricks.d.ts.map +1 -1
- package/dist/agents/databricks.js +20 -1
- package/dist/agents/databricks.js.map +1 -1
- package/dist/appkit/package.js +1 -1
- package/dist/beta.d.ts +2 -2
- package/dist/connectors/sql-warehouse/arrow-schema.js +283 -0
- package/dist/connectors/sql-warehouse/arrow-schema.js.map +1 -0
- package/dist/connectors/sql-warehouse/client.js +166 -60
- package/dist/connectors/sql-warehouse/client.js.map +1 -1
- package/dist/core/agent/load-agents.d.ts.map +1 -1
- package/dist/core/agent/load-agents.js +52 -0
- package/dist/core/agent/load-agents.js.map +1 -1
- package/dist/core/agent/types.d.ts +11 -0
- package/dist/core/agent/types.d.ts.map +1 -1
- package/dist/core/agent/types.js.map +1 -1
- package/dist/core/appkit.d.ts.map +1 -1
- package/dist/core/appkit.js +2 -0
- package/dist/core/appkit.js.map +1 -1
- package/dist/core/lifecycle-manager.js +183 -0
- package/dist/core/lifecycle-manager.js.map +1 -0
- package/dist/core/plugin-context.d.ts +19 -1
- package/dist/core/plugin-context.d.ts.map +1 -1
- package/dist/core/plugin-context.js +9 -3
- package/dist/core/plugin-context.js.map +1 -1
- package/dist/errors/base.d.ts +18 -0
- package/dist/errors/base.d.ts.map +1 -1
- package/dist/errors/base.js +20 -0
- package/dist/errors/base.js.map +1 -1
- package/dist/errors/execution.d.ts +34 -2
- package/dist/errors/execution.d.ts.map +1 -1
- package/dist/errors/execution.js +40 -5
- package/dist/errors/execution.js.map +1 -1
- package/dist/plugin/plugin.d.ts.map +1 -1
- package/dist/plugin/plugin.js +2 -1
- package/dist/plugin/plugin.js.map +1 -1
- package/dist/plugins/agents/agents.d.ts.map +1 -1
- package/dist/plugins/agents/agents.js +3 -3
- package/dist/plugins/agents/agents.js.map +1 -1
- package/dist/plugins/analytics/analytics.d.ts +84 -8
- package/dist/plugins/analytics/analytics.d.ts.map +1 -1
- package/dist/plugins/analytics/analytics.js +296 -61
- package/dist/plugins/analytics/analytics.js.map +1 -1
- package/dist/plugins/analytics/index.js +1 -1
- package/dist/plugins/analytics/result-delivery.js +287 -0
- package/dist/plugins/analytics/result-delivery.js.map +1 -0
- package/dist/plugins/analytics/types.d.ts +8 -0
- package/dist/plugins/analytics/types.d.ts.map +1 -1
- package/dist/plugins/analytics/types.js.map +1 -1
- package/dist/plugins/files/plugin.js +1 -1
- package/dist/plugins/genie/genie.js +1 -1
- package/dist/plugins/jobs/types.d.ts +1 -1
- package/dist/plugins/lakebase/lakebase.d.ts +10 -2
- package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
- package/dist/plugins/lakebase/lakebase.js +19 -8
- package/dist/plugins/lakebase/lakebase.js.map +1 -1
- package/dist/plugins/server/index.d.ts +51 -1
- package/dist/plugins/server/index.d.ts.map +1 -1
- package/dist/plugins/server/index.js +110 -23
- package/dist/plugins/server/index.js.map +1 -1
- package/dist/registry/resource-registry.d.ts +9 -1
- package/dist/registry/resource-registry.d.ts.map +1 -1
- package/dist/registry/resource-registry.js +22 -5
- package/dist/registry/resource-registry.js.map +1 -1
- package/dist/shared/src/execute.d.ts +1 -3
- package/dist/shared/src/execute.d.ts.map +1 -1
- package/dist/shared/src/index.d.ts +1 -0
- package/dist/shared/src/plugin.d.ts +16 -0
- package/dist/shared/src/plugin.d.ts.map +1 -1
- package/dist/shared/src/sse/analytics.d.ts +1 -0
- package/dist/shared/src/sse/analytics.js +40 -0
- package/dist/shared/src/sse/analytics.js.map +1 -0
- package/dist/stream/arrow-stream-processor.js +93 -117
- package/dist/stream/arrow-stream-processor.js.map +1 -1
- package/dist/stream/buffers.js +1 -1
- package/dist/stream/buffers.js.map +1 -1
- package/dist/stream/defaults.js +1 -3
- package/dist/stream/defaults.js.map +1 -1
- package/dist/stream/sse-writer.js +3 -2
- package/dist/stream/sse-writer.js.map +1 -1
- package/dist/stream/stream-manager.d.ts +2 -2
- package/dist/stream/stream-manager.d.ts.map +1 -1
- package/dist/stream/stream-manager.js +41 -33
- package/dist/stream/stream-manager.js.map +1 -1
- package/dist/stream/stream-registry.js +30 -23
- package/dist/stream/stream-registry.js.map +1 -1
- package/dist/stream/timers.js +17 -0
- package/dist/stream/timers.js.map +1 -0
- package/dist/stream/types.js.map +1 -1
- package/dist/telemetry/telemetry-manager.js +19 -13
- package/dist/telemetry/telemetry-manager.js.map +1 -1
- package/dist/type-generator/query-registry.js +32 -2
- package/dist/type-generator/query-registry.js.map +1 -1
- package/dist/utils/safe-handler.js +28 -0
- package/dist/utils/safe-handler.js.map +1 -0
- package/docs/api/appkit/Class.AppKitError.md +40 -8
- package/docs/api/appkit/Class.AuthenticationError.md +56 -16
- package/docs/api/appkit/Class.ConfigurationError.md +57 -17
- package/docs/api/appkit/Class.ConnectionError.md +57 -17
- package/docs/api/appkit/Class.ExecutionError.md +80 -22
- package/docs/api/appkit/Class.InitializationError.md +55 -15
- package/docs/api/appkit/Class.Plugin.md +2 -0
- package/docs/api/appkit/Class.ResourceRegistry.md +1 -1
- package/docs/api/appkit/Class.ServerError.md +55 -15
- package/docs/api/appkit/Class.TunnelError.md +56 -16
- package/docs/api/appkit/Class.ValidationError.md +55 -15
- package/docs/api/appkit/Interface.AgentDefinition.md +11 -0
- package/docs/api/appkit/Interface.GenerationParams.md +58 -0
- package/docs/api/appkit/Interface.RegisteredAgent.md +11 -0
- package/docs/api/appkit.md +1 -0
- package/llms.txt +1 -0
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
|
@@ -16,6 +16,19 @@ interface RouteTarget {
|
|
|
16
16
|
type ToolProviderPlugin = BasePlugin & ToolProvider & {
|
|
17
17
|
asUser: (req: IAppRequest) => ToolProvider;
|
|
18
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* Lifecycle events emitted through {@link PluginContext.emitLifecycle}.
|
|
21
|
+
*
|
|
22
|
+
* - `"setup:complete"` — emitted by AppKit core after every plugin's
|
|
23
|
+
* `setup()` has finished.
|
|
24
|
+
* - `"server:ready"` — emitted when the HTTP server is listening.
|
|
25
|
+
* - `"shutdown"` — emitted by the core lifecycle manager during graceful
|
|
26
|
+
* shutdown, AFTER all plugin `shutdown()` hooks have completed. The emit is
|
|
27
|
+
* bounded by a short timeout (see the lifecycle manager's shutdown budget),
|
|
28
|
+
* so subscribers must not start long-running async work — finish quickly or
|
|
29
|
+
* be cut off. (The server plugin subscribes here to force-close its
|
|
30
|
+
* remaining sockets once peers have drained.)
|
|
31
|
+
*/
|
|
19
32
|
type LifecycleEvent = "setup:complete" | "server:ready" | "shutdown";
|
|
20
33
|
/**
|
|
21
34
|
* Mediator for inter-plugin communication.
|
|
@@ -107,6 +120,10 @@ declare class PluginContext {
|
|
|
107
120
|
executeTool(req: express.Request, pluginName: string, toolName: string, args: unknown, signal?: AbortSignal, timeoutMs?: number): Promise<unknown>;
|
|
108
121
|
/**
|
|
109
122
|
* Register a lifecycle hook callback.
|
|
123
|
+
*
|
|
124
|
+
* See {@link LifecycleEvent} for event semantics. In particular,
|
|
125
|
+
* `"shutdown"` subscribers run inside a bounded shutdown phase and must
|
|
126
|
+
* not start long-running async work.
|
|
110
127
|
*/
|
|
111
128
|
onLifecycle(event: LifecycleEvent, fn: () => void | Promise<void>): void;
|
|
112
129
|
/**
|
|
@@ -114,7 +131,8 @@ declare class PluginContext {
|
|
|
114
131
|
* Errors in individual callbacks are logged but do not prevent
|
|
115
132
|
* other callbacks from running.
|
|
116
133
|
*
|
|
117
|
-
* @internal Called by AppKit core
|
|
134
|
+
* @internal Called by AppKit core: `setup:complete` after plugin setup,
|
|
135
|
+
* and `shutdown` by the lifecycle manager during graceful shutdown.
|
|
118
136
|
*/
|
|
119
137
|
emitLifecycle(event: LifecycleEvent): Promise<void>;
|
|
120
138
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-context.d.ts","names":[],"sources":["../../src/core/plugin-context.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"plugin-context.d.ts","names":[],"sources":["../../src/core/plugin-context.ts"],"mappings":";;;;;;UAcU,WAAA;EACR,YAAA,CAAa,EAAA,GAAK,GAAA,EAAK,OAAA,CAAQ,WAAA;AAAA;;AAdmC;;;;;KAuB/D,kBAAA,GAAqB,UAAA,GACxB,YAAA;EAAiB,MAAA,GAAS,GAAA,EAAK,WAAA,KAAgB,YAAA;AAAA;;;;AAVI;;;;;;;;;;KAyBhD,cAAA;;;;;;;AAfwD;;;;;AA8B7D;;cAAa,aAAA;EAAA,QACH,WAAA;EAAA,QACA,WAAA;EAAA,QACA,aAAA;EAAA,QACA,OAAA;EAAA,QACA,cAAA;EAAA,QAIA,SAAA;EA8FM;;;;;;;EArFd,QAAA,CACE,MAAA,UACA,IAAA,aACG,QAAA,EAAU,OAAA,CAAQ,cAAA;EAwLI;;;;;EA1K3B,aAAA,CAAc,IAAA,aAAiB,QAAA,EAAU,OAAA,CAAQ,cAAA;EAhCzC;;;;;;;;;EAiDR,qBAAA,CAAsB,MAAA,EAAQ,WAAA;EAjB9B;;;;;;;;EA4CA,oBAAA,CAAqB,IAAA,UAAc,MAAA,EAAQ,kBAAA;EAAtB;;;;EAcrB,cAAA,CAAe,IAAA,UAAc,QAAA,EAAU,UAAA;EAAA;;;;;;EAUvC,UAAA,CAAA,GAAc,WAAA,SAAoB,UAAA;EAQN;;;;EAA5B,gBAAA,CAAA,GAAoB,KAAA;IAAQ,IAAA;IAAc,QAAA,EAAU,YAAA;EAAA;EAwBlD;;;;;;;;;;;;;;EAHI,WAAA,CACJ,GAAA,EAAK,OAAA,CAAQ,OAAA,EACb,UAAA,UACA,QAAA,UACA,IAAA,WACA,MAAA,GAAS,WAAA,EACT,SAAA,YACC,OAAA;EAgGH;;;;;;;EA/CA,WAAA,CAAY,KAAA,EAAO,cAAA,EAAgB,EAAA,eAAiB,OAAA;;;;;;;;;EAiB9C,aAAA,CAAc,KAAA,EAAO,cAAA,GAAiB,OAAA;;;;EA8B5C,cAAA,CAAA;;;;EAOA,SAAA,CAAU,IAAA;EAAA,QAIF,UAAA;EAAA,QAaA,eAAA;AAAA"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createLogger } from "../logging/logger.js";
|
|
2
2
|
import { TelemetryManager } from "../telemetry/telemetry-manager.js";
|
|
3
3
|
import { SpanStatusCode } from "../telemetry/index.js";
|
|
4
|
+
import { forwardAsyncErrors } from "../utils/safe-handler.js";
|
|
4
5
|
|
|
5
6
|
//#region src/core/plugin-context.ts
|
|
6
7
|
const logger = createLogger("plugin-context");
|
|
@@ -153,6 +154,10 @@ var PluginContext = class {
|
|
|
153
154
|
}
|
|
154
155
|
/**
|
|
155
156
|
* Register a lifecycle hook callback.
|
|
157
|
+
*
|
|
158
|
+
* See {@link LifecycleEvent} for event semantics. In particular,
|
|
159
|
+
* `"shutdown"` subscribers run inside a bounded shutdown phase and must
|
|
160
|
+
* not start long-running async work.
|
|
156
161
|
*/
|
|
157
162
|
onLifecycle(event, fn) {
|
|
158
163
|
let hooks = this.lifecycleHooks.get(event);
|
|
@@ -167,7 +172,8 @@ var PluginContext = class {
|
|
|
167
172
|
* Errors in individual callbacks are logged but do not prevent
|
|
168
173
|
* other callbacks from running.
|
|
169
174
|
*
|
|
170
|
-
* @internal Called by AppKit core
|
|
175
|
+
* @internal Called by AppKit core: `setup:complete` after plugin setup,
|
|
176
|
+
* and `shutdown` by the lifecycle manager during graceful shutdown.
|
|
171
177
|
*/
|
|
172
178
|
async emitLifecycle(event) {
|
|
173
179
|
const hooks = this.lifecycleHooks.get(event);
|
|
@@ -195,13 +201,13 @@ var PluginContext = class {
|
|
|
195
201
|
if (!this.routeTarget) return;
|
|
196
202
|
this.routeTarget.addExtension((app) => {
|
|
197
203
|
const method = route.method.toLowerCase();
|
|
198
|
-
if (typeof app[method] === "function") app[method](route.path, ...route.handlers);
|
|
204
|
+
if (typeof app[method] === "function") app[method](route.path, ...route.handlers.map(forwardAsyncErrors));
|
|
199
205
|
});
|
|
200
206
|
}
|
|
201
207
|
applyMiddleware(path, handlers) {
|
|
202
208
|
if (!this.routeTarget) return;
|
|
203
209
|
this.routeTarget.addExtension((app) => {
|
|
204
|
-
app.use(path, ...handlers);
|
|
210
|
+
app.use(path, ...handlers.map(forwardAsyncErrors));
|
|
205
211
|
});
|
|
206
212
|
}
|
|
207
213
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-context.js","names":[],"sources":["../../src/core/plugin-context.ts"],"sourcesContent":["import type express from \"express\";\nimport type { BasePlugin, IAppRequest, ToolProvider } from \"shared\";\nimport { createLogger } from \"../logging/logger\";\nimport { SpanStatusCode, TelemetryManager } from \"../telemetry\";\n\nconst logger = createLogger(\"plugin-context\");\n\ninterface BufferedRoute {\n method: string;\n path: string;\n handlers: express.RequestHandler[];\n}\n\ninterface RouteTarget {\n addExtension(fn: (app: express.Application) => void): void;\n}\n\n/**\n * A tool-provider plugin that also exposes user-scoped execution. Plugins\n * derived from {@link Plugin} satisfy this implicitly because `asUser` lives\n * on the base class. {@link isToolProvider} narrows to this shape so\n * `executeTool` can call `asUser` without an unsafe cast.\n */\ntype ToolProviderPlugin = BasePlugin &\n ToolProvider & { asUser: (req: IAppRequest) => ToolProvider };\n\ntype LifecycleEvent = \"setup:complete\" | \"server:ready\" | \"shutdown\";\n\n/**\n * Mediator for inter-plugin communication.\n *\n * Created by AppKit core and passed to every plugin. Plugins request\n * capabilities from the context instead of holding direct references\n * to sibling plugin instances.\n *\n * Capabilities:\n * - Route mounting with buffering (order-independent)\n * - Typed ToolProvider registry (live, not snapshot-based)\n * - User-scoped tool execution with automatic telemetry\n * - Lifecycle hooks for plugin coordination\n */\nexport class PluginContext {\n private routeBuffer: BufferedRoute[] = [];\n private routeTarget: RouteTarget | null = null;\n private toolProviders = new Map<string, ToolProviderPlugin>();\n private plugins = new Map<string, BasePlugin>();\n private lifecycleHooks = new Map<\n LifecycleEvent,\n Set<() => void | Promise<void>>\n >();\n private telemetry = TelemetryManager.getProvider(\"plugin-context\");\n\n /**\n * Register a route on the root Express application.\n *\n * If a route target (server plugin) has registered, the route is applied\n * immediately. Otherwise it is buffered and flushed when a route target\n * becomes available.\n */\n addRoute(\n method: string,\n path: string,\n ...handlers: express.RequestHandler[]\n ): void {\n if (this.routeTarget) {\n this.applyRoute({ method, path, handlers });\n } else {\n this.routeBuffer.push({ method, path, handlers });\n }\n }\n\n /**\n * Register middleware on the root Express application.\n *\n * Same buffering semantics as `addRoute`.\n */\n addMiddleware(path: string, ...handlers: express.RequestHandler[]): void {\n if (this.routeTarget) {\n this.applyMiddleware(path, handlers);\n } else {\n this.routeBuffer.push({ method: \"use\", path, handlers });\n }\n }\n\n /**\n * Called by the server plugin to opt in as the route target.\n * Flushes all buffered routes via the server's `addExtension`.\n *\n * Only the first caller wins — subsequent calls are ignored with a warning.\n * In practice only the server plugin registers, but a misconfigured app\n * (two server plugins, or duplicate AppKit setup in tests) would otherwise\n * silently drop the first target's later extensions.\n */\n registerAsRouteTarget(target: RouteTarget): void {\n if (this.routeTarget) {\n logger.warn(\n \"registerAsRouteTarget called more than once; ignoring duplicate registration\",\n );\n return;\n }\n this.routeTarget = target;\n\n for (const route of this.routeBuffer) {\n if (route.method === \"use\") {\n this.applyMiddleware(route.path, route.handlers);\n } else {\n this.applyRoute(route);\n }\n }\n this.routeBuffer = [];\n }\n\n /**\n * Register a plugin that implements the ToolProvider interface.\n * Called by AppKit core after constructing each plugin.\n *\n * Plugin names should be unique (they are derived from `manifest.name`).\n * A duplicate registration overwrites the previous entry and emits a\n * warning so the misconfiguration is visible in startup logs.\n */\n registerToolProvider(name: string, plugin: ToolProviderPlugin): void {\n if (this.toolProviders.has(name)) {\n logger.warn(\n 'Tool provider \"%s\" registered more than once; the previous registration is being overwritten',\n name,\n );\n }\n this.toolProviders.set(name, plugin);\n }\n\n /**\n * Register a plugin instance.\n * Called by AppKit core after constructing each plugin.\n */\n registerPlugin(name: string, instance: BasePlugin): void {\n this.plugins.set(name, instance);\n }\n\n /**\n * Returns all registered plugin instances keyed by name.\n * Used by the server plugin for route injection, client config,\n * and shutdown coordination. The returned map is read-only at the\n * type level — callers must not mutate the live registry.\n */\n getPlugins(): ReadonlyMap<string, BasePlugin> {\n return this.plugins;\n }\n\n /**\n * Returns all registered ToolProvider plugins.\n * Always returns the current set — not a frozen snapshot.\n */\n getToolProviders(): Array<{ name: string; provider: ToolProvider }> {\n return Array.from(this.toolProviders.entries()).map(([name, provider]) => ({\n name,\n provider,\n }));\n }\n\n /**\n * Execute a tool on a ToolProvider plugin with automatic user scoping\n * and telemetry.\n *\n * The context:\n * 1. Resolves the plugin by name\n * 2. Calls `asUser(req)` for user-scoped execution\n * 3. Wraps the call in a telemetry span with a configurable timeout\n *\n * @param timeoutMs Per-call timeout. Defaults to 5 minutes — the floor\n * for cold SQL Warehouse round-trips, long Genie conversations, and\n * busy serverless Lakebase queries. The agents plugin overrides this\n * per-app via `agents({ limits: { toolCallTimeoutMs } })`.\n */\n async executeTool(\n req: express.Request,\n pluginName: string,\n toolName: string,\n args: unknown,\n signal?: AbortSignal,\n timeoutMs: number = 300_000,\n ): Promise<unknown> {\n const provider = this.toolProviders.get(pluginName);\n if (!provider) {\n throw new Error(\n `PluginContext: unknown plugin \"${pluginName}\". Available: ${Array.from(this.toolProviders.keys()).join(\", \")}`,\n );\n }\n\n const tracer = this.telemetry.getTracer();\n const operationName = `executeTool:${pluginName}.${toolName}`;\n\n return tracer.startActiveSpan(operationName, async (span) => {\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n const combinedSignal = signal\n ? AbortSignal.any([signal, timeoutSignal])\n : timeoutSignal;\n\n try {\n const userScoped = provider.asUser(req);\n const result = await userScoped.executeAgentTool(\n toolName,\n args,\n combinedSignal,\n );\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message:\n error instanceof Error ? error.message : \"Tool execution failed\",\n });\n span.recordException(\n error instanceof Error ? error : new Error(String(error)),\n );\n throw error;\n } finally {\n span.end();\n }\n });\n }\n\n /**\n * Register a lifecycle hook callback.\n */\n onLifecycle(event: LifecycleEvent, fn: () => void | Promise<void>): void {\n let hooks = this.lifecycleHooks.get(event);\n if (!hooks) {\n hooks = new Set();\n this.lifecycleHooks.set(event, hooks);\n }\n hooks.add(fn);\n }\n\n /**\n * Emit a lifecycle event, calling all registered callbacks.\n * Errors in individual callbacks are logged but do not prevent\n * other callbacks from running.\n *\n * @internal Called by AppKit core only.\n */\n async emitLifecycle(event: LifecycleEvent): Promise<void> {\n const hooks = this.lifecycleHooks.get(event);\n if (!hooks) return;\n\n if (\n event === \"setup:complete\" &&\n this.routeBuffer.length > 0 &&\n !this.routeTarget\n ) {\n logger.warn(\n \"%d buffered routes were never applied — no server plugin registered as route target\",\n this.routeBuffer.length,\n );\n }\n\n // Snapshot before iterating so a callback that registers a new hook for\n // the same event does not mutate the loop. ECMAScript Set iteration would\n // otherwise visit late-added entries, risking unexpected re-entry.\n for (const fn of [...hooks]) {\n try {\n await fn();\n } catch (error) {\n logger.error(\"Lifecycle hook '%s' failed: %O\", event, error);\n }\n }\n }\n\n /**\n * Returns all registered plugin names.\n */\n getPluginNames(): string[] {\n return Array.from(this.plugins.keys());\n }\n\n /**\n * Check if a plugin with the given name is registered.\n */\n hasPlugin(name: string): boolean {\n return this.plugins.has(name);\n }\n\n private applyRoute(route: BufferedRoute): void {\n if (!this.routeTarget) return;\n this.routeTarget.addExtension((app) => {\n const method = route.method.toLowerCase() as keyof express.Application;\n if (typeof app[method] === \"function\") {\n (app[method] as (...a: unknown[]) => void)(\n route.path,\n ...route.handlers,\n );\n }\n });\n }\n\n private applyMiddleware(\n path: string,\n handlers: express.RequestHandler[],\n ): void {\n if (!this.routeTarget) return;\n this.routeTarget.addExtension((app) => {\n app.use(path, ...handlers);\n });\n }\n}\n\n/**\n * Type guard: checks whether a plugin implements the ToolProvider interface\n * and exposes the user-scoped `asUser` helper that the {@link Plugin} base\n * class provides. Narrowing to {@link ToolProviderPlugin} lets `executeTool`\n * call `asUser` without an unsafe cast.\n */\nexport function isToolProvider(plugin: unknown): plugin is ToolProviderPlugin {\n return (\n typeof plugin === \"object\" &&\n plugin !== null &&\n \"getAgentTools\" in plugin &&\n typeof (plugin as ToolProvider).getAgentTools === \"function\" &&\n \"executeAgentTool\" in plugin &&\n typeof (plugin as ToolProvider).executeAgentTool === \"function\" &&\n \"asUser\" in plugin &&\n typeof (plugin as { asUser?: unknown }).asUser === \"function\"\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin-context.js","names":[],"sources":["../../src/core/plugin-context.ts"],"sourcesContent":["import type express from \"express\";\nimport type { BasePlugin, IAppRequest, ToolProvider } from \"shared\";\nimport { createLogger } from \"../logging/logger\";\nimport { SpanStatusCode, TelemetryManager } from \"../telemetry\";\nimport { forwardAsyncErrors } from \"../utils/safe-handler\";\n\nconst logger = createLogger(\"plugin-context\");\n\ninterface BufferedRoute {\n method: string;\n path: string;\n handlers: express.RequestHandler[];\n}\n\ninterface RouteTarget {\n addExtension(fn: (app: express.Application) => void): void;\n}\n\n/**\n * A tool-provider plugin that also exposes user-scoped execution. Plugins\n * derived from {@link Plugin} satisfy this implicitly because `asUser` lives\n * on the base class. {@link isToolProvider} narrows to this shape so\n * `executeTool` can call `asUser` without an unsafe cast.\n */\ntype ToolProviderPlugin = BasePlugin &\n ToolProvider & { asUser: (req: IAppRequest) => ToolProvider };\n\n/**\n * Lifecycle events emitted through {@link PluginContext.emitLifecycle}.\n *\n * - `\"setup:complete\"` — emitted by AppKit core after every plugin's\n * `setup()` has finished.\n * - `\"server:ready\"` — emitted when the HTTP server is listening.\n * - `\"shutdown\"` — emitted by the core lifecycle manager during graceful\n * shutdown, AFTER all plugin `shutdown()` hooks have completed. The emit is\n * bounded by a short timeout (see the lifecycle manager's shutdown budget),\n * so subscribers must not start long-running async work — finish quickly or\n * be cut off. (The server plugin subscribes here to force-close its\n * remaining sockets once peers have drained.)\n */\ntype LifecycleEvent = \"setup:complete\" | \"server:ready\" | \"shutdown\";\n\n/**\n * Mediator for inter-plugin communication.\n *\n * Created by AppKit core and passed to every plugin. Plugins request\n * capabilities from the context instead of holding direct references\n * to sibling plugin instances.\n *\n * Capabilities:\n * - Route mounting with buffering (order-independent)\n * - Typed ToolProvider registry (live, not snapshot-based)\n * - User-scoped tool execution with automatic telemetry\n * - Lifecycle hooks for plugin coordination\n */\nexport class PluginContext {\n private routeBuffer: BufferedRoute[] = [];\n private routeTarget: RouteTarget | null = null;\n private toolProviders = new Map<string, ToolProviderPlugin>();\n private plugins = new Map<string, BasePlugin>();\n private lifecycleHooks = new Map<\n LifecycleEvent,\n Set<() => void | Promise<void>>\n >();\n private telemetry = TelemetryManager.getProvider(\"plugin-context\");\n\n /**\n * Register a route on the root Express application.\n *\n * If a route target (server plugin) has registered, the route is applied\n * immediately. Otherwise it is buffered and flushed when a route target\n * becomes available.\n */\n addRoute(\n method: string,\n path: string,\n ...handlers: express.RequestHandler[]\n ): void {\n if (this.routeTarget) {\n this.applyRoute({ method, path, handlers });\n } else {\n this.routeBuffer.push({ method, path, handlers });\n }\n }\n\n /**\n * Register middleware on the root Express application.\n *\n * Same buffering semantics as `addRoute`.\n */\n addMiddleware(path: string, ...handlers: express.RequestHandler[]): void {\n if (this.routeTarget) {\n this.applyMiddleware(path, handlers);\n } else {\n this.routeBuffer.push({ method: \"use\", path, handlers });\n }\n }\n\n /**\n * Called by the server plugin to opt in as the route target.\n * Flushes all buffered routes via the server's `addExtension`.\n *\n * Only the first caller wins — subsequent calls are ignored with a warning.\n * In practice only the server plugin registers, but a misconfigured app\n * (two server plugins, or duplicate AppKit setup in tests) would otherwise\n * silently drop the first target's later extensions.\n */\n registerAsRouteTarget(target: RouteTarget): void {\n if (this.routeTarget) {\n logger.warn(\n \"registerAsRouteTarget called more than once; ignoring duplicate registration\",\n );\n return;\n }\n this.routeTarget = target;\n\n for (const route of this.routeBuffer) {\n if (route.method === \"use\") {\n this.applyMiddleware(route.path, route.handlers);\n } else {\n this.applyRoute(route);\n }\n }\n this.routeBuffer = [];\n }\n\n /**\n * Register a plugin that implements the ToolProvider interface.\n * Called by AppKit core after constructing each plugin.\n *\n * Plugin names should be unique (they are derived from `manifest.name`).\n * A duplicate registration overwrites the previous entry and emits a\n * warning so the misconfiguration is visible in startup logs.\n */\n registerToolProvider(name: string, plugin: ToolProviderPlugin): void {\n if (this.toolProviders.has(name)) {\n logger.warn(\n 'Tool provider \"%s\" registered more than once; the previous registration is being overwritten',\n name,\n );\n }\n this.toolProviders.set(name, plugin);\n }\n\n /**\n * Register a plugin instance.\n * Called by AppKit core after constructing each plugin.\n */\n registerPlugin(name: string, instance: BasePlugin): void {\n this.plugins.set(name, instance);\n }\n\n /**\n * Returns all registered plugin instances keyed by name.\n * Used by the server plugin for route injection, client config,\n * and shutdown coordination. The returned map is read-only at the\n * type level — callers must not mutate the live registry.\n */\n getPlugins(): ReadonlyMap<string, BasePlugin> {\n return this.plugins;\n }\n\n /**\n * Returns all registered ToolProvider plugins.\n * Always returns the current set — not a frozen snapshot.\n */\n getToolProviders(): Array<{ name: string; provider: ToolProvider }> {\n return Array.from(this.toolProviders.entries()).map(([name, provider]) => ({\n name,\n provider,\n }));\n }\n\n /**\n * Execute a tool on a ToolProvider plugin with automatic user scoping\n * and telemetry.\n *\n * The context:\n * 1. Resolves the plugin by name\n * 2. Calls `asUser(req)` for user-scoped execution\n * 3. Wraps the call in a telemetry span with a configurable timeout\n *\n * @param timeoutMs Per-call timeout. Defaults to 5 minutes — the floor\n * for cold SQL Warehouse round-trips, long Genie conversations, and\n * busy serverless Lakebase queries. The agents plugin overrides this\n * per-app via `agents({ limits: { toolCallTimeoutMs } })`.\n */\n async executeTool(\n req: express.Request,\n pluginName: string,\n toolName: string,\n args: unknown,\n signal?: AbortSignal,\n timeoutMs: number = 300_000,\n ): Promise<unknown> {\n const provider = this.toolProviders.get(pluginName);\n if (!provider) {\n throw new Error(\n `PluginContext: unknown plugin \"${pluginName}\". Available: ${Array.from(this.toolProviders.keys()).join(\", \")}`,\n );\n }\n\n const tracer = this.telemetry.getTracer();\n const operationName = `executeTool:${pluginName}.${toolName}`;\n\n return tracer.startActiveSpan(operationName, async (span) => {\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n const combinedSignal = signal\n ? AbortSignal.any([signal, timeoutSignal])\n : timeoutSignal;\n\n try {\n const userScoped = provider.asUser(req);\n const result = await userScoped.executeAgentTool(\n toolName,\n args,\n combinedSignal,\n );\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message:\n error instanceof Error ? error.message : \"Tool execution failed\",\n });\n span.recordException(\n error instanceof Error ? error : new Error(String(error)),\n );\n throw error;\n } finally {\n span.end();\n }\n });\n }\n\n /**\n * Register a lifecycle hook callback.\n *\n * See {@link LifecycleEvent} for event semantics. In particular,\n * `\"shutdown\"` subscribers run inside a bounded shutdown phase and must\n * not start long-running async work.\n */\n onLifecycle(event: LifecycleEvent, fn: () => void | Promise<void>): void {\n let hooks = this.lifecycleHooks.get(event);\n if (!hooks) {\n hooks = new Set();\n this.lifecycleHooks.set(event, hooks);\n }\n hooks.add(fn);\n }\n\n /**\n * Emit a lifecycle event, calling all registered callbacks.\n * Errors in individual callbacks are logged but do not prevent\n * other callbacks from running.\n *\n * @internal Called by AppKit core: `setup:complete` after plugin setup,\n * and `shutdown` by the lifecycle manager during graceful shutdown.\n */\n async emitLifecycle(event: LifecycleEvent): Promise<void> {\n const hooks = this.lifecycleHooks.get(event);\n if (!hooks) return;\n\n if (\n event === \"setup:complete\" &&\n this.routeBuffer.length > 0 &&\n !this.routeTarget\n ) {\n logger.warn(\n \"%d buffered routes were never applied — no server plugin registered as route target\",\n this.routeBuffer.length,\n );\n }\n\n // Snapshot before iterating so a callback that registers a new hook for\n // the same event does not mutate the loop. ECMAScript Set iteration would\n // otherwise visit late-added entries, risking unexpected re-entry.\n for (const fn of [...hooks]) {\n try {\n await fn();\n } catch (error) {\n logger.error(\"Lifecycle hook '%s' failed: %O\", event, error);\n }\n }\n }\n\n /**\n * Returns all registered plugin names.\n */\n getPluginNames(): string[] {\n return Array.from(this.plugins.keys());\n }\n\n /**\n * Check if a plugin with the given name is registered.\n */\n hasPlugin(name: string): boolean {\n return this.plugins.has(name);\n }\n\n private applyRoute(route: BufferedRoute): void {\n if (!this.routeTarget) return;\n this.routeTarget.addExtension((app) => {\n const method = route.method.toLowerCase() as keyof express.Application;\n if (typeof app[method] === \"function\") {\n (app[method] as (...a: unknown[]) => void)(\n route.path,\n ...route.handlers.map(forwardAsyncErrors),\n );\n }\n });\n }\n\n private applyMiddleware(\n path: string,\n handlers: express.RequestHandler[],\n ): void {\n if (!this.routeTarget) return;\n this.routeTarget.addExtension((app) => {\n app.use(path, ...handlers.map(forwardAsyncErrors));\n });\n }\n}\n\n/**\n * Type guard: checks whether a plugin implements the ToolProvider interface\n * and exposes the user-scoped `asUser` helper that the {@link Plugin} base\n * class provides. Narrowing to {@link ToolProviderPlugin} lets `executeTool`\n * call `asUser` without an unsafe cast.\n */\nexport function isToolProvider(plugin: unknown): plugin is ToolProviderPlugin {\n return (\n typeof plugin === \"object\" &&\n plugin !== null &&\n \"getAgentTools\" in plugin &&\n typeof (plugin as ToolProvider).getAgentTools === \"function\" &&\n \"executeAgentTool\" in plugin &&\n typeof (plugin as ToolProvider).executeAgentTool === \"function\" &&\n \"asUser\" in plugin &&\n typeof (plugin as { asUser?: unknown }).asUser === \"function\"\n );\n}\n"],"mappings":"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"}
|
package/dist/errors/base.d.ts
CHANGED
|
@@ -31,10 +31,28 @@ declare abstract class AppKitError extends Error {
|
|
|
31
31
|
readonly cause?: Error;
|
|
32
32
|
/** Additional context for the error */
|
|
33
33
|
readonly context?: Record<string, unknown>;
|
|
34
|
+
/**
|
|
35
|
+
* Client-safe error message. When set, callers serializing the error to
|
|
36
|
+
* a client (SSE, HTTP body) MUST prefer `clientMessage` over `message`
|
|
37
|
+
* — `message` may contain raw upstream / SDK text including statement
|
|
38
|
+
* fragments, internal object names, and correlation IDs.
|
|
39
|
+
*
|
|
40
|
+
* Subclasses can set this in their constructor for a fixed sanitized
|
|
41
|
+
* string. When unset, `clientMessage` defaults to a generic per-code
|
|
42
|
+
* string (see the getter), and the raw `message` is kept server-side
|
|
43
|
+
* only.
|
|
44
|
+
*/
|
|
45
|
+
protected readonly _clientMessage?: string;
|
|
34
46
|
constructor(message: string, options?: {
|
|
35
47
|
cause?: Error;
|
|
36
48
|
context?: Record<string, unknown>;
|
|
49
|
+
clientMessage?: string;
|
|
37
50
|
});
|
|
51
|
+
/**
|
|
52
|
+
* Sanitized message safe to forward to clients. Override in subclasses
|
|
53
|
+
* if a more specific default is appropriate.
|
|
54
|
+
*/
|
|
55
|
+
get clientMessage(): string;
|
|
38
56
|
/**
|
|
39
57
|
* Convert error to JSON for logging/serialization.
|
|
40
58
|
* Sensitive values in context are automatically redacted.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","names":[],"sources":["../../src/errors/base.ts"],"mappings":";;AAgCA;;;;;;;;;;;;;;;;;;;;uBAAsB,WAAA,SAAoB,KAAA;
|
|
1
|
+
{"version":3,"file":"base.d.ts","names":[],"sources":["../../src/errors/base.ts"],"mappings":";;AAgCA;;;;;;;;;;;;;;;;;;;;uBAAsB,WAAA,SAAoB,KAAA;EA8BtC;EAAA,kBA5BgB,IAAA;EA8BN;EAAA,kBA3BM,UAAA;EA4BJ;EAAA,kBAzBI,WAAA;EAuBhB;EAAA,SApBO,KAAA,GAAQ,KAAA;EAkDjB;EAAA,SA/CS,OAAA,GAAU,MAAA;EA+DnB;;;;;;;;;;;EAAA,mBAlDmB,cAAA;cAGjB,OAAA,UACA,OAAA;IACE,KAAA,GAAQ,KAAA;IACR,OAAA,GAAU,MAAA;IACV,aAAA;EAAA;;;;;MAmBA,aAAA,CAAA;;;;;EAQJ,MAAA,CAAA,GAAU,MAAA;;;;EAgBV,QAAA,CAAA;;;;UAWQ,eAAA;;;;UAwBA,gBAAA;AAAA"}
|
package/dist/errors/base.js
CHANGED
|
@@ -35,14 +35,34 @@ var AppKitError = class extends Error {
|
|
|
35
35
|
cause;
|
|
36
36
|
/** Additional context for the error */
|
|
37
37
|
context;
|
|
38
|
+
/**
|
|
39
|
+
* Client-safe error message. When set, callers serializing the error to
|
|
40
|
+
* a client (SSE, HTTP body) MUST prefer `clientMessage` over `message`
|
|
41
|
+
* — `message` may contain raw upstream / SDK text including statement
|
|
42
|
+
* fragments, internal object names, and correlation IDs.
|
|
43
|
+
*
|
|
44
|
+
* Subclasses can set this in their constructor for a fixed sanitized
|
|
45
|
+
* string. When unset, `clientMessage` defaults to a generic per-code
|
|
46
|
+
* string (see the getter), and the raw `message` is kept server-side
|
|
47
|
+
* only.
|
|
48
|
+
*/
|
|
49
|
+
_clientMessage;
|
|
38
50
|
constructor(message, options) {
|
|
39
51
|
super(message);
|
|
40
52
|
this.name = this.constructor.name;
|
|
41
53
|
this.cause = options?.cause;
|
|
42
54
|
this.context = options?.context;
|
|
55
|
+
this._clientMessage = options?.clientMessage;
|
|
43
56
|
if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
|
|
44
57
|
}
|
|
45
58
|
/**
|
|
59
|
+
* Sanitized message safe to forward to clients. Override in subclasses
|
|
60
|
+
* if a more specific default is appropriate.
|
|
61
|
+
*/
|
|
62
|
+
get clientMessage() {
|
|
63
|
+
return this._clientMessage ?? "An internal error occurred";
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
46
66
|
* Convert error to JSON for logging/serialization.
|
|
47
67
|
* Sensitive values in context are automatically redacted.
|
|
48
68
|
*/
|
package/dist/errors/base.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.js","names":[],"sources":["../../src/errors/base.ts"],"sourcesContent":["/** Fields that should be redacted in logs/serialization for security */\nconst SENSITIVE_FIELD_PATTERNS = [\n /token/i,\n /password/i,\n /secret/i,\n /credential/i,\n /auth/i,\n /key$/i,\n /apikey/i,\n];\n\n/**\n * Base error class for all AppKit errors.\n * Provides a consistent structure for error handling across the framework.\n *\n * @example\n * ```typescript\n * // Catching errors by type\n * try {\n * await lakebase.query(\"...\");\n * } catch (e) {\n * if (e instanceof AuthenticationError) {\n * // Re-authenticate\n * } else if (e instanceof ConnectionError && e.isRetryable) {\n * // Retry with backoff\n * }\n * }\n *\n * // Logging errors\n * console.error(error.toJSON()); // Safe for logging, sensitive values redacted\n * ```\n */\nexport abstract class AppKitError extends Error {\n /** Error code for programmatic error handling */\n abstract readonly code: string;\n\n /** HTTP status code suggestion (can be overridden) */\n abstract readonly statusCode: number;\n\n /** Whether this error type is generally safe to retry */\n abstract readonly isRetryable: boolean;\n\n /** Optional cause of the error */\n readonly cause?: Error;\n\n /** Additional context for the error */\n readonly context?: Record<string, unknown>;\n\n constructor(\n message: string,\n options?: {
|
|
1
|
+
{"version":3,"file":"base.js","names":[],"sources":["../../src/errors/base.ts"],"sourcesContent":["/** Fields that should be redacted in logs/serialization for security */\nconst SENSITIVE_FIELD_PATTERNS = [\n /token/i,\n /password/i,\n /secret/i,\n /credential/i,\n /auth/i,\n /key$/i,\n /apikey/i,\n];\n\n/**\n * Base error class for all AppKit errors.\n * Provides a consistent structure for error handling across the framework.\n *\n * @example\n * ```typescript\n * // Catching errors by type\n * try {\n * await lakebase.query(\"...\");\n * } catch (e) {\n * if (e instanceof AuthenticationError) {\n * // Re-authenticate\n * } else if (e instanceof ConnectionError && e.isRetryable) {\n * // Retry with backoff\n * }\n * }\n *\n * // Logging errors\n * console.error(error.toJSON()); // Safe for logging, sensitive values redacted\n * ```\n */\nexport abstract class AppKitError extends Error {\n /** Error code for programmatic error handling */\n abstract readonly code: string;\n\n /** HTTP status code suggestion (can be overridden) */\n abstract readonly statusCode: number;\n\n /** Whether this error type is generally safe to retry */\n abstract readonly isRetryable: boolean;\n\n /** Optional cause of the error */\n readonly cause?: Error;\n\n /** Additional context for the error */\n readonly context?: Record<string, unknown>;\n\n /**\n * Client-safe error message. When set, callers serializing the error to\n * a client (SSE, HTTP body) MUST prefer `clientMessage` over `message`\n * — `message` may contain raw upstream / SDK text including statement\n * fragments, internal object names, and correlation IDs.\n *\n * Subclasses can set this in their constructor for a fixed sanitized\n * string. When unset, `clientMessage` defaults to a generic per-code\n * string (see the getter), and the raw `message` is kept server-side\n * only.\n */\n protected readonly _clientMessage?: string;\n\n constructor(\n message: string,\n options?: {\n cause?: Error;\n context?: Record<string, unknown>;\n clientMessage?: string;\n },\n ) {\n super(message);\n this.name = this.constructor.name;\n this.cause = options?.cause;\n this.context = options?.context;\n this._clientMessage = options?.clientMessage;\n\n // Maintains proper stack trace for where the error was thrown\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n\n /**\n * Sanitized message safe to forward to clients. Override in subclasses\n * if a more specific default is appropriate.\n */\n get clientMessage(): string {\n return this._clientMessage ?? \"An internal error occurred\";\n }\n\n /**\n * Convert error to JSON for logging/serialization.\n * Sensitive values in context are automatically redacted.\n */\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n statusCode: this.statusCode,\n isRetryable: this.isRetryable,\n context: this.sanitizeContext(this.context),\n cause: this.cause?.message,\n stack: this.stack,\n };\n }\n\n /**\n * Create a human-readable string representation\n */\n toString(): string {\n let str = `${this.name} [${this.code}]: ${this.message}`;\n if (this.cause) {\n str += ` (caused by: ${this.cause.message})`;\n }\n return str;\n }\n\n /**\n * Sanitize context by redacting sensitive field values\n */\n private sanitizeContext(\n context?: Record<string, unknown>,\n ): Record<string, unknown> | undefined {\n if (!context) return undefined;\n\n const sanitized: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(context)) {\n if (this.isSensitiveField(key)) {\n sanitized[key] = \"[REDACTED]\";\n } else if (typeof value === \"object\" && value !== null) {\n // Don't deep-sanitize nested objects, just indicate their type\n sanitized[key] = Array.isArray(value)\n ? `[Array(${value.length})]`\n : \"[Object]\";\n } else {\n sanitized[key] = value;\n }\n }\n return sanitized;\n }\n\n /**\n * Check if a field name matches sensitive patterns\n */\n private isSensitiveField(fieldName: string): boolean {\n return SENSITIVE_FIELD_PATTERNS.some((pattern) => pattern.test(fieldName));\n }\n}\n"],"mappings":";;AACA,MAAM,2BAA2B;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;;;;;;;;;;;;;;;AAuBD,IAAsB,cAAtB,cAA0C,MAAM;;CAW9C,AAAS;;CAGT,AAAS;;;;;;;;;;;;CAaT,AAAmB;CAEnB,YACE,SACA,SAKA;AACA,QAAM,QAAQ;AACd,OAAK,OAAO,KAAK,YAAY;AAC7B,OAAK,QAAQ,SAAS;AACtB,OAAK,UAAU,SAAS;AACxB,OAAK,iBAAiB,SAAS;AAG/B,MAAI,MAAM,kBACR,OAAM,kBAAkB,MAAM,KAAK,YAAY;;;;;;CAQnD,IAAI,gBAAwB;AAC1B,SAAO,KAAK,kBAAkB;;;;;;CAOhC,SAAkC;AAChC,SAAO;GACL,MAAM,KAAK;GACX,MAAM,KAAK;GACX,SAAS,KAAK;GACd,YAAY,KAAK;GACjB,aAAa,KAAK;GAClB,SAAS,KAAK,gBAAgB,KAAK,QAAQ;GAC3C,OAAO,KAAK,OAAO;GACnB,OAAO,KAAK;GACb;;;;;CAMH,WAAmB;EACjB,IAAI,MAAM,GAAG,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK;AAC/C,MAAI,KAAK,MACP,QAAO,gBAAgB,KAAK,MAAM,QAAQ;AAE5C,SAAO;;;;;CAMT,AAAQ,gBACN,SACqC;AACrC,MAAI,CAAC,QAAS,QAAO;EAErB,MAAM,YAAqC,EAAE;AAC7C,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,CAChD,KAAI,KAAK,iBAAiB,IAAI,CAC5B,WAAU,OAAO;WACR,OAAO,UAAU,YAAY,UAAU,KAEhD,WAAU,OAAO,MAAM,QAAQ,MAAM,GACjC,UAAU,MAAM,OAAO,MACvB;MAEJ,WAAU,OAAO;AAGrB,SAAO;;;;;CAMT,AAAQ,iBAAiB,WAA4B;AACnD,SAAO,yBAAyB,MAAM,YAAY,QAAQ,KAAK,UAAU,CAAC"}
|
|
@@ -16,9 +16,41 @@ declare class ExecutionError extends AppKitError {
|
|
|
16
16
|
readonly statusCode = 500;
|
|
17
17
|
readonly isRetryable = false;
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* Structured error code from the upstream source (typically the warehouse's
|
|
20
|
+
* `error_code` for statement-level failures, or the SDK's `ApiError.errorCode`
|
|
21
|
+
* for HTTP failures). Preserved through wrapping so callers can branch on a
|
|
22
|
+
* stable identifier without substring-matching the message.
|
|
20
23
|
*/
|
|
21
|
-
|
|
24
|
+
readonly errorCode?: string;
|
|
25
|
+
constructor(message: string, options?: {
|
|
26
|
+
cause?: Error;
|
|
27
|
+
context?: Record<string, unknown>;
|
|
28
|
+
errorCode?: string;
|
|
29
|
+
clientMessage?: string;
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* Execution errors default to a generic message — the raw warehouse /
|
|
33
|
+
* SDK text in `.message` often includes statement fragments, internal
|
|
34
|
+
* paths, and correlation IDs. UI code should branch on `errorCode`
|
|
35
|
+
* (`RESULT_TOO_LARGE_FOR_JSON_FALLBACK`, `NOT_IMPLEMENTED`, etc.) and not on
|
|
36
|
+
* the human string.
|
|
37
|
+
*/
|
|
38
|
+
get clientMessage(): string;
|
|
39
|
+
/**
|
|
40
|
+
* Create an execution error for statement failure.
|
|
41
|
+
* @param errorMessage Human-readable error from the warehouse / SDK.
|
|
42
|
+
* Goes into `.message` for server logs only — *never* echoed to the
|
|
43
|
+
* client. Pass `clientMessage` explicitly if a sanitized text should
|
|
44
|
+
* reach the UI.
|
|
45
|
+
* @param errorCode Structured code (e.g. "INVALID_PARAMETER_VALUE") to
|
|
46
|
+
* preserve through wrapping. Optional. Forwarded on SSE error
|
|
47
|
+
* payloads so UI can branch on it instead of substring-matching
|
|
48
|
+
* `error`.
|
|
49
|
+
* @param clientMessage Optional client-safe replacement for `.message`.
|
|
50
|
+
* Defaults to "Query execution failed" via the `clientMessage`
|
|
51
|
+
* getter. Set this only when the upstream text is known-safe.
|
|
52
|
+
*/
|
|
53
|
+
static statementFailed(errorMessage?: string, errorCode?: string, clientMessage?: string): ExecutionError;
|
|
22
54
|
/**
|
|
23
55
|
* Create an execution error for canceled operation
|
|
24
56
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execution.d.ts","names":[],"sources":["../../src/errors/execution.ts"],"mappings":";;;;;AAYA;;;;;;;;cAAa,cAAA,SAAuB,WAAA;EAAA,SACzB,IAAA;EAAA,SACA,UAAA;EAAA,SACA,WAAA;
|
|
1
|
+
{"version":3,"file":"execution.d.ts","names":[],"sources":["../../src/errors/execution.ts"],"mappings":";;;;;AAYA;;;;;;;;cAAa,cAAA,SAAuB,WAAA;EAAA,SACzB,IAAA;EAAA,SACA,UAAA;EAAA,SACA,WAAA;EAHoC;;;;;;EAAA,SAWpC,SAAA;cAGP,OAAA,UACA,OAAA;IACE,KAAA,GAAQ,KAAA;IACR,OAAA,GAAU,MAAA;IACV,SAAA;IACA,aAAA;EAAA;EAAA;;;;;;;EAAA,IAcS,aAAA,CAAA;EAgCN;;;;;;;;;;;;;;EAAA,OAdA,eAAA,CACL,YAAA,WACA,SAAA,WACA,aAAA,YACC,cAAA;;;;SAUI,QAAA,CAAA,GAAY,cAAA;;;;SASZ,aAAA,CAAA,GAAiB,cAAA;;;;SAUjB,YAAA,CAAa,KAAA,WAAgB,cAAA;;;;SAS7B,WAAA,CAAY,QAAA,WAAmB,cAAA;AAAA"}
|
package/dist/errors/execution.js
CHANGED
|
@@ -16,22 +16,57 @@ var ExecutionError = class ExecutionError extends AppKitError {
|
|
|
16
16
|
statusCode = 500;
|
|
17
17
|
isRetryable = false;
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* Structured error code from the upstream source (typically the warehouse's
|
|
20
|
+
* `error_code` for statement-level failures, or the SDK's `ApiError.errorCode`
|
|
21
|
+
* for HTTP failures). Preserved through wrapping so callers can branch on a
|
|
22
|
+
* stable identifier without substring-matching the message.
|
|
20
23
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
errorCode;
|
|
25
|
+
constructor(message, options) {
|
|
26
|
+
super(message, options);
|
|
27
|
+
this.errorCode = options?.errorCode;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Execution errors default to a generic message — the raw warehouse /
|
|
31
|
+
* SDK text in `.message` often includes statement fragments, internal
|
|
32
|
+
* paths, and correlation IDs. UI code should branch on `errorCode`
|
|
33
|
+
* (`RESULT_TOO_LARGE_FOR_JSON_FALLBACK`, `NOT_IMPLEMENTED`, etc.) and not on
|
|
34
|
+
* the human string.
|
|
35
|
+
*/
|
|
36
|
+
get clientMessage() {
|
|
37
|
+
return this._clientMessage ?? "Query execution failed";
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create an execution error for statement failure.
|
|
41
|
+
* @param errorMessage Human-readable error from the warehouse / SDK.
|
|
42
|
+
* Goes into `.message` for server logs only — *never* echoed to the
|
|
43
|
+
* client. Pass `clientMessage` explicitly if a sanitized text should
|
|
44
|
+
* reach the UI.
|
|
45
|
+
* @param errorCode Structured code (e.g. "INVALID_PARAMETER_VALUE") to
|
|
46
|
+
* preserve through wrapping. Optional. Forwarded on SSE error
|
|
47
|
+
* payloads so UI can branch on it instead of substring-matching
|
|
48
|
+
* `error`.
|
|
49
|
+
* @param clientMessage Optional client-safe replacement for `.message`.
|
|
50
|
+
* Defaults to "Query execution failed" via the `clientMessage`
|
|
51
|
+
* getter. Set this only when the upstream text is known-safe.
|
|
52
|
+
*/
|
|
53
|
+
static statementFailed(errorMessage, errorCode, clientMessage) {
|
|
54
|
+
return new ExecutionError(errorMessage ? `Statement failed: ${errorMessage}` : "Statement failed: Unknown error", {
|
|
55
|
+
errorCode,
|
|
56
|
+
clientMessage
|
|
57
|
+
});
|
|
23
58
|
}
|
|
24
59
|
/**
|
|
25
60
|
* Create an execution error for canceled operation
|
|
26
61
|
*/
|
|
27
62
|
static canceled() {
|
|
28
|
-
return new ExecutionError("Statement was canceled");
|
|
63
|
+
return new ExecutionError("Statement was canceled", { clientMessage: "Query was canceled" });
|
|
29
64
|
}
|
|
30
65
|
/**
|
|
31
66
|
* Create an execution error for closed/expired results
|
|
32
67
|
*/
|
|
33
68
|
static resultsClosed() {
|
|
34
|
-
return new ExecutionError("Statement execution completed but results are no longer available (CLOSED state)");
|
|
69
|
+
return new ExecutionError("Statement execution completed but results are no longer available (CLOSED state)", { clientMessage: "Query results expired" });
|
|
35
70
|
}
|
|
36
71
|
/**
|
|
37
72
|
* Create an execution error for unknown state
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execution.js","names":[],"sources":["../../src/errors/execution.ts"],"sourcesContent":["import { AppKitError } from \"./base\";\n\n/**\n * Error thrown when an operation execution fails.\n * Use for statement failures, canceled operations, or unexpected states.\n *\n * @example\n * ```typescript\n * throw new ExecutionError(\"Statement failed: syntax error\");\n * throw new ExecutionError(\"Statement was canceled\");\n * ```\n */\nexport class ExecutionError extends AppKitError {\n readonly code = \"EXECUTION_ERROR\";\n readonly statusCode = 500;\n readonly isRetryable = false;\n\n /**\n * Create an execution error for statement failure\n */\n static statementFailed(errorMessage?: string): ExecutionError {\n const message = errorMessage\n ? `Statement failed: ${errorMessage}`\n : \"Statement failed: Unknown error\";\n return new ExecutionError(message);\n }\n\n /**\n * Create an execution error for canceled operation\n */\n static canceled(): ExecutionError {\n return new ExecutionError(\"Statement was canceled\");\n }\n\n /**\n * Create an execution error for closed/expired results\n */\n static resultsClosed(): ExecutionError {\n return new ExecutionError(\n \"Statement execution completed but results are no longer available (CLOSED state)\",\n );\n }\n\n /**\n * Create an execution error for unknown state\n */\n static unknownState(state: string): ExecutionError {\n return new ExecutionError(`Unknown statement state: ${state}`, {\n context: { state },\n });\n }\n\n /**\n * Create an execution error for missing data\n */\n static missingData(dataType: string): ExecutionError {\n return new ExecutionError(`No ${dataType} found in response`, {\n context: { dataType },\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,IAAa,iBAAb,MAAa,uBAAuB,YAAY;CAC9C,AAAS,OAAO;CAChB,AAAS,aAAa;CACtB,AAAS,cAAc
|
|
1
|
+
{"version":3,"file":"execution.js","names":[],"sources":["../../src/errors/execution.ts"],"sourcesContent":["import { AppKitError } from \"./base\";\n\n/**\n * Error thrown when an operation execution fails.\n * Use for statement failures, canceled operations, or unexpected states.\n *\n * @example\n * ```typescript\n * throw new ExecutionError(\"Statement failed: syntax error\");\n * throw new ExecutionError(\"Statement was canceled\");\n * ```\n */\nexport class ExecutionError extends AppKitError {\n readonly code = \"EXECUTION_ERROR\";\n readonly statusCode = 500;\n readonly isRetryable = false;\n\n /**\n * Structured error code from the upstream source (typically the warehouse's\n * `error_code` for statement-level failures, or the SDK's `ApiError.errorCode`\n * for HTTP failures). Preserved through wrapping so callers can branch on a\n * stable identifier without substring-matching the message.\n */\n readonly errorCode?: string;\n\n constructor(\n message: string,\n options?: {\n cause?: Error;\n context?: Record<string, unknown>;\n errorCode?: string;\n clientMessage?: string;\n },\n ) {\n super(message, options);\n this.errorCode = options?.errorCode;\n }\n\n /**\n * Execution errors default to a generic message — the raw warehouse /\n * SDK text in `.message` often includes statement fragments, internal\n * paths, and correlation IDs. UI code should branch on `errorCode`\n * (`RESULT_TOO_LARGE_FOR_JSON_FALLBACK`, `NOT_IMPLEMENTED`, etc.) and not on\n * the human string.\n */\n override get clientMessage(): string {\n return this._clientMessage ?? \"Query execution failed\";\n }\n\n /**\n * Create an execution error for statement failure.\n * @param errorMessage Human-readable error from the warehouse / SDK.\n * Goes into `.message` for server logs only — *never* echoed to the\n * client. Pass `clientMessage` explicitly if a sanitized text should\n * reach the UI.\n * @param errorCode Structured code (e.g. \"INVALID_PARAMETER_VALUE\") to\n * preserve through wrapping. Optional. Forwarded on SSE error\n * payloads so UI can branch on it instead of substring-matching\n * `error`.\n * @param clientMessage Optional client-safe replacement for `.message`.\n * Defaults to \"Query execution failed\" via the `clientMessage`\n * getter. Set this only when the upstream text is known-safe.\n */\n static statementFailed(\n errorMessage?: string,\n errorCode?: string,\n clientMessage?: string,\n ): ExecutionError {\n const message = errorMessage\n ? `Statement failed: ${errorMessage}`\n : \"Statement failed: Unknown error\";\n return new ExecutionError(message, { errorCode, clientMessage });\n }\n\n /**\n * Create an execution error for canceled operation\n */\n static canceled(): ExecutionError {\n return new ExecutionError(\"Statement was canceled\", {\n clientMessage: \"Query was canceled\",\n });\n }\n\n /**\n * Create an execution error for closed/expired results\n */\n static resultsClosed(): ExecutionError {\n return new ExecutionError(\n \"Statement execution completed but results are no longer available (CLOSED state)\",\n { clientMessage: \"Query results expired\" },\n );\n }\n\n /**\n * Create an execution error for unknown state\n */\n static unknownState(state: string): ExecutionError {\n return new ExecutionError(`Unknown statement state: ${state}`, {\n context: { state },\n });\n }\n\n /**\n * Create an execution error for missing data\n */\n static missingData(dataType: string): ExecutionError {\n return new ExecutionError(`No ${dataType} found in response`, {\n context: { dataType },\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,IAAa,iBAAb,MAAa,uBAAuB,YAAY;CAC9C,AAAS,OAAO;CAChB,AAAS,aAAa;CACtB,AAAS,cAAc;;;;;;;CAQvB,AAAS;CAET,YACE,SACA,SAMA;AACA,QAAM,SAAS,QAAQ;AACvB,OAAK,YAAY,SAAS;;;;;;;;;CAU5B,IAAa,gBAAwB;AACnC,SAAO,KAAK,kBAAkB;;;;;;;;;;;;;;;;CAiBhC,OAAO,gBACL,cACA,WACA,eACgB;AAIhB,SAAO,IAAI,eAHK,eACZ,qBAAqB,iBACrB,mCAC+B;GAAE;GAAW;GAAe,CAAC;;;;;CAMlE,OAAO,WAA2B;AAChC,SAAO,IAAI,eAAe,0BAA0B,EAClD,eAAe,sBAChB,CAAC;;;;;CAMJ,OAAO,gBAAgC;AACrC,SAAO,IAAI,eACT,oFACA,EAAE,eAAe,yBAAyB,CAC3C;;;;;CAMH,OAAO,aAAa,OAA+B;AACjD,SAAO,IAAI,eAAe,4BAA4B,SAAS,EAC7D,SAAS,EAAE,OAAO,EACnB,CAAC;;;;;CAMJ,OAAO,YAAY,UAAkC;AACnD,SAAO,IAAI,eAAe,MAAM,SAAS,qBAAqB,EAC5D,SAAS,EAAE,UAAU,EACtB,CAAC"}
|
|
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAwNsB,MAAA,iBACJ,gBAAA,GAAmB,gBAAA,aACxB,UAAA;EAAA,UA6BW,MAAA,EAAQ,OAAA;EAAA,UA3BpB,OAAA;EAAA,UACA,KAAA,EAAQ,YAAA;EAAA,UACR,GAAA,EAAK,UAAA;EAAA,UACL,aAAA,EAAe,aAAA;EAAA,UACf,aAAA,EAAe,aAAA;EAAA,UACf,SAAA,EAAY,UAAA;EAAA,UACZ,OAAA,GAAU,aAAA;EAwSO;EAAA,QArSnB,mBAAA;EAsSG;EAAA,QAnSH,oBAAA;EAoSN;;;;;;EAAA,OA5RK,KAAA,EAAO,WAAA;EA6W0B;;;EAxWxC,IAAA;cAEsB,MAAA,EAAQ,OAAA;EAAA,QAoBtB,gBAAA;EAqVG;;;;;;;;EAhUX,aAAA,CACE,IAAA;IACE,OAAA;IACA,eAAA,GAAkB,gBAAA;EAAA;EAgBtB,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;EA8ayB;;;;;;;;;;;;;;;;;;;;;;;;EAlZzB,OAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiDA,YAAA,CAAA,GAAgB,MAAA;;;;;;;;;;;YAcN,aAAA,CAAc,GAAA,EAAK,OAAA,CAAQ,OAAA;;;;;;;;;;;EAiBrC,MAAA,CAAO,GAAA,EAAK,OAAA,CAAQ,OAAA;;;;;;;;;;;;;;UAyDZ,kBAAA;EAAA,UAkCQ,aAAA,GAAA,CACd,GAAA,EAAK,YAAA,EACL,EAAA,EAAI,oBAAA,CAAqB,CAAA,GACzB,OAAA,EAAS,uBAAA,EACT,OAAA,YAAgB,OAAA;;;;;;;;;;YAgFF,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
|
@@ -13,6 +13,7 @@ import "../context/index.js";
|
|
|
13
13
|
import { AppManager } from "../app/index.js";
|
|
14
14
|
import { StreamManager } from "../stream/stream-manager.js";
|
|
15
15
|
import "../stream/index.js";
|
|
16
|
+
import { forwardAsyncErrors } from "../utils/safe-handler.js";
|
|
16
17
|
import { DevFileReader } from "./dev-reader.js";
|
|
17
18
|
import { CacheInterceptor } from "./interceptors/cache.js";
|
|
18
19
|
import { RetryInterceptor } from "./interceptors/retry.js";
|
|
@@ -464,7 +465,7 @@ var Plugin = class {
|
|
|
464
465
|
}
|
|
465
466
|
route(router, config) {
|
|
466
467
|
const { name, method, path, handler } = config;
|
|
467
|
-
router[method](path, handler);
|
|
468
|
+
router[method](path, forwardAsyncErrors(handler));
|
|
468
469
|
const fullPath = `/api/${this.name}${path}`;
|
|
469
470
|
this.registerEndpoint(name, fullPath);
|
|
470
471
|
if (config.skipBodyParsing) this.skipBodyParsingPaths.add(fullPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","names":["otelContext","context"],"sources":["../../src/plugin/plugin.ts"],"sourcesContent":["import { createContextKey, context as otelContext } from \"@opentelemetry/api\";\nimport 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 { getCurrentUserId, runInUserContext, ServiceContext } from \"../context\";\nimport type { PluginContext } from \"../core/plugin-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 * OTel context key for marking OBO dev mode fallback.\n * Set when asUser() is called in development mode without a user token.\n */\nconst DEV_OBO_FALLBACK_KEY = createContextKey(\"appkit.devOboFallback\");\n\n/**\n * Returns true if `value` is a plain object literal (not an array, Date,\n * class instance, etc.). Used to decide whether to recurse into nested\n * export shapes when wrapping functions.\n *\n * @internal exported so the AppKit core can reuse the same predicate for\n * its `bindExportMethods` walk; not part of the public package surface.\n */\nexport function isPlainObject(\n value: unknown,\n): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Returns a deep copy of `exports` where every function has been replaced\n * with `wrap(fn)`, walking into nested plain objects.\n *\n * Used by the asUser proxy to make the user context follow function\n * references that escape the proxy via `exports()`. The original input is\n * not mutated, so plugins that memoize `exports()` are safe — each call\n * through the proxy yields an independent, freshly wrapped view.\n */\nfunction wrapExportFunctions(\n exports: Record<string, unknown>,\n wrap: (fn: (...a: unknown[]) => unknown) => (...a: unknown[]) => unknown,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(exports)) {\n const val = exports[key];\n if (typeof val === \"function\") {\n result[key] = wrap(val as (...a: unknown[]) => unknown);\n } else if (isPlainObject(val)) {\n result[key] = wrapExportFunctions(val, wrap);\n } else {\n result[key] = val;\n }\n }\n return result;\n}\n\n/**\n * Returns true if the current execution is an OBO dev mode fallback\n * (asUser() was called but fell back to service principal due to missing token).\n */\nexport function isDevOboFallback(): boolean {\n return otelContext.active().getValue(DEV_OBO_FALLBACK_KEY) === true;\n}\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 \"attachContext\",\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 protected context?: PluginContext;\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.streamManager = new StreamManager();\n this.app = new AppManager();\n this.devFileReader = DevFileReader.getInstance();\n this.context = (config as Record<string, unknown>).context as\n | PluginContext\n | undefined;\n\n // Eagerly bind telemetry + cache if the core services have already been\n // initialized (normal createApp path, or tests that mock CacheManager).\n // If they haven't, we leave these undefined and rely on `attachContext`\n // being called later — this lets factories eagerly construct plugin\n // instances at module top-level before `createApp` has run.\n this.tryAttachContext();\n }\n\n private tryAttachContext(): void {\n try {\n this.cache = CacheManager.getInstanceSync();\n } catch {\n return;\n }\n this.telemetry = TelemetryManager.getProvider(\n this.name,\n this.config.telemetry,\n );\n this.isReady = true;\n }\n\n /**\n * Binds runtime dependencies (telemetry provider, cache, plugin context) to\n * this plugin. Called by `AppKit._createApp` after construction and before\n * `setup()`. Idempotent: safe to call if the constructor already bound them\n * eagerly. Kept separate so factories can eagerly construct plugin instances\n * without running this before `TelemetryManager.initialize()` /\n * `CacheManager.getInstance()` have run.\n */\n attachContext(\n deps: {\n context?: unknown;\n telemetryConfig?: BasePluginConfig[\"telemetry\"];\n } = {},\n ): void {\n if (!this.cache) {\n this.cache = CacheManager.getInstanceSync();\n }\n this.telemetry = TelemetryManager.getProvider(\n this.name,\n deps.telemetryConfig ?? this.config.telemetry,\n );\n if (deps.context !== undefined) {\n this.context = deps.context as PluginContext;\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\")?.trim();\n if (userId) return userId;\n if (process.env.NODE_ENV === \"development\") return getCurrentUserId();\n throw AuthenticationError.missingUserId();\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\")?.trim();\n const userId = req.header(\"x-forwarded-user\")?.trim();\n const userEmail = req.header(\"x-forwarded-email\");\n const isDev = process.env.NODE_ENV === \"development\";\n\n // In local development, skip user impersonation since there's no user\n // token available. Mark execution as OBO dev fallback via OTel context\n // so telemetry can distinguish intended OBO calls from regular SP calls.\n if (!token && isDev) {\n logger.warn(\n \"asUser() called without user token in development mode. Skipping user impersonation.\",\n );\n\n return this._createAsUserProxy((fn) => (...args) => {\n const ctx = otelContext.active().setValue(DEV_OBO_FALLBACK_KEY, true);\n return otelContext.with(ctx, () => fn(...args));\n });\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 undefined,\n userEmail ?? undefined,\n );\n\n return this._createAsUserProxy(\n (fn) =>\n (...args) =>\n runInUserContext(userContext, () => fn(...args)),\n );\n }\n\n /**\n * Creates a proxy of `this` where every method call — and every function\n * in the result of `exports()` — runs inside `wrapCall`.\n *\n * `wrapCall` decides the per-call scope. Two strategies are used today:\n * - real OBO: fn => (...args) => runInUserContext(userContext, () => fn(...args))\n * - dev fallback: fn => (...args) => otelContext.with(DEV_OBO_FALLBACK_KEY=true, () => fn(...args))\n *\n * `exports` is intercepted because methods captured in the returned\n * exports object never re-enter the proxy's `get` trap. Wrapping them\n * here is the only way to make the user context follow function\n * references back out of the plugin.\n */\n private _createAsUserProxy(\n wrapCall: (\n fn: (...a: unknown[]) => unknown,\n ) => (...a: unknown[]) => unknown,\n ): 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\") return value;\n if (typeof prop === \"string\" && EXCLUDED_FROM_PROXY.has(prop))\n return value;\n\n if (prop === \"exports\") {\n return () => {\n const raw = (value as () => unknown).call(target);\n if (raw == null) return {};\n // Callable exports (e.g. files, jobs) manage per-call asUser\n // themselves; leave them untouched.\n if (typeof raw === \"function\") return raw;\n if (isPlainObject(raw)) {\n return wrapExportFunctions(raw, wrapCall);\n }\n return raw;\n };\n }\n\n const fn = (value as (...a: unknown[]) => unknown).bind(target);\n return wrapCall(fn);\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 // capture the active OTel context (HTTP span) before entering the async generator,\n // where it would otherwise be lost across the async boundary\n const parentOtelContext = otelContext.active();\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, restoring the parent OTel context\n // so telemetry spans are linked as children of the HTTP request span\n const result = await otelContext.with(parentOtelContext, () =>\n self._executeWithInterceptors(\n wrappedFn as (signal?: AbortSignal) => Promise<T>,\n interceptors,\n context,\n ),\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. The effective user key is forwarded\n // to the stream manager so that reconnections to existing streamIds are\n // bound to the original creator (prevents cross-user stream takeover via\n // guessed/leaked IDs).\n await this.streamManager.stream(\n res,\n asyncWrapperFn,\n streamConfig,\n effectiveUserKey,\n );\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
|
+
{"version":3,"file":"plugin.js","names":["otelContext","context"],"sources":["../../src/plugin/plugin.ts"],"sourcesContent":["import { createContextKey, context as otelContext } from \"@opentelemetry/api\";\nimport 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 { getCurrentUserId, runInUserContext, ServiceContext } from \"../context\";\nimport type { PluginContext } from \"../core/plugin-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 { forwardAsyncErrors } from \"../utils/safe-handler\";\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 * OTel context key for marking OBO dev mode fallback.\n * Set when asUser() is called in development mode without a user token.\n */\nconst DEV_OBO_FALLBACK_KEY = createContextKey(\"appkit.devOboFallback\");\n\n/**\n * Returns true if `value` is a plain object literal (not an array, Date,\n * class instance, etc.). Used to decide whether to recurse into nested\n * export shapes when wrapping functions.\n *\n * @internal exported so the AppKit core can reuse the same predicate for\n * its `bindExportMethods` walk; not part of the public package surface.\n */\nexport function isPlainObject(\n value: unknown,\n): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Returns a deep copy of `exports` where every function has been replaced\n * with `wrap(fn)`, walking into nested plain objects.\n *\n * Used by the asUser proxy to make the user context follow function\n * references that escape the proxy via `exports()`. The original input is\n * not mutated, so plugins that memoize `exports()` are safe — each call\n * through the proxy yields an independent, freshly wrapped view.\n */\nfunction wrapExportFunctions(\n exports: Record<string, unknown>,\n wrap: (fn: (...a: unknown[]) => unknown) => (...a: unknown[]) => unknown,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(exports)) {\n const val = exports[key];\n if (typeof val === \"function\") {\n result[key] = wrap(val as (...a: unknown[]) => unknown);\n } else if (isPlainObject(val)) {\n result[key] = wrapExportFunctions(val, wrap);\n } else {\n result[key] = val;\n }\n }\n return result;\n}\n\n/**\n * Returns true if the current execution is an OBO dev mode fallback\n * (asUser() was called but fell back to service principal due to missing token).\n */\nexport function isDevOboFallback(): boolean {\n return otelContext.active().getValue(DEV_OBO_FALLBACK_KEY) === true;\n}\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 \"attachContext\",\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 protected context?: PluginContext;\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.streamManager = new StreamManager();\n this.app = new AppManager();\n this.devFileReader = DevFileReader.getInstance();\n this.context = (config as Record<string, unknown>).context as\n | PluginContext\n | undefined;\n\n // Eagerly bind telemetry + cache if the core services have already been\n // initialized (normal createApp path, or tests that mock CacheManager).\n // If they haven't, we leave these undefined and rely on `attachContext`\n // being called later — this lets factories eagerly construct plugin\n // instances at module top-level before `createApp` has run.\n this.tryAttachContext();\n }\n\n private tryAttachContext(): void {\n try {\n this.cache = CacheManager.getInstanceSync();\n } catch {\n return;\n }\n this.telemetry = TelemetryManager.getProvider(\n this.name,\n this.config.telemetry,\n );\n this.isReady = true;\n }\n\n /**\n * Binds runtime dependencies (telemetry provider, cache, plugin context) to\n * this plugin. Called by `AppKit._createApp` after construction and before\n * `setup()`. Idempotent: safe to call if the constructor already bound them\n * eagerly. Kept separate so factories can eagerly construct plugin instances\n * without running this before `TelemetryManager.initialize()` /\n * `CacheManager.getInstance()` have run.\n */\n attachContext(\n deps: {\n context?: unknown;\n telemetryConfig?: BasePluginConfig[\"telemetry\"];\n } = {},\n ): void {\n if (!this.cache) {\n this.cache = CacheManager.getInstanceSync();\n }\n this.telemetry = TelemetryManager.getProvider(\n this.name,\n deps.telemetryConfig ?? this.config.telemetry,\n );\n if (deps.context !== undefined) {\n this.context = deps.context as PluginContext;\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\")?.trim();\n if (userId) return userId;\n if (process.env.NODE_ENV === \"development\") return getCurrentUserId();\n throw AuthenticationError.missingUserId();\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\")?.trim();\n const userId = req.header(\"x-forwarded-user\")?.trim();\n const userEmail = req.header(\"x-forwarded-email\");\n const isDev = process.env.NODE_ENV === \"development\";\n\n // In local development, skip user impersonation since there's no user\n // token available. Mark execution as OBO dev fallback via OTel context\n // so telemetry can distinguish intended OBO calls from regular SP calls.\n if (!token && isDev) {\n logger.warn(\n \"asUser() called without user token in development mode. Skipping user impersonation.\",\n );\n\n return this._createAsUserProxy((fn) => (...args) => {\n const ctx = otelContext.active().setValue(DEV_OBO_FALLBACK_KEY, true);\n return otelContext.with(ctx, () => fn(...args));\n });\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 undefined,\n userEmail ?? undefined,\n );\n\n return this._createAsUserProxy(\n (fn) =>\n (...args) =>\n runInUserContext(userContext, () => fn(...args)),\n );\n }\n\n /**\n * Creates a proxy of `this` where every method call — and every function\n * in the result of `exports()` — runs inside `wrapCall`.\n *\n * `wrapCall` decides the per-call scope. Two strategies are used today:\n * - real OBO: fn => (...args) => runInUserContext(userContext, () => fn(...args))\n * - dev fallback: fn => (...args) => otelContext.with(DEV_OBO_FALLBACK_KEY=true, () => fn(...args))\n *\n * `exports` is intercepted because methods captured in the returned\n * exports object never re-enter the proxy's `get` trap. Wrapping them\n * here is the only way to make the user context follow function\n * references back out of the plugin.\n */\n private _createAsUserProxy(\n wrapCall: (\n fn: (...a: unknown[]) => unknown,\n ) => (...a: unknown[]) => unknown,\n ): 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\") return value;\n if (typeof prop === \"string\" && EXCLUDED_FROM_PROXY.has(prop))\n return value;\n\n if (prop === \"exports\") {\n return () => {\n const raw = (value as () => unknown).call(target);\n if (raw == null) return {};\n // Callable exports (e.g. files, jobs) manage per-call asUser\n // themselves; leave them untouched.\n if (typeof raw === \"function\") return raw;\n if (isPlainObject(raw)) {\n return wrapExportFunctions(raw, wrapCall);\n }\n return raw;\n };\n }\n\n const fn = (value as (...a: unknown[]) => unknown).bind(target);\n return wrapCall(fn);\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 // capture the active OTel context (HTTP span) before entering the async generator,\n // where it would otherwise be lost across the async boundary\n const parentOtelContext = otelContext.active();\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, restoring the parent OTel context\n // so telemetry spans are linked as children of the HTTP request span\n const result = await otelContext.with(parentOtelContext, () =>\n self._executeWithInterceptors(\n wrappedFn as (signal?: AbortSignal) => Promise<T>,\n interceptors,\n context,\n ),\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. The effective user key is forwarded\n // to the stream manager so that reconnections to existing streamIds are\n // bound to the original creator (prevents cross-user stream takeover via\n // guessed/leaked IDs).\n await this.streamManager.stream(\n res,\n asyncWrapperFn,\n streamConfig,\n effectiveUserKey,\n );\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, forwardAsyncErrors(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":"agents.d.ts","names":[],"sources":["../../../src/plugins/agents/agents.ts"],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"agents.d.ts","names":[],"sources":["../../../src/plugins/agents/agents.ts"],"mappings":";;;;;;;;;;cAiIa,YAAA,SAAqB,MAAA,YAAkB,YAAA;EAAA,OAC3C,QAAA,EAAuB,cAAA;EAAA,OACvB,KAAA,EAAO,WAAA;EAAA,UAEI,MAAA,EAAQ,kBAAA;EAAA,QAElB,MAAA;EAAA,QACA,gBAAA;EAAA,QACA,aAAA;EARgB;;;;;;EAAA,QAkBhB,gBAAA;EAAA,QACA,SAAA;EAAA,QACA,WAAA;EAAA,QACA,YAAA;cAEI,MAAA,EAAQ,kBAAA;EA4qBJ;;;;;;;;;;EAAA,QA7oBR,oBAAA;EAAA,YAKI,sBAAA,CAAA;EA3DoB;EAAA,YAqFpB,cAAA,CAAA;EApFL;EAAA,QAuGC,gBAAA;EAtGD;;;;;;EAAA,QAgHC,WAAA;EAhGA;;;;;EAAA,QAiHA,aAAA;EAYF,KAAA,CAAA,GAAK,OAAA;EAzFH;;;;;;EAuGF,MAAA,CAAA,GAAU,OAAA;EAdL;;;;;EAAA,QAwCG,kBAAA;EAAA,QAmEN,iBAAA;EAAA,QAMM,mBAAA;EAkEA;;;;;;EAAA,QAxCN,mBAAA;EAAA,QAmBM,oBAAA;EAAA,QAqBA,cAAA;EAyTY;;;;;EAAA,QA7QZ,cAAA;EAqVE;;;;;;;;;;;EAAA,QArPR,eAAA;EAmhCM;;;;;;;;;;;;EAAA,QAv/BN,eAAA;EAAA,QAkBM,gBAAA;EAAA,QA0DA,kBAAA;EAiEd,aAAA,CAAA,GAAiB,mBAAA;EAIX,gBAAA,CAAA,GAAoB,OAAA;;;;;;;UAYlB,iBAAA;EAUR,YAAA,CAAa,MAAA,EAAQ,UAAA;EAkDrB,YAAA,CAAA,GAAgB,MAAA;EAAA,QAOF,WAAA;;;;;;;;;;;;UA6EN,gCAAA;;;;;;;;;;;UAuBM,aAAA;EAAA,QAoFA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;UAqLA,qBAAA;;;;;;;;;;;;UA6IA,gBAAA;;;;;;;;;;;;;;;UA6GA,WAAA;EAAA,QA2FA,aAAA;EAAA,QA2BA,cAAA;EAAA,QAuCA,kBAAA;EAAA,QASA,gBAAA;EAAA,QAUA,mBAAA;EAAA,QAaN,YAAA;EAAA,QASA,aAAA;EAgBF,QAAA,CAAA,GAAY,OAAA;EAQlB,OAAA,CAAA;6BAE2B,GAAA,EAAO,eAAA,KAAe,OAAA;;2BAG3B,eAAA;;;oCAGS,OAAA,CAAA,MAAA;EAAA;EAAA,QAIjB,iBAAA;AAAA;;;;;;;;;;;;;;;;cA+DH,MAAA,EAAM,QAAA,QAAA,YAAA,EAAA,kBAAA"}
|