Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ coverage
*.tsbuildinfo

.turbo

# AI generated files
.serena
docs/plans/
10 changes: 9 additions & 1 deletion packages/appkit/src/context/execution-context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AsyncLocalStorage } from "node:async_hooks";
import { ConfigurationError } from "../errors";
import { ServiceContext } from "./service-context";
import {
type ExecutionContext,
Expand Down Expand Up @@ -64,7 +65,14 @@ export function getWorkspaceClient() {
* Get the warehouse ID promise.
*/
export function getWarehouseId(): Promise<string> {
return getExecutionContext().warehouseId;
const ctx = getExecutionContext();
if (!ctx.warehouseId) {
throw ConfigurationError.resourceNotFound(
"Warehouse ID",
'Ensure a plugin declares static requiredResources = ["warehouseId"] or set DATABRICKS_WAREHOUSE_ID',
);
}
return ctx.warehouseId;
}

/**
Expand Down
17 changes: 13 additions & 4 deletions packages/appkit/src/context/service-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
WorkspaceClient,
} from "@databricks/sdk-experimental";
import { coerce } from "semver";
import type { ServiceContextResource } from "shared";
import {
name as productName,
version as productVersion,
Expand All @@ -24,8 +25,8 @@ export interface ServiceContextState {
client: WorkspaceClient;
/** The service principal's user ID */
serviceUserId: string;
/** Promise that resolves to the warehouse ID */
warehouseId: Promise<string>;
/** Promise that resolves to the warehouse ID (only present when a plugin requires it) */
warehouseId?: Promise<string>;
/** Promise that resolves to the workspace ID */
workspaceId: Promise<string>;
}
Expand Down Expand Up @@ -62,6 +63,7 @@ export class ServiceContext {
* of creating one from environment credentials.
*/
static async initialize(
requiredResources: ServiceContextResource[] = [],
client?: WorkspaceClient,
): Promise<ServiceContextState> {
if (ServiceContext.instance) {
Expand All @@ -72,7 +74,10 @@ export class ServiceContext {
return ServiceContext.initPromise;
}

ServiceContext.initPromise = ServiceContext.createContext(client);
ServiceContext.initPromise = ServiceContext.createContext(
requiredResources,
client,
);
ServiceContext.instance = await ServiceContext.initPromise;
return ServiceContext.instance;
}
Expand Down Expand Up @@ -153,11 +158,15 @@ export class ServiceContext {
}

private static async createContext(
requiredResources: ServiceContextResource[] = [],
client?: WorkspaceClient,
): Promise<ServiceContextState> {
const wsClient = client ?? new WorkspaceClient({}, getClientOptions());

const warehouseId = ServiceContext.getWarehouseId(wsClient);
const warehouseId = requiredResources.includes("warehouseId")
? ServiceContext.getWarehouseId(wsClient)
: undefined;

const workspaceId = ServiceContext.getWorkspaceId(wsClient);
const currentUser = await wsClient.currentUser.me();

Expand Down
4 changes: 2 additions & 2 deletions packages/appkit/src/context/user-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export interface UserContext {
userId: string;
/** The user's name (from request headers) */
userName?: string;
/** Promise that resolves to the warehouse ID (inherited from service context) */
warehouseId: Promise<string>;
/** Promise that resolves to the warehouse ID (inherited from service context, only present when a plugin requires it) */
warehouseId?: Promise<string>;
/** Promise that resolves to the workspace ID (inherited from service context) */
workspaceId: Promise<string>;
/** Flag indicating this is a user context */
Expand Down
23 changes: 19 additions & 4 deletions packages/appkit/src/core/appkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
PluginConstructor,
PluginData,
PluginMap,
ServiceContextResource,
} from "shared";
import { CacheManager } from "../cache";
import { ServiceContext } from "../context";
Expand Down Expand Up @@ -149,11 +150,13 @@ export class AppKit<TPlugins extends InputPluginMap> {
TelemetryManager.initialize(config?.telemetry);
await CacheManager.getInstance(config?.cache);

// Initialize ServiceContext for Databricks client management
// This provides the service principal client and shared resources
await ServiceContext.initialize(config?.client);

// Collect required resources from all plugins before initializing context
const rawPlugins = config.plugins as T;
const requiredResources = AppKit.collectRequiredResources(rawPlugins);

// Initialize ServiceContext for Databricks client management
// Only resolves resources that plugins actually need
await ServiceContext.initialize(requiredResources, config?.client);
const preparedPlugins = AppKit.preparePlugins(rawPlugins);
const mergedConfig = {
plugins: preparedPlugins,
Expand All @@ -178,6 +181,18 @@ export class AppKit<TPlugins extends InputPluginMap> {
}
return result;
}

private static collectRequiredResources(
plugins: PluginData<PluginConstructor, unknown, string>[],
): ServiceContextResource[] {
const resources = new Set<ServiceContextResource>();
for (const { plugin } of plugins) {
for (const req of plugin.requiredResources ?? []) {
resources.add(req);
}
}
return [...resources];
}
}

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/appkit/src/plugin/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
PluginExecutionSettings,
PluginPhase,
RouteConfig,
ServiceContextResource,
StreamExecuteHandler,
StreamExecutionSettings,
} from "shared";
Expand Down Expand Up @@ -76,6 +77,7 @@ export abstract class Plugin<
private registeredEndpoints: PluginEndpointMap = {};

static phase: PluginPhase = "normal";
static requiredResources: ServiceContextResource[] = [];
name: string;

constructor(protected config: TConfig) {
Expand Down
2 changes: 2 additions & 0 deletions packages/appkit/src/plugins/analytics/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type express from "express";
import type {
IAppRouter,
PluginExecuteConfig,
ServiceContextResource,
SQLTypeMarker,
StreamExecutionSettings,
} from "shared";
Expand All @@ -25,6 +26,7 @@ import type {
const logger = createLogger("analytics");

export class AnalyticsPlugin extends Plugin {
static requiredResources: ServiceContextResource[] = ["warehouseId"];
name = "analytics";
protected envVars: string[] = [];

Expand Down
3 changes: 3 additions & 0 deletions packages/shared/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export interface PluginConfig {

export type PluginPhase = "core" | "normal" | "deferred";

export type ServiceContextResource = "warehouseId";

export type PluginConstructor<
C = BasePluginConfig,
I extends BasePlugin = BasePlugin,
Expand All @@ -54,6 +56,7 @@ export type PluginConstructor<
) => I) & {
DEFAULT_CONFIG?: Record<string, unknown>;
phase?: PluginPhase;
requiredResources?: ServiceContextResource[];
};

export type ConfigFor<T> = T extends { DEFAULT_CONFIG: infer D }
Expand Down
Loading