@databricks/appkit 0.82.0 → 0.83.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.
Files changed (104) hide show
  1. package/CLAUDE.md +6 -0
  2. package/dist/appkit/package.js +1 -1
  3. package/dist/connectors/lakebase/index.js +1 -1
  4. package/dist/connectors/lakebase/routing-pool.d.ts +1 -1
  5. package/dist/connectors/lakebase/routing-pool.js +3 -3
  6. package/dist/connectors/lakebase/routing-pool.js.map +1 -1
  7. package/dist/context/caller-context.d.ts +22 -0
  8. package/dist/context/caller-context.d.ts.map +1 -0
  9. package/dist/context/caller-context.js +21 -0
  10. package/dist/context/caller-context.js.map +1 -0
  11. package/dist/context/deprecation.js +14 -0
  12. package/dist/context/deprecation.js.map +1 -0
  13. package/dist/context/execution-context.d.ts +12 -4
  14. package/dist/context/execution-context.d.ts.map +1 -1
  15. package/dist/context/execution-context.js +36 -34
  16. package/dist/context/execution-context.js.map +1 -1
  17. package/dist/context/index.d.ts +3 -2
  18. package/dist/context/index.js +2 -1
  19. package/dist/context/service-context.d.ts +18 -12
  20. package/dist/context/service-context.d.ts.map +1 -1
  21. package/dist/context/service-context.js +44 -54
  22. package/dist/context/service-context.js.map +1 -1
  23. package/dist/context/user-context.d.ts +5 -8
  24. package/dist/context/user-context.d.ts.map +1 -1
  25. package/dist/context/user-context.js +33 -6
  26. package/dist/context/user-context.js.map +1 -1
  27. package/dist/core/appkit.d.ts.map +1 -1
  28. package/dist/core/appkit.js +4 -2
  29. package/dist/core/appkit.js.map +1 -1
  30. package/dist/index.d.ts +7 -4
  31. package/dist/index.js +4 -2
  32. package/dist/plugin/plugin.d.ts +2 -2
  33. package/dist/plugin/plugin.d.ts.map +1 -1
  34. package/dist/plugin/plugin.js +6 -6
  35. package/dist/plugin/plugin.js.map +1 -1
  36. package/dist/plugins/agents/agents.d.ts +1 -1
  37. package/dist/plugins/ai-search/ai-search.d.ts +1 -1
  38. package/dist/plugins/analytics/analytics.d.ts +1 -1
  39. package/dist/plugins/analytics/analytics.d.ts.map +1 -1
  40. package/dist/plugins/analytics/analytics.js +3 -1
  41. package/dist/plugins/analytics/analytics.js.map +1 -1
  42. package/dist/plugins/database/database.d.ts +1 -1
  43. package/dist/plugins/files/plugin.d.ts +11 -11
  44. package/dist/plugins/files/plugin.js +17 -17
  45. package/dist/plugins/files/plugin.js.map +1 -1
  46. package/dist/plugins/genie/genie.d.ts +1 -1
  47. package/dist/plugins/jobs/plugin.d.ts +1 -1
  48. package/dist/plugins/lakebase/lakebase.d.ts +1 -1
  49. package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
  50. package/dist/plugins/lakebase/lakebase.js +4 -4
  51. package/dist/plugins/lakebase/lakebase.js.map +1 -1
  52. package/dist/plugins/server/index.d.ts +1 -1
  53. package/dist/plugins/server/index.js +2 -2
  54. package/dist/plugins/server/index.js.map +1 -1
  55. package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js +3 -3
  56. package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js.map +1 -1
  57. package/dist/plugins/server/static-server.js +3 -3
  58. package/dist/plugins/server/static-server.js.map +1 -1
  59. package/dist/plugins/server/utils.js +3 -3
  60. package/dist/plugins/server/utils.js.map +1 -1
  61. package/dist/plugins/server/vite-dev-server.js +4 -4
  62. package/dist/plugins/server/vite-dev-server.js.map +1 -1
  63. package/dist/plugins/serving/serving.d.ts +1 -1
  64. package/dist/resources/index.d.ts +1 -0
  65. package/dist/resources/index.js +3 -0
  66. package/dist/resources/warehouse.d.ts +16 -0
  67. package/dist/resources/warehouse.d.ts.map +1 -0
  68. package/dist/resources/warehouse.js +93 -0
  69. package/dist/resources/warehouse.js.map +1 -0
  70. package/dist/schemas/manifest.d.ts +9 -0
  71. package/dist/schemas/manifest.d.ts.map +1 -1
  72. package/dist/schemas/manifest.js +11 -0
  73. package/dist/schemas/manifest.js.map +1 -1
  74. package/dist/shared/src/schemas/manifest.d.ts +42 -33
  75. package/dist/shared/src/schemas/manifest.d.ts.map +1 -1
  76. package/dist/shared/src/schemas/manifest.js +11 -0
  77. package/dist/shared/src/schemas/manifest.js.map +1 -1
  78. package/dist/testing/create-test-app.js +2 -2
  79. package/dist/testing/create-test-app.js.map +1 -1
  80. package/dist/testing/fixtures.d.ts +3 -2
  81. package/dist/testing/fixtures.d.ts.map +1 -1
  82. package/dist/testing/fixtures.js +21 -14
  83. package/dist/testing/fixtures.js.map +1 -1
  84. package/dist/testing/reset-singletons.js +1 -1
  85. package/dist/type-generator/database/generate.js +3 -3
  86. package/dist/type-generator/database/generate.js.map +1 -1
  87. package/dist/type-generator/migration.js +2 -2
  88. package/dist/type-generator/migration.js.map +1 -1
  89. package/dist/type-generator/serving/server-file-extractor.js +3 -3
  90. package/dist/type-generator/serving/server-file-extractor.js.map +1 -1
  91. package/docs/api/appkit/Class.Plugin.md +1 -1
  92. package/docs/api/appkit/Function.getCurrentActorId.md +12 -0
  93. package/docs/api/appkit/Function.getCurrentPrincipalKey.md +12 -0
  94. package/docs/api/appkit/Function.getExecutionContext.md +5 -3
  95. package/docs/api/appkit/Function.getWarehouseId.md +20 -0
  96. package/docs/api/appkit/Interface.CallerContext.md +41 -0
  97. package/docs/api/appkit/Interface.PluginManifest.md +25 -2
  98. package/docs/api/appkit/TypeAlias.CallerPrincipal.md +13 -0
  99. package/docs/api/appkit/TypeAlias.ExecutionContext.md +6 -0
  100. package/docs/api/appkit.md +6 -0
  101. package/docs/plugins/analytics.md +8 -0
  102. package/llms.txt +6 -0
  103. package/package.json +1 -1
  104. package/sbom.cdx.json +1 -1
@@ -5,12 +5,16 @@ import { AuthenticationError } from "../errors/authentication.js";
5
5
  import { ConfigurationError } from "../errors/configuration.js";
6
6
  import { InitializationError } from "../errors/initialization.js";
7
7
  import "../errors/index.js";
8
+ import { WarehouseResource } from "../resources/warehouse.js";
9
+ import { snapshotCallerContext } from "./caller-context.js";
10
+ import { warnContextDeprecation } from "./deprecation.js";
11
+ import { legacyUserContext } from "./user-context.js";
8
12
  import { createHash } from "node:crypto";
9
13
 
10
14
  //#region src/context/service-context.ts
11
15
  /**
12
16
  * ServiceContext is a singleton that manages the service principal's
13
- * WorkspaceClient and shared resources like warehouse/workspace IDs.
17
+ * WorkspaceClient and workspace ID. WarehouseResource owns warehouse bindings.
14
18
  *
15
19
  * It's initialized once at app startup and provides the foundation
16
20
  * for both service principal and user context execution.
@@ -22,7 +26,7 @@ var ServiceContext = class ServiceContext {
22
26
  * Initialize the service context. Should be called once at app startup.
23
27
  * Safe to call multiple times - will return the same instance.
24
28
  *
25
- * @param options - Which shared resources to resolve (derived from plugin manifests).
29
+ * @param options - A resolved warehouse ID, or a boolean enabling discovery.
26
30
  * @param client - Optional pre-configured WorkspaceClient to use instead
27
31
  * of creating one from environment credentials.
28
32
  */
@@ -48,33 +52,44 @@ var ServiceContext = class ServiceContext {
48
52
  return ServiceContext.instance !== null;
49
53
  }
50
54
  /**
51
- * Create a user context from request headers.
55
+ * Create an immutable caller context from the existing user request headers.
52
56
  *
53
57
  * @param token - The user's access token from x-forwarded-access-token header
54
58
  * @param userId - The user's ID from x-forwarded-user header
55
59
  * @param userName - Optional user name
60
+ * @param userEmail - Optional email from x-forwarded-email
56
61
  * @throws Error if token is not provided
57
62
  */
58
- static createUserContext(token, userId, userName, userEmail) {
63
+ static createCallerContext(token, userId, userName, userEmail) {
59
64
  if (!token) throw AuthenticationError.missingToken("user token");
60
65
  const host = process.env.DATABRICKS_HOST;
61
66
  if (!host) throw ConfigurationError.missingEnvVar("DATABRICKS_HOST");
62
67
  const serviceCtx = ServiceContext.get();
63
- return {
64
- client: createWorkspaceClient({
65
- token,
66
- host,
67
- authType: "pat",
68
- clientOptions: getClientOptions()
69
- }),
70
- userId,
71
- userName,
72
- userEmail,
73
- tokenFingerprint: createHash("sha256").update(token).digest("hex").slice(0, 16),
74
- warehouseId: serviceCtx.warehouseId,
75
- workspaceId: serviceCtx.workspaceId,
76
- isUserContext: true
77
- };
68
+ const userClient = createWorkspaceClient({
69
+ token,
70
+ host,
71
+ authType: "pat",
72
+ clientOptions: getClientOptions()
73
+ });
74
+ const tokenFingerprint = createHash("sha256").update(token).digest("hex").slice(0, 16);
75
+ return snapshotCallerContext({
76
+ client: userClient,
77
+ principal: {
78
+ type: "user",
79
+ userId,
80
+ userName,
81
+ userEmail
82
+ },
83
+ tokenFingerprint,
84
+ workspaceId: serviceCtx.workspaceId
85
+ });
86
+ }
87
+ /** @deprecated Use ServiceContext.createCallerContext. */
88
+ static createUserContext(token, userId, userName, userEmail) {
89
+ warnContextDeprecation("ServiceContext.createUserContext", "ServiceContext.createCallerContext");
90
+ const caller = ServiceContext.createCallerContext(token, userId, userName, userEmail);
91
+ const warehouseId = WarehouseResource.get()?.warehouseId;
92
+ return legacyUserContext(caller, () => warehouseId);
78
93
  }
79
94
  /**
80
95
  * Get the client options for WorkspaceClient.
@@ -86,19 +101,22 @@ var ServiceContext = class ServiceContext {
86
101
  static async createContext(options, client) {
87
102
  try {
88
103
  const wsClient = client ?? createWorkspaceClient({ clientOptions: getClientOptions() });
89
- const [resolvedWorkspaceId, currentUser, resolvedWarehouseId] = await Promise.all([
104
+ const [resolvedWorkspaceId, currentUser, resolvedResources] = await Promise.all([
90
105
  ServiceContext.getWorkspaceId(wsClient),
91
106
  wsClient.currentUser.me(),
92
- options?.warehouseId ? ServiceContext.getWarehouseId(wsClient) : Promise.resolve(void 0)
107
+ WarehouseResource.resolve(wsClient, options?.warehouseId)
93
108
  ]);
94
109
  if (!currentUser.id) throw ConfigurationError.resourceNotFound("Service user ID");
95
- const warehouseId = options?.warehouseId && resolvedWarehouseId !== void 0 ? Promise.resolve(resolvedWarehouseId) : void 0;
96
- return {
110
+ const resources = WarehouseResource.bind(resolvedResources);
111
+ return Object.freeze({
97
112
  client: wsClient,
98
113
  serviceUserId: currentUser.id,
99
- warehouseId,
114
+ get warehouseId() {
115
+ warnContextDeprecation("ServiceContextState.warehouseId", "getWarehouseId() from @databricks/appkit");
116
+ return resources.warehouseId;
117
+ },
100
118
  workspaceId: Promise.resolve(resolvedWorkspaceId)
101
- };
119
+ });
102
120
  } catch (e) {
103
121
  if (e instanceof ConfigError) throw ConfigurationError.databricksAuthenticationSetupFailed(e.baseMessage, { cause: e });
104
122
  throw e;
@@ -117,41 +135,13 @@ var ServiceContext = class ServiceContext {
117
135
  if (!response["x-databricks-org-id"]) throw ConfigurationError.resourceNotFound("Workspace ID");
118
136
  return response["x-databricks-org-id"];
119
137
  }
120
- static async getWarehouseId(client) {
121
- if (process.env.DATABRICKS_WAREHOUSE_ID) return process.env.DATABRICKS_WAREHOUSE_ID;
122
- const agenticMode = process.env.DATABRICKS_APPS_AGENTIC_MODE === "true" || process.env.DATABRICKS_APPS_AGENTIC_MODE === "1";
123
- if (process.env.NODE_ENV === "development" && !agenticMode) {
124
- const response = await client.apiClient.request({
125
- path: "/api/2.0/sql/warehouses",
126
- method: "GET",
127
- headers: new Headers(),
128
- raw: false,
129
- query: { skip_cannot_use: "true" }
130
- });
131
- const priorities = {
132
- RUNNING: 0,
133
- STOPPED: 1,
134
- STARTING: 2,
135
- STOPPING: 3,
136
- DELETED: 99,
137
- DELETING: 99
138
- };
139
- const warehouses = (response.warehouses || []).sort((a, b) => {
140
- return priorities[a.state] - priorities[b.state];
141
- });
142
- if (response.warehouses.length === 0) throw ConfigurationError.resourceNotFound("Warehouse ID", "Please configure the DATABRICKS_WAREHOUSE_ID environment variable");
143
- const firstWarehouse = warehouses[0];
144
- if (firstWarehouse.state === "DELETED" || firstWarehouse.state === "DELETING" || !firstWarehouse.id) throw ConfigurationError.resourceNotFound("Warehouse ID", "Please configure the DATABRICKS_WAREHOUSE_ID environment variable");
145
- return firstWarehouse.id;
146
- }
147
- throw ConfigurationError.resourceNotFound("Warehouse ID", "Please configure the DATABRICKS_WAREHOUSE_ID environment variable");
148
- }
149
138
  /**
150
139
  * Reset the service context. Only for testing purposes.
151
140
  */
152
141
  static reset() {
153
142
  ServiceContext.instance = null;
154
143
  ServiceContext.initPromise = null;
144
+ WarehouseResource.reset();
155
145
  }
156
146
  };
157
147
 
@@ -1 +1 @@
1
- {"version":3,"file":"service-context.js","names":[],"sources":["../../src/context/service-context.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\n\nimport {\n AuthenticationError,\n ConfigurationError,\n InitializationError,\n} from \"../errors\";\nimport {\n type ClientOptions,\n ConfigError,\n createWorkspaceClient,\n type sql,\n type WorkspaceClient,\n} from \"../workspace-client\";\nimport { getClientOptions } from \"./client-options\";\nimport type { UserContext } from \"./user-context\";\n\n/**\n * Service context holds the service principal client and shared resources.\n * This is initialized once at app startup and shared across all requests.\n */\nexport interface ServiceContextState {\n /** WorkspaceClient authenticated as the service principal */\n client: WorkspaceClient;\n /** The service principal's user ID */\n serviceUserId: string;\n /** Promise that resolves to the warehouse ID (only present when a plugin requires `SQL_WAREHOUSE` resource) */\n warehouseId?: Promise<string>;\n /** Promise that resolves to the workspace ID */\n workspaceId: Promise<string>;\n}\n\n/**\n * ServiceContext is a singleton that manages the service principal's\n * WorkspaceClient and shared resources like warehouse/workspace IDs.\n *\n * It's initialized once at app startup and provides the foundation\n * for both service principal and user context execution.\n */\nexport class ServiceContext {\n private static instance: ServiceContextState | null = null;\n private static initPromise: Promise<ServiceContextState> | null = null;\n\n /**\n * Initialize the service context. Should be called once at app startup.\n * Safe to call multiple times - will return the same instance.\n *\n * @param options - Which shared resources to resolve (derived from plugin manifests).\n * @param client - Optional pre-configured WorkspaceClient to use instead\n * of creating one from environment credentials.\n */\n static async initialize(\n options?: { warehouseId?: boolean },\n client?: WorkspaceClient,\n ): Promise<ServiceContextState> {\n if (ServiceContext.instance) {\n return ServiceContext.instance;\n }\n\n if (ServiceContext.initPromise) {\n return ServiceContext.initPromise;\n }\n\n ServiceContext.initPromise = ServiceContext.createContext(options, client);\n ServiceContext.instance = await ServiceContext.initPromise;\n return ServiceContext.instance;\n }\n\n /**\n * Get the initialized service context.\n * @throws Error if not initialized\n */\n static get(): ServiceContextState {\n if (!ServiceContext.instance) {\n throw InitializationError.notInitialized(\n \"ServiceContext\",\n \"Call ServiceContext.initialize() first\",\n );\n }\n return ServiceContext.instance;\n }\n\n /**\n * Check if the service context has been initialized.\n */\n static isInitialized(): boolean {\n return ServiceContext.instance !== null;\n }\n\n /**\n * Create a user context from request headers.\n *\n * @param token - The user's access token from x-forwarded-access-token header\n * @param userId - The user's ID from x-forwarded-user header\n * @param userName - Optional user name\n * @throws Error if token is not provided\n */\n static createUserContext(\n token: string,\n userId: string,\n userName?: string,\n userEmail?: string,\n ): UserContext {\n if (!token) {\n throw AuthenticationError.missingToken(\"user token\");\n }\n\n const host = process.env.DATABRICKS_HOST;\n if (!host) {\n throw ConfigurationError.missingEnvVar(\"DATABRICKS_HOST\");\n }\n\n const serviceCtx = ServiceContext.get();\n\n // Create user client with the OAuth token from Databricks Apps\n // Note: We use authType: \"pat\" because the token is passed as a Bearer token\n // just like a PAT, even though it's technically an OAuth token\n const userClient = createWorkspaceClient({\n token,\n host,\n authType: \"pat\",\n clientOptions: getClientOptions(),\n });\n\n const tokenFingerprint = createHash(\"sha256\")\n .update(token)\n .digest(\"hex\")\n .slice(0, 16);\n\n return {\n client: userClient,\n userId,\n userName,\n userEmail,\n tokenFingerprint,\n warehouseId: serviceCtx.warehouseId,\n workspaceId: serviceCtx.workspaceId,\n isUserContext: true,\n };\n }\n\n /**\n * Get the client options for WorkspaceClient.\n * Exposed for testing purposes.\n */\n static getClientOptions(): ClientOptions {\n return getClientOptions();\n }\n\n private static async createContext(\n options?: { warehouseId?: boolean },\n client?: WorkspaceClient,\n ): Promise<ServiceContextState> {\n try {\n const wsClient =\n client ?? createWorkspaceClient({ clientOptions: getClientOptions() });\n\n const [resolvedWorkspaceId, currentUser, resolvedWarehouseId] =\n await Promise.all([\n ServiceContext.getWorkspaceId(wsClient),\n wsClient.currentUser.me(),\n options?.warehouseId\n ? ServiceContext.getWarehouseId(wsClient)\n : Promise.resolve(undefined as string | undefined),\n ]);\n\n if (!currentUser.id) {\n throw ConfigurationError.resourceNotFound(\"Service user ID\");\n }\n\n const warehouseId =\n options?.warehouseId && resolvedWarehouseId !== undefined\n ? Promise.resolve(resolvedWarehouseId)\n : undefined;\n\n return {\n client: wsClient,\n serviceUserId: currentUser.id,\n warehouseId,\n workspaceId: Promise.resolve(resolvedWorkspaceId),\n };\n } catch (e) {\n if (e instanceof ConfigError) {\n throw ConfigurationError.databricksAuthenticationSetupFailed(\n e.baseMessage,\n { cause: e },\n );\n }\n throw e;\n }\n }\n\n private static async getWorkspaceId(\n client: WorkspaceClient,\n ): Promise<string> {\n if (process.env.DATABRICKS_WORKSPACE_ID) {\n return process.env.DATABRICKS_WORKSPACE_ID;\n }\n\n const response = (await client.apiClient.request({\n path: \"/api/2.0/preview/scim/v2/Me\",\n method: \"GET\",\n headers: new Headers(),\n raw: false,\n query: {},\n responseHeaders: [\"x-databricks-org-id\"],\n })) as { \"x-databricks-org-id\": string };\n\n if (!response[\"x-databricks-org-id\"]) {\n throw ConfigurationError.resourceNotFound(\"Workspace ID\");\n }\n\n return response[\"x-databricks-org-id\"];\n }\n\n private static async getWarehouseId(\n client: WorkspaceClient,\n ): Promise<string> {\n if (process.env.DATABRICKS_WAREHOUSE_ID) {\n return process.env.DATABRICKS_WAREHOUSE_ID;\n }\n\n const agenticMode =\n process.env.DATABRICKS_APPS_AGENTIC_MODE === \"true\" ||\n process.env.DATABRICKS_APPS_AGENTIC_MODE === \"1\";\n\n if (process.env.NODE_ENV === \"development\" && !agenticMode) {\n const response = (await client.apiClient.request({\n path: \"/api/2.0/sql/warehouses\",\n method: \"GET\",\n headers: new Headers(),\n raw: false,\n query: {\n skip_cannot_use: \"true\",\n },\n })) as { warehouses: sql.EndpointInfo[] };\n\n const priorities: Record<sql.State, number> = {\n RUNNING: 0,\n STOPPED: 1,\n STARTING: 2,\n STOPPING: 3,\n DELETED: 99,\n DELETING: 99,\n };\n\n const warehouses = (response.warehouses || []).sort((a, b) => {\n return (\n priorities[a.state as sql.State] - priorities[b.state as sql.State]\n );\n });\n\n if (response.warehouses.length === 0) {\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n \"Please configure the DATABRICKS_WAREHOUSE_ID environment variable\",\n );\n }\n\n const firstWarehouse = warehouses[0];\n if (\n firstWarehouse.state === \"DELETED\" ||\n firstWarehouse.state === \"DELETING\" ||\n !firstWarehouse.id\n ) {\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n \"Please configure the DATABRICKS_WAREHOUSE_ID environment variable\",\n );\n }\n\n return firstWarehouse.id;\n }\n\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n \"Please configure the DATABRICKS_WAREHOUSE_ID environment variable\",\n );\n }\n\n /**\n * Reset the service context. Only for testing purposes.\n */\n static reset(): void {\n ServiceContext.instance = null;\n ServiceContext.initPromise = null;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"service-context.js","names":[],"sources":["../../src/context/service-context.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\n\nimport {\n AuthenticationError,\n ConfigurationError,\n InitializationError,\n} from \"../errors\";\nimport { WarehouseResource } from \"../resources/warehouse\";\nimport {\n type ClientOptions,\n ConfigError,\n createWorkspaceClient,\n type WorkspaceClient,\n} from \"../workspace-client\";\nimport { type CallerContext, snapshotCallerContext } from \"./caller-context\";\nimport { getClientOptions } from \"./client-options\";\nimport { warnContextDeprecation } from \"./deprecation\";\nimport { legacyUserContext, type UserContext } from \"./user-context\";\n\n/**\n * Service context holds the service principal identity and workspace client.\n * This is initialized once at app startup and shared across all requests.\n */\nexport interface ServiceContextState {\n /** WorkspaceClient authenticated as the service principal */\n readonly client: WorkspaceClient;\n /** The service principal's user ID */\n readonly serviceUserId: string;\n /**\n * @deprecated Use getWarehouseId() from @databricks/appkit.\n * Retained for backward compatibility.\n */\n readonly warehouseId?: Promise<string>;\n /** Promise that resolves to the workspace ID */\n readonly workspaceId: Promise<string>;\n}\n\n/**\n * ServiceContext is a singleton that manages the service principal's\n * WorkspaceClient and workspace ID. WarehouseResource owns warehouse bindings.\n *\n * It's initialized once at app startup and provides the foundation\n * for both service principal and user context execution.\n */\nexport class ServiceContext {\n private static instance: ServiceContextState | null = null;\n private static initPromise: Promise<ServiceContextState> | null = null;\n\n /**\n * Initialize the service context. Should be called once at app startup.\n * Safe to call multiple times - will return the same instance.\n *\n * @param options - A resolved warehouse ID, or a boolean enabling discovery.\n * @param client - Optional pre-configured WorkspaceClient to use instead\n * of creating one from environment credentials.\n */\n static async initialize(\n options?: { warehouseId?: boolean | string },\n client?: WorkspaceClient,\n ): Promise<ServiceContextState> {\n if (ServiceContext.instance) {\n return ServiceContext.instance;\n }\n\n if (ServiceContext.initPromise) {\n return ServiceContext.initPromise;\n }\n\n ServiceContext.initPromise = ServiceContext.createContext(options, client);\n ServiceContext.instance = await ServiceContext.initPromise;\n return ServiceContext.instance;\n }\n\n /**\n * Get the initialized service context.\n * @throws Error if not initialized\n */\n static get(): ServiceContextState {\n if (!ServiceContext.instance) {\n throw InitializationError.notInitialized(\n \"ServiceContext\",\n \"Call ServiceContext.initialize() first\",\n );\n }\n return ServiceContext.instance;\n }\n\n /**\n * Check if the service context has been initialized.\n */\n static isInitialized(): boolean {\n return ServiceContext.instance !== null;\n }\n\n /**\n * Create an immutable caller context from the existing user request headers.\n *\n * @param token - The user's access token from x-forwarded-access-token header\n * @param userId - The user's ID from x-forwarded-user header\n * @param userName - Optional user name\n * @param userEmail - Optional email from x-forwarded-email\n * @throws Error if token is not provided\n */\n static createCallerContext(\n token: string,\n userId: string,\n userName?: string,\n userEmail?: string,\n ): CallerContext {\n if (!token) {\n throw AuthenticationError.missingToken(\"user token\");\n }\n\n const host = process.env.DATABRICKS_HOST;\n if (!host) {\n throw ConfigurationError.missingEnvVar(\"DATABRICKS_HOST\");\n }\n\n const serviceCtx = ServiceContext.get();\n\n // Create user client with the OAuth token from Databricks Apps\n // Note: We use authType: \"pat\" because the token is passed as a Bearer token\n // just like a PAT, even though it's technically an OAuth token\n const userClient = createWorkspaceClient({\n token,\n host,\n authType: \"pat\",\n clientOptions: getClientOptions(),\n });\n\n const tokenFingerprint = createHash(\"sha256\")\n .update(token)\n .digest(\"hex\")\n .slice(0, 16);\n\n return snapshotCallerContext({\n client: userClient,\n principal: { type: \"user\", userId, userName, userEmail },\n tokenFingerprint,\n workspaceId: serviceCtx.workspaceId,\n });\n }\n\n /** @deprecated Use ServiceContext.createCallerContext. */\n static createUserContext(\n token: string,\n userId: string,\n userName?: string,\n userEmail?: string,\n ): CallerContext & UserContext {\n warnContextDeprecation(\n \"ServiceContext.createUserContext\",\n \"ServiceContext.createCallerContext\",\n );\n const caller = ServiceContext.createCallerContext(\n token,\n userId,\n userName,\n userEmail,\n );\n const warehouseId = WarehouseResource.get()?.warehouseId;\n return legacyUserContext(caller, () => warehouseId);\n }\n\n /**\n * Get the client options for WorkspaceClient.\n * Exposed for testing purposes.\n */\n static getClientOptions(): ClientOptions {\n return getClientOptions();\n }\n\n private static async createContext(\n options?: { warehouseId?: boolean | string },\n client?: WorkspaceClient,\n ): Promise<ServiceContextState> {\n try {\n const wsClient =\n client ?? createWorkspaceClient({ clientOptions: getClientOptions() });\n\n const [resolvedWorkspaceId, currentUser, resolvedResources] =\n await Promise.all([\n ServiceContext.getWorkspaceId(wsClient),\n wsClient.currentUser.me(),\n WarehouseResource.resolve(wsClient, options?.warehouseId),\n ]);\n\n if (!currentUser.id) {\n throw ConfigurationError.resourceNotFound(\"Service user ID\");\n }\n\n const resources = WarehouseResource.bind(resolvedResources);\n\n return Object.freeze({\n client: wsClient,\n serviceUserId: currentUser.id,\n get warehouseId() {\n warnContextDeprecation(\n \"ServiceContextState.warehouseId\",\n \"getWarehouseId() from @databricks/appkit\",\n );\n return resources.warehouseId;\n },\n workspaceId: Promise.resolve(resolvedWorkspaceId),\n });\n } catch (e) {\n if (e instanceof ConfigError) {\n throw ConfigurationError.databricksAuthenticationSetupFailed(\n e.baseMessage,\n { cause: e },\n );\n }\n throw e;\n }\n }\n\n private static async getWorkspaceId(\n client: WorkspaceClient,\n ): Promise<string> {\n if (process.env.DATABRICKS_WORKSPACE_ID) {\n return process.env.DATABRICKS_WORKSPACE_ID;\n }\n\n const response = (await client.apiClient.request({\n path: \"/api/2.0/preview/scim/v2/Me\",\n method: \"GET\",\n headers: new Headers(),\n raw: false,\n query: {},\n responseHeaders: [\"x-databricks-org-id\"],\n })) as { \"x-databricks-org-id\": string };\n\n if (!response[\"x-databricks-org-id\"]) {\n throw ConfigurationError.resourceNotFound(\"Workspace ID\");\n }\n\n return response[\"x-databricks-org-id\"];\n }\n\n /**\n * Reset the service context. Only for testing purposes.\n */\n static reset(): void {\n ServiceContext.instance = null;\n ServiceContext.initPromise = null;\n WarehouseResource.reset();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA4CA,IAAa,iBAAb,MAAa,eAAe;CAC1B,OAAe,WAAuC;CACtD,OAAe,cAAmD;;;;;;;;;CAUlE,aAAa,WACX,SACA,QAC8B;AAC9B,MAAI,eAAe,SACjB,QAAO,eAAe;AAGxB,MAAI,eAAe,YACjB,QAAO,eAAe;AAGxB,iBAAe,cAAc,eAAe,cAAc,SAAS,OAAO;AAC1E,iBAAe,WAAW,MAAM,eAAe;AAC/C,SAAO,eAAe;;;;;;CAOxB,OAAO,MAA2B;AAChC,MAAI,CAAC,eAAe,SAClB,OAAM,oBAAoB,eACxB,kBACA,yCACD;AAEH,SAAO,eAAe;;;;;CAMxB,OAAO,gBAAyB;AAC9B,SAAO,eAAe,aAAa;;;;;;;;;;;CAYrC,OAAO,oBACL,OACA,QACA,UACA,WACe;AACf,MAAI,CAAC,MACH,OAAM,oBAAoB,aAAa,aAAa;EAGtD,MAAM,OAAO,QAAQ,IAAI;AACzB,MAAI,CAAC,KACH,OAAM,mBAAmB,cAAc,kBAAkB;EAG3D,MAAM,aAAa,eAAe,KAAK;EAKvC,MAAM,aAAa,sBAAsB;GACvC;GACA;GACA,UAAU;GACV,eAAe,kBAAkB;GAClC,CAAC;EAEF,MAAM,mBAAmB,WAAW,SAAS,CAC1C,OAAO,MAAM,CACb,OAAO,MAAM,CACb,MAAM,GAAG,GAAG;AAEf,SAAO,sBAAsB;GAC3B,QAAQ;GACR,WAAW;IAAE,MAAM;IAAQ;IAAQ;IAAU;IAAW;GACxD;GACA,aAAa,WAAW;GACzB,CAAC;;;CAIJ,OAAO,kBACL,OACA,QACA,UACA,WAC6B;AAC7B,yBACE,oCACA,qCACD;EACD,MAAM,SAAS,eAAe,oBAC5B,OACA,QACA,UACA,UACD;EACD,MAAM,cAAc,kBAAkB,KAAK,EAAE;AAC7C,SAAO,kBAAkB,cAAc,YAAY;;;;;;CAOrD,OAAO,mBAAkC;AACvC,SAAO,kBAAkB;;CAG3B,aAAqB,cACnB,SACA,QAC8B;AAC9B,MAAI;GACF,MAAM,WACJ,UAAU,sBAAsB,EAAE,eAAe,kBAAkB,EAAE,CAAC;GAExE,MAAM,CAAC,qBAAqB,aAAa,qBACvC,MAAM,QAAQ,IAAI;IAChB,eAAe,eAAe,SAAS;IACvC,SAAS,YAAY,IAAI;IACzB,kBAAkB,QAAQ,UAAU,SAAS,YAAY;IAC1D,CAAC;AAEJ,OAAI,CAAC,YAAY,GACf,OAAM,mBAAmB,iBAAiB,kBAAkB;GAG9D,MAAM,YAAY,kBAAkB,KAAK,kBAAkB;AAE3D,UAAO,OAAO,OAAO;IACnB,QAAQ;IACR,eAAe,YAAY;IAC3B,IAAI,cAAc;AAChB,4BACE,mCACA,2CACD;AACD,YAAO,UAAU;;IAEnB,aAAa,QAAQ,QAAQ,oBAAoB;IAClD,CAAC;WACK,GAAG;AACV,OAAI,aAAa,YACf,OAAM,mBAAmB,oCACvB,EAAE,aACF,EAAE,OAAO,GAAG,CACb;AAEH,SAAM;;;CAIV,aAAqB,eACnB,QACiB;AACjB,MAAI,QAAQ,IAAI,wBACd,QAAO,QAAQ,IAAI;EAGrB,MAAM,WAAY,MAAM,OAAO,UAAU,QAAQ;GAC/C,MAAM;GACN,QAAQ;GACR,SAAS,IAAI,SAAS;GACtB,KAAK;GACL,OAAO,EAAE;GACT,iBAAiB,CAAC,sBAAsB;GACzC,CAAC;AAEF,MAAI,CAAC,SAAS,uBACZ,OAAM,mBAAmB,iBAAiB,eAAe;AAG3D,SAAO,SAAS;;;;;CAMlB,OAAO,QAAc;AACnB,iBAAe,WAAW;AAC1B,iBAAe,cAAc;AAC7B,oBAAkB,OAAO"}
@@ -1,9 +1,10 @@
1
1
  import { ServiceContextState } from "./service-context.js";
2
+ import { ExecutionContext } from "./caller-context.js";
2
3
 
3
4
  //#region src/context/user-context.d.ts
4
5
  /**
5
- * User execution context extends the service context with user-specific data.
6
- * Created on-demand when asUser(req) is called.
6
+ * @deprecated Use CallerContext and its principal field. Kept for callers
7
+ * that construct the legacy shape or read its flat identity fields.
7
8
  */
8
9
  interface UserContext {
9
10
  /** WorkspaceClient authenticated as the user */
@@ -16,17 +17,13 @@ interface UserContext {
16
17
  userEmail?: string;
17
18
  /** Truncated SHA-256 hash of the user's OBO token, used to detect token rotation */
18
19
  tokenFingerprint?: string;
19
- /** Promise that resolves to the warehouse ID (inherited from service context, only present when a plugin requires `SQL_WAREHOUSE` resource) */
20
+ /** @deprecated Use getWarehouseId() from @databricks/appkit. */
20
21
  warehouseId?: Promise<string>;
21
22
  /** Promise that resolves to the workspace ID (inherited from service context) */
22
23
  workspaceId: Promise<string>;
23
24
  /** Flag indicating this is a user context */
24
25
  isUserContext: true;
25
26
  }
26
- /**
27
- * Execution context can be either service or user context.
28
- */
29
- type ExecutionContext = ServiceContextState | UserContext;
30
27
  //#endregion
31
- export { ExecutionContext, UserContext };
28
+ export { UserContext };
32
29
  //# sourceMappingURL=user-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"user-context.d.ts","names":[],"sources":["../../src/context/user-context.ts"],"mappings":";;;;;AAMA;;UAAiB,WAAA;EAEP;EAAR,MAAA,EAAQ,mBAAA;EAYK;EAVb,MAAA;EAUoB;EARpB,QAAA;EAJQ;EAMR,SAAA;EAFA;EAIA,gBAAA;EAAA;EAEA,WAAA,GAAc,OAAA;EAAA;EAEd,WAAA,EAAa,OAAA;EAAA;EAEb,aAAA;AAAA;;AAMF;;KAAY,gBAAA,GAAmB,mBAAA,GAAsB,WAAA"}
1
+ {"version":3,"file":"user-context.d.ts","names":[],"sources":["../../src/context/user-context.ts"],"mappings":";;;;;AAeA;;;UAAiB,WAAA;EAYD;EAVd,MAAA,EAAQ,mBAAA;EAYY;EAVpB,MAAA;EAFA;EAIA,QAAA;EAFA;EAIA,SAAA;EAAA;EAEA,gBAAA;EAEA;EAAA,WAAA,GAAc,OAAA;EAEd;EAAA,WAAA,EAAa,OAAA;EAEb;EAAA,aAAA;AAAA"}
@@ -1,11 +1,38 @@
1
+ import { snapshotCallerContext } from "./caller-context.js";
2
+ import { warnContextDeprecation } from "./deprecation.js";
3
+
1
4
  //#region src/context/user-context.ts
2
- /**
3
- * Check if an execution context is a user context.
4
- */
5
- function isUserContext(ctx) {
6
- return "isUserContext" in ctx && ctx.isUserContext === true;
5
+ function legacyIdentityContext(ctx) {
6
+ const caller = snapshotCallerContext(ctx);
7
+ const { principal } = caller;
8
+ return Object.freeze({
9
+ ...caller,
10
+ get userId() {
11
+ warnContextDeprecation("UserContext.userId", "CallerContext.principal.userId");
12
+ return principal.userId;
13
+ },
14
+ get userName() {
15
+ warnContextDeprecation("UserContext.userName", "CallerContext.principal.userName");
16
+ return principal.userName;
17
+ },
18
+ get userEmail() {
19
+ warnContextDeprecation("UserContext.userEmail", "CallerContext.principal.userEmail");
20
+ return principal.userEmail;
21
+ },
22
+ get isUserContext() {
23
+ warnContextDeprecation("UserContext.isUserContext", "CallerContext.principal.type");
24
+ return true;
25
+ }
26
+ });
27
+ }
28
+ /** Expose the old resource field only through the deprecated context APIs. */
29
+ function legacyUserContext(ctx, resolveWarehouseId) {
30
+ return Object.freeze(Object.defineProperties({ get warehouseId() {
31
+ warnContextDeprecation("UserContext.warehouseId", "getWarehouseId() from @databricks/appkit");
32
+ return resolveWarehouseId();
33
+ } }, Object.getOwnPropertyDescriptors(legacyIdentityContext(ctx))));
7
34
  }
8
35
 
9
36
  //#endregion
10
- export { isUserContext };
37
+ export { legacyUserContext };
11
38
  //# sourceMappingURL=user-context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"user-context.js","names":[],"sources":["../../src/context/user-context.ts"],"sourcesContent":["import type { ServiceContextState } from \"./service-context\";\n\n/**\n * User execution context extends the service context with user-specific data.\n * Created on-demand when asUser(req) is called.\n */\nexport interface UserContext {\n /** WorkspaceClient authenticated as the user */\n client: ServiceContextState[\"client\"];\n /** The user's ID (from request headers) */\n userId: string;\n /** The user's name (from request headers) */\n userName?: string;\n /** The user's email (from `x-forwarded-email` header) */\n userEmail?: string;\n /** Truncated SHA-256 hash of the user's OBO token, used to detect token rotation */\n tokenFingerprint?: string;\n /** Promise that resolves to the warehouse ID (inherited from service context, only present when a plugin requires `SQL_WAREHOUSE` resource) */\n warehouseId?: Promise<string>;\n /** Promise that resolves to the workspace ID (inherited from service context) */\n workspaceId: Promise<string>;\n /** Flag indicating this is a user context */\n isUserContext: true;\n}\n\n/**\n * Execution context can be either service or user context.\n */\nexport type ExecutionContext = ServiceContextState | UserContext;\n\n/**\n * Check if an execution context is a user context.\n */\nexport function isUserContext(ctx: ExecutionContext): ctx is UserContext {\n return \"isUserContext\" in ctx && ctx.isUserContext === true;\n}\n"],"mappings":";;;;AAiCA,SAAgB,cAAc,KAA2C;AACvE,QAAO,mBAAmB,OAAO,IAAI,kBAAkB"}
1
+ {"version":3,"file":"user-context.js","names":[],"sources":["../../src/context/user-context.ts"],"sourcesContent":["import {\n type CallerContext,\n type ExecutionContext,\n isCallerContext,\n snapshotCallerContext,\n} from \"./caller-context\";\nimport { warnContextDeprecation } from \"./deprecation\";\nimport type { ServiceContextState } from \"./service-context\";\n\nexport type { ExecutionContext } from \"./caller-context\";\n\n/**\n * @deprecated Use CallerContext and its principal field. Kept for callers\n * that construct the legacy shape or read its flat identity fields.\n */\nexport interface UserContext {\n /** WorkspaceClient authenticated as the user */\n client: ServiceContextState[\"client\"];\n /** The user's ID (from request headers) */\n userId: string;\n /** The user's name (from request headers) */\n userName?: string;\n /** The user's email (from `x-forwarded-email` header) */\n userEmail?: string;\n /** Truncated SHA-256 hash of the user's OBO token, used to detect token rotation */\n tokenFingerprint?: string;\n /** @deprecated Use getWarehouseId() from @databricks/appkit. */\n warehouseId?: Promise<string>;\n /** Promise that resolves to the workspace ID (inherited from service context) */\n workspaceId: Promise<string>;\n /** Flag indicating this is a user context */\n isUserContext: true;\n}\n\n/**\n * @deprecated Use snapshotCallerContext for identity-only snapshots.\n * Retains the legacy fields for existing callers.\n */\nexport function immutableCallerContext(\n ctx: CallerContext,\n): CallerContext & UserContext {\n warnContextDeprecation(\"immutableCallerContext\", \"snapshotCallerContext\");\n return legacyIdentityContext(ctx);\n}\n\nfunction legacyIdentityContext(\n ctx: CallerContext,\n): CallerContext & UserContext {\n const caller = snapshotCallerContext(ctx);\n const { principal } = caller;\n return Object.freeze({\n ...caller,\n get userId() {\n warnContextDeprecation(\n \"UserContext.userId\",\n \"CallerContext.principal.userId\",\n );\n return principal.userId;\n },\n get userName() {\n warnContextDeprecation(\n \"UserContext.userName\",\n \"CallerContext.principal.userName\",\n );\n return principal.userName;\n },\n get userEmail() {\n warnContextDeprecation(\n \"UserContext.userEmail\",\n \"CallerContext.principal.userEmail\",\n );\n return principal.userEmail;\n },\n get isUserContext(): true {\n warnContextDeprecation(\n \"UserContext.isUserContext\",\n \"CallerContext.principal.type\",\n );\n return true;\n },\n });\n}\n\n/** Expose the old resource field only through the deprecated context APIs. */\nexport function legacyUserContext(\n ctx: CallerContext,\n resolveWarehouseId: () => Promise<string> | undefined,\n): CallerContext & UserContext {\n return Object.freeze(\n Object.defineProperties(\n {\n get warehouseId() {\n warnContextDeprecation(\n \"UserContext.warehouseId\",\n \"getWarehouseId() from @databricks/appkit\",\n );\n return resolveWarehouseId();\n },\n },\n Object.getOwnPropertyDescriptors(legacyIdentityContext(ctx)),\n ),\n ) as CallerContext & UserContext;\n}\n\n/** Normalize legacy inputs before opening a caller scope. */\nexport function toCallerContext(\n ctx: CallerContext | UserContext,\n): CallerContext {\n if (\"principal\" in ctx) return ctx;\n return {\n client: ctx.client,\n principal: {\n type: \"user\",\n userId: ctx.userId,\n userName: ctx.userName,\n userEmail: ctx.userEmail,\n },\n tokenFingerprint: ctx.tokenFingerprint,\n workspaceId: ctx.workspaceId,\n };\n}\n\n/**\n * @deprecated Use isCallerContext. Active caller contexts retain the legacy\n * identity accessors for callers narrowed by this guard.\n */\nexport function isUserContext(\n ctx: ExecutionContext | UserContext,\n): ctx is UserContext & Partial<CallerContext> {\n warnContextDeprecation(\"isUserContext\", \"isCallerContext\");\n return \"principal\" in ctx\n ? isCallerContext(ctx)\n : \"isUserContext\" in ctx && ctx.isUserContext === true;\n}\n"],"mappings":";;;;AA6CA,SAAS,sBACP,KAC6B;CAC7B,MAAM,SAAS,sBAAsB,IAAI;CACzC,MAAM,EAAE,cAAc;AACtB,QAAO,OAAO,OAAO;EACnB,GAAG;EACH,IAAI,SAAS;AACX,0BACE,sBACA,iCACD;AACD,UAAO,UAAU;;EAEnB,IAAI,WAAW;AACb,0BACE,wBACA,mCACD;AACD,UAAO,UAAU;;EAEnB,IAAI,YAAY;AACd,0BACE,yBACA,oCACD;AACD,UAAO,UAAU;;EAEnB,IAAI,gBAAsB;AACxB,0BACE,6BACA,+BACD;AACD,UAAO;;EAEV,CAAC;;;AAIJ,SAAgB,kBACd,KACA,oBAC6B;AAC7B,QAAO,OAAO,OACZ,OAAO,iBACL,EACE,IAAI,cAAc;AAChB,yBACE,2BACA,2CACD;AACD,SAAO,oBAAoB;IAE9B,EACD,OAAO,0BAA0B,sBAAsB,IAAI,CAAC,CAC7D,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"appkit.d.ts","names":[],"sources":["../../src/core/appkit.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8ZsB,SAAA,WACV,UAAA,CAAW,iBAAA,qBAAA,CAErB,MAAA;EACE,OAAA,GAAU,CAAA;EACV,SAAA,GAAY,eAAA;EACZ,KAAA,GAAQ,WAAA;EACR,MAAA,GAAS,eAAA;EAET,cAAA,IAAkB,MAAA,EAAQ,SAAA,CAAU,CAAA,aAAc,OAAA;EAClD,wBAAA;AAAA,IAED,OAAA,CAAQ,SAAA,CAAU,CAAA"}
1
+ {"version":3,"file":"appkit.d.ts","names":[],"sources":["../../src/core/appkit.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmasB,SAAA,WACV,UAAA,CAAW,iBAAA,qBAAA,CAErB,MAAA;EACE,OAAA,GAAU,CAAA;EACV,SAAA,GAAY,eAAA;EACZ,KAAA,GAAQ,WAAA;EACR,MAAA,GAAS,eAAA;EAET,cAAA,IAAkB,MAAA,EAAQ,SAAA,CAAU,CAAA,aAAc,OAAA;EAClD,wBAAA;AAAA,IAED,OAAA,CAAQ,SAAA,CAAU,CAAA"}
@@ -1,6 +1,7 @@
1
1
  import { version } from "../appkit/package.js";
2
- import { ServiceContext } from "../context/service-context.js";
2
+ import { getConfiguredWarehouseId } from "../resources/warehouse.js";
3
3
  import { createLogger } from "../logging/logger.js";
4
+ import { ServiceContext } from "../context/service-context.js";
4
5
  import { TelemetryManager } from "../telemetry/telemetry-manager.js";
5
6
  import "../telemetry/index.js";
6
7
  import { CacheManager } from "../cache/index.js";
@@ -114,8 +115,9 @@ var AppKit = class AppKit {
114
115
  const rawPlugins = AppKit.filterDevOnlyPlugins(withDefaults);
115
116
  const registry = new ResourceRegistry();
116
117
  registry.collectResources(rawPlugins);
118
+ registry.validate();
117
119
  const needsWarehouse = registry.getRequired().some((r) => r.type === ResourceType.SQL_WAREHOUSE);
118
- await ServiceContext.initialize({ warehouseId: needsWarehouse }, config?.client);
120
+ await ServiceContext.initialize({ warehouseId: getConfiguredWarehouseId(registry.getRequired()) ?? needsWarehouse }, config?.client);
119
121
  registry.enforceValidation();
120
122
  const instance = new AppKit({ plugins: AppKit.preparePlugins(rawPlugins) });
121
123
  await Promise.all(instance.#setupPromises);
@@ -1 +1 @@
1
- {"version":3,"file":"appkit.js","names":["#context","#pluginInstances","#setupPromises","#lifecycle","productVersion"],"sources":["../../src/core/appkit.ts"],"sourcesContent":["import type {\n BasePlugin,\n CacheConfig,\n InputPluginMap,\n OptionalConfigPluginDef,\n PluginConstructor,\n PluginData,\n PluginMap,\n} from \"shared\";\n\nimport { version as productVersion } from \"../../package.json\";\nimport { CacheManager } from \"../cache\";\nimport { ServiceContext } from \"../context\";\nimport {\n isInternalTelemetryEnabled,\n TelemetryReporter,\n} from \"../internal-telemetry\";\nimport { createLogger } from \"../logging/logger\";\nimport { isPlainObject } from \"../plugin/plugin\";\nimport { uiVariants } from \"../plugins/ui-variants\";\nimport { ResourceRegistry, ResourceType } from \"../registry\";\nimport type { TelemetryConfig } from \"../telemetry\";\nimport { TelemetryManager } from \"../telemetry\";\nimport type { WorkspaceClient } from \"../workspace-client\";\nimport { LifecycleManager } from \"./lifecycle-manager\";\nimport { isToolProvider, PluginContext } from \"./plugin-context\";\n\nconst logger = createLogger(\"appkit\");\n\n/**\n * Internal teardown entry for the test harness (see `createTestApp`).\n * Symbol-keyed so it cannot collide with — or be shadowed by — a plugin\n * manifest name, and so it stays off the public `PluginMap` surface.\n * @internal\n */\nexport const disposeApp = Symbol(\"appkit.internal.dispose\");\n\nexport class AppKit<TPlugins extends InputPluginMap> {\n #pluginInstances: Record<string, BasePlugin> = {};\n #setupPromises: Promise<void>[] = [];\n #context: PluginContext;\n /** Owns the shutdown sequence; assigned once every plugin has started. */\n #lifecycle: LifecycleManager | undefined;\n\n private constructor(config: { plugins: TPlugins }) {\n const { plugins, ...globalConfig } = config;\n\n this.#context = new PluginContext();\n\n const pluginEntries = Object.entries(plugins);\n\n const corePlugins = pluginEntries.filter(([_, p]) => {\n return (p?.plugin?.phase ?? \"normal\") === \"core\";\n });\n const normalPlugins = pluginEntries.filter(\n ([_, p]) => (p?.plugin?.phase ?? \"normal\") === \"normal\",\n );\n const deferredPlugins = pluginEntries.filter(\n ([_, p]) => (p?.plugin?.phase ?? \"normal\") === \"deferred\",\n );\n\n for (const [name, pluginData] of corePlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n\n for (const [name, pluginData] of normalPlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n\n for (const [name, pluginData] of deferredPlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n }\n\n private createAndRegisterPlugin<T extends PluginConstructor>(\n config: Omit<{ plugins: TPlugins }, \"plugins\">,\n name: string,\n pluginData: OptionalConfigPluginDef<T>,\n extraData?: Record<string, unknown>,\n ) {\n const { plugin: Plugin, config: pluginConfig } = pluginData;\n const baseConfig = {\n ...config,\n ...Plugin.DEFAULT_CONFIG,\n ...pluginConfig,\n name,\n ...extraData,\n };\n const pluginInstance = new Plugin(baseConfig);\n\n if (typeof pluginInstance.attachContext === \"function\") {\n pluginInstance.attachContext({\n context: this.#context,\n telemetryConfig: baseConfig.telemetry,\n });\n }\n\n this.#pluginInstances[name] = pluginInstance;\n\n this.#context.registerPlugin(name, pluginInstance);\n if (isToolProvider(pluginInstance)) {\n this.#context.registerToolProvider(name, pluginInstance);\n }\n\n this.#setupPromises.push(pluginInstance.setup());\n\n const self = this;\n\n // The manifest `name` is camelCase, so it doubles as the public handle key\n // (`appkit.aiSearch`). The kebab HTTP route is derived separately.\n Object.defineProperty(this, name, {\n get() {\n const plugin = self.#pluginInstances[name];\n return self.wrapWithAsUser(plugin);\n },\n enumerable: true,\n });\n }\n\n /**\n * Binds all function properties in an exports object to the given context.\n * Recurses into plain objects to handle nested APIs (e.g., volume APIs).\n */\n private bindExportMethods(\n exports: Record<string, unknown>,\n context: BasePlugin,\n ) {\n for (const key in exports) {\n if (!Object.hasOwn(exports, key)) continue;\n const val = exports[key];\n if (typeof val === \"function\") {\n exports[key] = (val as (...args: unknown[]) => unknown).bind(context);\n } else if (isPlainObject(val)) {\n this.bindExportMethods(val as Record<string, unknown>, context);\n }\n }\n }\n\n /**\n * Wraps a plugin's exports with an `asUser` method that returns\n * a user-scoped version of the exports.\n *\n * When `exports()` returns a callable (function), it is returned as-is\n * since the plugin manages its own `asUser` per-call (e.g. files plugin).\n * When it returns a plain object, the standard `asUser` wrapper is added.\n *\n * The OBO-side wrapping lives inside `Plugin.asUser` — calling\n * `plugin.asUser(req).exports()` returns exports whose functions already\n * run inside the user's AsyncLocalStorage scope. AppKit only adapts the\n * shape; it does not own the user-context concept.\n */\n private wrapWithAsUser<T extends BasePlugin>(plugin: T) {\n // If plugin doesn't implement exports(), return empty object\n const pluginExports = plugin.exports?.() ?? {};\n\n // If exports is a function, the plugin manages its own asUser pattern\n if (typeof pluginExports === \"function\") {\n return pluginExports;\n }\n\n const objExports = pluginExports as Record<string, unknown>;\n this.bindExportMethods(objExports, plugin);\n\n // If plugin doesn't support asUser (no asUser method), return exports as-is\n if (typeof (plugin as any).asUser !== \"function\") {\n return objExports;\n }\n\n return {\n ...objExports,\n /**\n * Execute operations using the user's identity from the request.\n * Returns user-scoped exports where all methods execute with the\n * user's Databricks credentials instead of the service principal.\n */\n asUser: (req: import(\"express\").Request) =>\n (plugin as any).asUser(req).exports() as Record<string, unknown>,\n };\n }\n\n static async _createApp<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(\n config: {\n plugins?: T;\n telemetry?: TelemetryConfig;\n cache?: CacheConfig;\n client?: WorkspaceClient;\n onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;\n disableInternalTelemetry?: boolean;\n /**\n * Skip installing the SIGTERM/SIGINT handlers. Internal, and not exposed\n * on {@link createApp}: only the test harness sets it, because it boots\n * repeatedly in one process and manages its own teardown, so\n * accumulating signal handlers would be a leak.\n */\n installSignalHandlers?: boolean;\n } = {},\n ): Promise<PluginMap<T>> {\n // Initialize core services\n TelemetryManager.initialize(config?.telemetry);\n await CacheManager.getInstance(config?.cache);\n\n const withDefaults = AppKit.withDefaultPlugins(config.plugins as T);\n const rawPlugins = AppKit.filterDevOnlyPlugins(withDefaults);\n\n // Collect manifest resources via registry\n const registry = new ResourceRegistry();\n registry.collectResources(rawPlugins);\n\n // Derive ServiceContext needs from what manifests declared\n const needsWarehouse = registry\n .getRequired()\n .some((r) => r.type === ResourceType.SQL_WAREHOUSE);\n await ServiceContext.initialize(\n { warehouseId: needsWarehouse },\n config?.client,\n );\n\n // Validate env vars\n registry.enforceValidation();\n\n const preparedPlugins = AppKit.preparePlugins(rawPlugins);\n const mergedConfig = {\n plugins: preparedPlugins,\n };\n\n const instance = new AppKit(mergedConfig);\n\n await Promise.all(instance.#setupPromises);\n\n // Build the global tracer provider now that every plugin's setup() has run\n // and contributed any span processors. Deferred to here so a single provider\n // carries all processors (OTLP + plugin-contributed); see TelemetryManager.\n TelemetryManager.start();\n\n await instance.#context.emitLifecycle(\"setup:complete\");\n\n const app = instance as unknown as PluginMap<T>;\n\n if (config.onPluginsReady) {\n logger.debug(\"Running onPluginsReady hook\");\n await config.onPluginsReady(app);\n logger.debug(\"onPluginsReady hook completed\");\n }\n\n if (isInternalTelemetryEnabled(config)) {\n AppKit.bootstrapInternalTelemetry();\n }\n\n const serverPlugin = instance.#pluginInstances.server;\n if (serverPlugin && typeof (serverPlugin as any).start === \"function\") {\n await (serverPlugin as any).start();\n }\n\n // Core owns graceful shutdown: install the signal handlers once every\n // plugin has started. Applies uniformly whether or not a server plugin\n // is present — server-less apps still get their telemetry flushed and\n // plugin shutdown() hooks run.\n instance.#lifecycle = new LifecycleManager(instance.#context);\n if (config.installSignalHandlers !== false) {\n instance.#lifecycle.installSignalHandlers();\n }\n\n return app;\n }\n\n /**\n * Internal teardown entry point for the test harness: delegates to the\n * lifecycle's non-exiting `shutdown({ exit: false })` (the phases are\n * canonical there). Not public API — AppKit does not support re-booting or\n * embedding, so real apps tear down only through the signal path. The harness\n * drops the core singletons and restores env after this resolves.\n * @internal\n */\n async [disposeApp](): Promise<void> {\n await this.#lifecycle?.shutdown({ exit: false });\n }\n\n private static bootstrapInternalTelemetry(): void {\n const serviceCtx = ServiceContext.get();\n const reporter = TelemetryReporter.initialize({\n workspaceId: serviceCtx.workspaceId,\n client: serviceCtx.client,\n appId: process.env.DATABRICKS_CLIENT_ID || \"\",\n appkitVersion: productVersion,\n });\n reporter.start();\n reporter.sendStartup().catch(() => {});\n }\n\n /**\n * Injects framework-owned default plugins the app author shouldn't have to\n * register by hand. Runs before {@link filterDevOnlyPlugins}, so a default\n * that is itself `devOnly` (like `ui-variants`) is present in dev and stripped\n * in prod through the exact same guard as any user plugin — never alive in a\n * deployed app.\n *\n * Currently injects the dev-only `ui-variants` recorder that backs the\n * `<Variants>` UI picker, so neither the developer nor their coding agent has\n * to remember to add it. If the app already registered it explicitly, that\n * entry wins and no duplicate is added.\n */\n private static withDefaultPlugins<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(plugins: T): T {\n const list = (plugins ?? []) as PluginData<\n PluginConstructor,\n unknown,\n string\n >[];\n\n const defaults: PluginData<PluginConstructor, unknown, string>[] = [\n uiVariants(),\n ];\n\n const missing = defaults.filter(\n (def) => !list.some((p) => p?.name === def.name),\n );\n\n return [...list, ...missing] as T;\n }\n\n /**\n * Drops plugins whose manifest declares `devOnly: true` unless\n * `NODE_ENV === \"development\"`. Runs before resource collection so a skipped\n * plugin is never constructed, never injects routes, and has its resource\n * requirements ignored — the framework, not the app author, enforces that\n * dev-only tooling can never run in a deployed app.\n *\n * This is the primary guard; plugins that expose a mutating dev endpoint\n * should still keep an in-handler `NODE_ENV` check as fail-safe defense\n * against being mounted through a path that bypasses this filter.\n */\n private static filterDevOnlyPlugins<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(plugins: T): T {\n if (!plugins || process.env.NODE_ENV === \"development\") return plugins;\n\n return plugins.filter((pluginData) => {\n const isDevOnly = pluginData?.plugin?.manifest?.devOnly === true;\n if (isDevOnly) {\n logger.debug(\n \"Skipping dev-only plugin %s (NODE_ENV=%s)\",\n pluginData?.name ?? \"unknown\",\n process.env.NODE_ENV ?? \"<unset>\",\n );\n }\n return !isDevOnly;\n }) as T;\n }\n\n private static preparePlugins(\n plugins: PluginData<PluginConstructor, unknown, string>[],\n ) {\n const result: InputPluginMap = {};\n for (const currentPlugin of plugins) {\n result[currentPlugin.name] = {\n plugin: currentPlugin.plugin,\n config: currentPlugin.config as Record<string, unknown>,\n };\n }\n return result;\n }\n}\n\n/**\n * Bootstraps AppKit with the provided configuration.\n *\n * Initializes telemetry, cache, and service context, then registers plugins\n * in phase order (core, normal, deferred) and awaits their setup.\n * If a `onPluginsReady` callback is provided it runs after plugin setup but\n * before the server starts, giving you access to the full appkit handle\n * for registering custom routes or performing async setup.\n * The returned object maps each plugin name to its `exports()` API,\n * with an `asUser(req)` method for user-scoped execution.\n *\n * @returns A `PluginMap` keyed by plugin name with typed exports\n *\n * @example Minimal server\n * ```ts\n * import { createApp, server } from \"@databricks/appkit\";\n *\n * await createApp({\n * plugins: [server()],\n * });\n * ```\n *\n * @example Server with custom routes via onPluginsReady\n * ```ts\n * import { createApp, server, analytics } from \"@databricks/appkit\";\n *\n * await createApp({\n * plugins: [server(), analytics({})],\n * onPluginsReady(appkit) {\n * appkit.server.extend((app) => {\n * app.get(\"/custom\", (_req, res) => res.json({ ok: true }));\n * });\n * },\n * });\n * ```\n */\nexport async function createApp<\n T extends PluginData<PluginConstructor, unknown, string>[],\n>(\n config: {\n plugins?: T;\n telemetry?: TelemetryConfig;\n cache?: CacheConfig;\n client?: WorkspaceClient;\n /** Runs after plugin setup but **before** the server starts. */\n onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;\n disableInternalTelemetry?: boolean;\n } = {},\n): Promise<PluginMap<T>> {\n return AppKit._createApp(config);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"appkit.js","names":["#context","#pluginInstances","#setupPromises","#lifecycle","productVersion"],"sources":["../../src/core/appkit.ts"],"sourcesContent":["import type {\n BasePlugin,\n CacheConfig,\n InputPluginMap,\n OptionalConfigPluginDef,\n PluginConstructor,\n PluginData,\n PluginMap,\n} from \"shared\";\n\nimport { version as productVersion } from \"../../package.json\";\nimport { CacheManager } from \"../cache\";\nimport { ServiceContext } from \"../context\";\nimport {\n isInternalTelemetryEnabled,\n TelemetryReporter,\n} from \"../internal-telemetry\";\nimport { createLogger } from \"../logging/logger\";\nimport { isPlainObject } from \"../plugin/plugin\";\nimport { uiVariants } from \"../plugins/ui-variants\";\nimport { ResourceRegistry, ResourceType } from \"../registry\";\nimport { getConfiguredWarehouseId } from \"../resources/warehouse\";\nimport type { TelemetryConfig } from \"../telemetry\";\nimport { TelemetryManager } from \"../telemetry\";\nimport type { WorkspaceClient } from \"../workspace-client\";\nimport { LifecycleManager } from \"./lifecycle-manager\";\nimport { isToolProvider, PluginContext } from \"./plugin-context\";\n\nconst logger = createLogger(\"appkit\");\n\n/**\n * Internal teardown entry for the test harness (see `createTestApp`).\n * Symbol-keyed so it cannot collide with — or be shadowed by — a plugin\n * manifest name, and so it stays off the public `PluginMap` surface.\n * @internal\n */\nexport const disposeApp = Symbol(\"appkit.internal.dispose\");\n\nexport class AppKit<TPlugins extends InputPluginMap> {\n #pluginInstances: Record<string, BasePlugin> = {};\n #setupPromises: Promise<void>[] = [];\n #context: PluginContext;\n /** Owns the shutdown sequence; assigned once every plugin has started. */\n #lifecycle: LifecycleManager | undefined;\n\n private constructor(config: { plugins: TPlugins }) {\n const { plugins, ...globalConfig } = config;\n\n this.#context = new PluginContext();\n\n const pluginEntries = Object.entries(plugins);\n\n const corePlugins = pluginEntries.filter(([_, p]) => {\n return (p?.plugin?.phase ?? \"normal\") === \"core\";\n });\n const normalPlugins = pluginEntries.filter(\n ([_, p]) => (p?.plugin?.phase ?? \"normal\") === \"normal\",\n );\n const deferredPlugins = pluginEntries.filter(\n ([_, p]) => (p?.plugin?.phase ?? \"normal\") === \"deferred\",\n );\n\n for (const [name, pluginData] of corePlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n\n for (const [name, pluginData] of normalPlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n\n for (const [name, pluginData] of deferredPlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n }\n\n private createAndRegisterPlugin<T extends PluginConstructor>(\n config: Omit<{ plugins: TPlugins }, \"plugins\">,\n name: string,\n pluginData: OptionalConfigPluginDef<T>,\n extraData?: Record<string, unknown>,\n ) {\n const { plugin: Plugin, config: pluginConfig } = pluginData;\n const baseConfig = {\n ...config,\n ...Plugin.DEFAULT_CONFIG,\n ...pluginConfig,\n name,\n ...extraData,\n };\n const pluginInstance = new Plugin(baseConfig);\n\n if (typeof pluginInstance.attachContext === \"function\") {\n pluginInstance.attachContext({\n context: this.#context,\n telemetryConfig: baseConfig.telemetry,\n });\n }\n\n this.#pluginInstances[name] = pluginInstance;\n\n this.#context.registerPlugin(name, pluginInstance);\n if (isToolProvider(pluginInstance)) {\n this.#context.registerToolProvider(name, pluginInstance);\n }\n\n this.#setupPromises.push(pluginInstance.setup());\n\n const self = this;\n\n // The manifest `name` is camelCase, so it doubles as the public handle key\n // (`appkit.aiSearch`). The kebab HTTP route is derived separately.\n Object.defineProperty(this, name, {\n get() {\n const plugin = self.#pluginInstances[name];\n return self.wrapWithAsUser(plugin);\n },\n enumerable: true,\n });\n }\n\n /**\n * Binds all function properties in an exports object to the given context.\n * Recurses into plain objects to handle nested APIs (e.g., volume APIs).\n */\n private bindExportMethods(\n exports: Record<string, unknown>,\n context: BasePlugin,\n ) {\n for (const key in exports) {\n if (!Object.hasOwn(exports, key)) continue;\n const val = exports[key];\n if (typeof val === \"function\") {\n exports[key] = (val as (...args: unknown[]) => unknown).bind(context);\n } else if (isPlainObject(val)) {\n this.bindExportMethods(val as Record<string, unknown>, context);\n }\n }\n }\n\n /**\n * Wraps a plugin's exports with an `asUser` method that returns\n * a user-scoped version of the exports.\n *\n * When `exports()` returns a callable (function), it is returned as-is\n * since the plugin manages its own `asUser` per-call (e.g. files plugin).\n * When it returns a plain object, the standard `asUser` wrapper is added.\n *\n * The OBO-side wrapping lives inside `Plugin.asUser` — calling\n * `plugin.asUser(req).exports()` returns exports whose functions already\n * run inside the user's AsyncLocalStorage scope. AppKit only adapts the\n * shape; it does not own the user-context concept.\n */\n private wrapWithAsUser<T extends BasePlugin>(plugin: T) {\n // If plugin doesn't implement exports(), return empty object\n const pluginExports = plugin.exports?.() ?? {};\n\n // If exports is a function, the plugin manages its own asUser pattern\n if (typeof pluginExports === \"function\") {\n return pluginExports;\n }\n\n const objExports = pluginExports as Record<string, unknown>;\n this.bindExportMethods(objExports, plugin);\n\n // If plugin doesn't support asUser (no asUser method), return exports as-is\n if (typeof (plugin as any).asUser !== \"function\") {\n return objExports;\n }\n\n return {\n ...objExports,\n /**\n * Execute operations using the user's identity from the request.\n * Returns user-scoped exports where all methods execute with the\n * user's Databricks credentials instead of the service principal.\n */\n asUser: (req: import(\"express\").Request) =>\n (plugin as any).asUser(req).exports() as Record<string, unknown>,\n };\n }\n\n static async _createApp<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(\n config: {\n plugins?: T;\n telemetry?: TelemetryConfig;\n cache?: CacheConfig;\n client?: WorkspaceClient;\n onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;\n disableInternalTelemetry?: boolean;\n /**\n * Skip installing the SIGTERM/SIGINT handlers. Internal, and not exposed\n * on {@link createApp}: only the test harness sets it, because it boots\n * repeatedly in one process and manages its own teardown, so\n * accumulating signal handlers would be a leak.\n */\n installSignalHandlers?: boolean;\n } = {},\n ): Promise<PluginMap<T>> {\n // Initialize core services\n TelemetryManager.initialize(config?.telemetry);\n await CacheManager.getInstance(config?.cache);\n\n const withDefaults = AppKit.withDefaultPlugins(config.plugins as T);\n const rawPlugins = AppKit.filterDevOnlyPlugins(withDefaults);\n\n // Collect manifest resources via registry\n const registry = new ResourceRegistry();\n registry.collectResources(rawPlugins);\n registry.validate();\n\n // Derive ServiceContext needs from what manifests declared\n const needsWarehouse = registry\n .getRequired()\n .some((r) => r.type === ResourceType.SQL_WAREHOUSE);\n await ServiceContext.initialize(\n {\n warehouseId:\n getConfiguredWarehouseId(registry.getRequired()) ?? needsWarehouse,\n },\n config?.client,\n );\n\n // Validate env vars\n registry.enforceValidation();\n\n const preparedPlugins = AppKit.preparePlugins(rawPlugins);\n const mergedConfig = {\n plugins: preparedPlugins,\n };\n\n const instance = new AppKit(mergedConfig);\n\n await Promise.all(instance.#setupPromises);\n\n // Build the global tracer provider now that every plugin's setup() has run\n // and contributed any span processors. Deferred to here so a single provider\n // carries all processors (OTLP + plugin-contributed); see TelemetryManager.\n TelemetryManager.start();\n\n await instance.#context.emitLifecycle(\"setup:complete\");\n\n const app = instance as unknown as PluginMap<T>;\n\n if (config.onPluginsReady) {\n logger.debug(\"Running onPluginsReady hook\");\n await config.onPluginsReady(app);\n logger.debug(\"onPluginsReady hook completed\");\n }\n\n if (isInternalTelemetryEnabled(config)) {\n AppKit.bootstrapInternalTelemetry();\n }\n\n const serverPlugin = instance.#pluginInstances.server;\n if (serverPlugin && typeof (serverPlugin as any).start === \"function\") {\n await (serverPlugin as any).start();\n }\n\n // Core owns graceful shutdown: install the signal handlers once every\n // plugin has started. Applies uniformly whether or not a server plugin\n // is present — server-less apps still get their telemetry flushed and\n // plugin shutdown() hooks run.\n instance.#lifecycle = new LifecycleManager(instance.#context);\n if (config.installSignalHandlers !== false) {\n instance.#lifecycle.installSignalHandlers();\n }\n\n return app;\n }\n\n /**\n * Internal teardown entry point for the test harness: delegates to the\n * lifecycle's non-exiting `shutdown({ exit: false })` (the phases are\n * canonical there). Not public API — AppKit does not support re-booting or\n * embedding, so real apps tear down only through the signal path. The harness\n * drops the core singletons and restores env after this resolves.\n * @internal\n */\n async [disposeApp](): Promise<void> {\n await this.#lifecycle?.shutdown({ exit: false });\n }\n\n private static bootstrapInternalTelemetry(): void {\n const serviceCtx = ServiceContext.get();\n const reporter = TelemetryReporter.initialize({\n workspaceId: serviceCtx.workspaceId,\n client: serviceCtx.client,\n appId: process.env.DATABRICKS_CLIENT_ID || \"\",\n appkitVersion: productVersion,\n });\n reporter.start();\n reporter.sendStartup().catch(() => {});\n }\n\n /**\n * Injects framework-owned default plugins the app author shouldn't have to\n * register by hand. Runs before {@link filterDevOnlyPlugins}, so a default\n * that is itself `devOnly` (like `ui-variants`) is present in dev and stripped\n * in prod through the exact same guard as any user plugin — never alive in a\n * deployed app.\n *\n * Currently injects the dev-only `ui-variants` recorder that backs the\n * `<Variants>` UI picker, so neither the developer nor their coding agent has\n * to remember to add it. If the app already registered it explicitly, that\n * entry wins and no duplicate is added.\n */\n private static withDefaultPlugins<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(plugins: T): T {\n const list = (plugins ?? []) as PluginData<\n PluginConstructor,\n unknown,\n string\n >[];\n\n const defaults: PluginData<PluginConstructor, unknown, string>[] = [\n uiVariants(),\n ];\n\n const missing = defaults.filter(\n (def) => !list.some((p) => p?.name === def.name),\n );\n\n return [...list, ...missing] as T;\n }\n\n /**\n * Drops plugins whose manifest declares `devOnly: true` unless\n * `NODE_ENV === \"development\"`. Runs before resource collection so a skipped\n * plugin is never constructed, never injects routes, and has its resource\n * requirements ignored — the framework, not the app author, enforces that\n * dev-only tooling can never run in a deployed app.\n *\n * This is the primary guard; plugins that expose a mutating dev endpoint\n * should still keep an in-handler `NODE_ENV` check as fail-safe defense\n * against being mounted through a path that bypasses this filter.\n */\n private static filterDevOnlyPlugins<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(plugins: T): T {\n if (!plugins || process.env.NODE_ENV === \"development\") return plugins;\n\n return plugins.filter((pluginData) => {\n const isDevOnly = pluginData?.plugin?.manifest?.devOnly === true;\n if (isDevOnly) {\n logger.debug(\n \"Skipping dev-only plugin %s (NODE_ENV=%s)\",\n pluginData?.name ?? \"unknown\",\n process.env.NODE_ENV ?? \"<unset>\",\n );\n }\n return !isDevOnly;\n }) as T;\n }\n\n private static preparePlugins(\n plugins: PluginData<PluginConstructor, unknown, string>[],\n ) {\n const result: InputPluginMap = {};\n for (const currentPlugin of plugins) {\n result[currentPlugin.name] = {\n plugin: currentPlugin.plugin,\n config: currentPlugin.config as Record<string, unknown>,\n };\n }\n return result;\n }\n}\n\n/**\n * Bootstraps AppKit with the provided configuration.\n *\n * Initializes telemetry, cache, and service context, then registers plugins\n * in phase order (core, normal, deferred) and awaits their setup.\n * If a `onPluginsReady` callback is provided it runs after plugin setup but\n * before the server starts, giving you access to the full appkit handle\n * for registering custom routes or performing async setup.\n * The returned object maps each plugin name to its `exports()` API,\n * with an `asUser(req)` method for user-scoped execution.\n *\n * @returns A `PluginMap` keyed by plugin name with typed exports\n *\n * @example Minimal server\n * ```ts\n * import { createApp, server } from \"@databricks/appkit\";\n *\n * await createApp({\n * plugins: [server()],\n * });\n * ```\n *\n * @example Server with custom routes via onPluginsReady\n * ```ts\n * import { createApp, server, analytics } from \"@databricks/appkit\";\n *\n * await createApp({\n * plugins: [server(), analytics({})],\n * onPluginsReady(appkit) {\n * appkit.server.extend((app) => {\n * app.get(\"/custom\", (_req, res) => res.json({ ok: true }));\n * });\n * },\n * });\n * ```\n */\nexport async function createApp<\n T extends PluginData<PluginConstructor, unknown, string>[],\n>(\n config: {\n plugins?: T;\n telemetry?: TelemetryConfig;\n cache?: CacheConfig;\n client?: WorkspaceClient;\n /** Runs after plugin setup but **before** the server starts. */\n onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;\n disableInternalTelemetry?: boolean;\n } = {},\n): Promise<PluginMap<T>> {\n return AppKit._createApp(config);\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -12,14 +12,19 @@ import { CacheManager } from "./cache/index.js";
12
12
  import { JobsConnectorConfig } from "./connectors/jobs/types.js";
13
13
  import "./connectors/jobs/index.js";
14
14
  import { LakebasePoolManager, createLakebasePoolManager } from "./connectors/lakebase/pool-manager.js";
15
+ import { CallerContext, CallerPrincipal, ExecutionContext } from "./context/caller-context.js";
15
16
  import { LakebasePool } from "./connectors/lakebase/routing-pool.js";
16
17
  import { DatabaseCredential, GenerateDatabaseCredentialRequest, LakebasePoolConfig, RequestedClaims, RequestedClaimsPermissionSet, RequestedResource, createLakebasePool, generateDatabaseCredential, getLakebaseOrmConfig, getLakebasePgConfig, getUsernameWithApiLookup, getWorkspaceClient } from "./connectors/lakebase/index.js";
17
- import { getExecutionContext } from "./context/execution-context.js";
18
+ import { getCurrentActorId, getCurrentPrincipalKey, getExecutionContext } from "./context/execution-context.js";
18
19
  import "./context/index.js";
19
20
  import { ITelemetry, TelemetryConfig } from "./telemetry/types.js";
20
21
  import { Counter, Histogram, SeverityNumber, Span, SpanStatusCode } from "./telemetry/index.js";
21
22
  import { createApp } from "./core/appkit.js";
22
23
  import "./core/index.js";
24
+ import { ResourcePermission, ResourceType } from "./registry/types.generated.js";
25
+ import { ConfigSchema, PluginManifest, ResourceEntry, ResourceRequirement, ValidationResult } from "./registry/types.js";
26
+ import { getWarehouseId } from "./resources/warehouse.js";
27
+ import "./resources/index.js";
23
28
  import { AppKitError } from "./errors/base.js";
24
29
  import { AuthenticationError } from "./errors/authentication.js";
25
30
  import { ConfigurationError } from "./errors/configuration.js";
@@ -36,8 +41,6 @@ import { toPlugin } from "./plugin/to-plugin.js";
36
41
  import "./plugin/index.js";
37
42
  import { FileAction, FilePolicy, FilePolicyUser, FileResource, PolicyDeniedError, READ_ACTIONS, WRITE_ACTIONS } from "./plugins/files/policy.js";
38
43
  import { analytics } from "./plugins/analytics/analytics.js";
39
- import { ResourcePermission, ResourceType } from "./registry/types.generated.js";
40
- import { ConfigSchema, PluginManifest, ResourceEntry, ResourceRequirement, ValidationResult } from "./registry/types.js";
41
44
  import { defineManifest, getPluginManifest, getResourceRequirements } from "./registry/manifest-loader.js";
42
45
  import { ResourceRegistry } from "./registry/resource-registry.js";
43
46
  import "./registry/index.js";
@@ -54,4 +57,4 @@ import "./plugins/ga-exports.generated.js";
54
57
  import { extractServingEndpoints, findServerFile } from "./type-generator/serving/server-file-extractor.js";
55
58
  import { appKitServingTypesPlugin } from "./type-generator/serving/vite-plugin.js";
56
59
  import { appKitTypesPlugin } from "./type-generator/vite-plugin.js";
57
- export { ApiError, AppKitError, AuthenticationError, type BasePluginConfig, type CacheConfig, CacheManager, type ConfigSchema, ConfigurationError, ConnectionError, type Counter, type DatabaseCredential, type DatabaseRegistry, DatabaseValidationError, type DatabaseValidationIssue, type EndpointConfig, ExecutionError, type ExecutionResult, type FileAction, type FilePolicy, type FilePolicyUser, type FileResource, type GenerateDatabaseCredentialRequest, type Histogram, type IAppRouter, type IJobsConfig, type ITelemetry, InitializationError, type JobAPI, type JobConfig, type JobsConnectorConfig, type JobsExport, type LakebasePool, type LakebasePoolConfig, type LakebasePoolManager, Plugin, type PluginData, type PluginManifest, PolicyDeniedError, READ_ACTIONS, type RequestedClaims, RequestedClaimsPermissionSet, type RequestedResource, type ResourceEntry, type ResourceFieldEntry, type ResourcePermission, ResourceRegistry, type ResourceRequirement, ResourceType, ServerError, type ServingEndpointEntry, type ServingEndpointRegistry, type ServingFactory, SeverityNumber, type Span, SpanStatusCode, type StreamExecutionSettings, type TelemetryConfig, type ToPlugin, TunnelError, ValidationError, type ValidationResult, WRITE_ACTIONS, type WorkspaceClient, type WorkspaceClientOptions, analytics, appKitServingTypesPlugin, appKitTypesPlugin, createApp, createLakebasePool, createLakebasePoolManager, createWorkspaceClient, defineManifest, extractServingEndpoints, files, findServerFile, generateDatabaseCredential, genie, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUsernameWithApiLookup, getWorkspaceClient, isSQLTypeMarker, jobs, lakebase, server, serving, sql, toPlugin };
60
+ export { ApiError, AppKitError, AuthenticationError, type BasePluginConfig, type CacheConfig, CacheManager, type CallerContext, type CallerPrincipal, type ConfigSchema, ConfigurationError, ConnectionError, type Counter, type DatabaseCredential, type DatabaseRegistry, DatabaseValidationError, type DatabaseValidationIssue, type EndpointConfig, type ExecutionContext, ExecutionError, type ExecutionResult, type FileAction, type FilePolicy, type FilePolicyUser, type FileResource, type GenerateDatabaseCredentialRequest, type Histogram, type IAppRouter, type IJobsConfig, type ITelemetry, InitializationError, type JobAPI, type JobConfig, type JobsConnectorConfig, type JobsExport, type LakebasePool, type LakebasePoolConfig, type LakebasePoolManager, Plugin, type PluginData, type PluginManifest, PolicyDeniedError, READ_ACTIONS, type RequestedClaims, RequestedClaimsPermissionSet, type RequestedResource, type ResourceEntry, type ResourceFieldEntry, type ResourcePermission, ResourceRegistry, type ResourceRequirement, ResourceType, ServerError, type ServingEndpointEntry, type ServingEndpointRegistry, type ServingFactory, SeverityNumber, type Span, SpanStatusCode, type StreamExecutionSettings, type TelemetryConfig, type ToPlugin, TunnelError, ValidationError, type ValidationResult, WRITE_ACTIONS, type WorkspaceClient, type WorkspaceClientOptions, analytics, appKitServingTypesPlugin, appKitTypesPlugin, createApp, createLakebasePool, createLakebasePoolManager, createWorkspaceClient, defineManifest, extractServingEndpoints, files, findServerFile, generateDatabaseCredential, genie, getCurrentActorId, getCurrentPrincipalKey, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUsernameWithApiLookup, getWarehouseId, getWorkspaceClient, isSQLTypeMarker, jobs, lakebase, server, serving, sql, toPlugin };
package/dist/index.js CHANGED
@@ -12,8 +12,9 @@ import { ServerError } from "./errors/server.js";
12
12
  import { TunnelError } from "./errors/tunnel.js";
13
13
  import { ValidationError } from "./errors/validation.js";
14
14
  import "./errors/index.js";
15
+ import { getWarehouseId } from "./resources/warehouse.js";
15
16
  import { createLakebasePoolManager } from "./connectors/lakebase/pool-manager.js";
16
- import { getExecutionContext } from "./context/execution-context.js";
17
+ import { getCurrentActorId, getCurrentPrincipalKey, getExecutionContext } from "./context/execution-context.js";
17
18
  import { RequestedClaimsPermissionSet, createLakebasePool, generateDatabaseCredential, getLakebaseOrmConfig, getLakebasePgConfig, getUsernameWithApiLookup, getWorkspaceClient } from "./connectors/lakebase/index.js";
18
19
  import { SeverityNumber, SpanStatusCode } from "./telemetry/index.js";
19
20
  import { CacheManager } from "./cache/index.js";
@@ -27,6 +28,7 @@ import { ResourceRegistry } from "./registry/resource-registry.js";
27
28
  import "./registry/index.js";
28
29
  import { createApp } from "./core/appkit.js";
29
30
  import "./core/index.js";
31
+ import "./resources/index.js";
30
32
  import { PolicyDeniedError, READ_ACTIONS, WRITE_ACTIONS } from "./plugins/files/policy.js";
31
33
  import { analytics } from "./plugins/analytics/analytics.js";
32
34
  import { files } from "./plugins/files/plugin.js";
@@ -40,4 +42,4 @@ import { server } from "./plugins/server/index.js";
40
42
  import { serving } from "./plugins/serving/serving.js";
41
43
  import "./plugins/ga-exports.generated.js";
42
44
 
43
- export { ApiError, AppKitError, AuthenticationError, CacheManager, ConfigurationError, ConnectionError, DatabaseValidationError, ExecutionError, InitializationError, Plugin, PolicyDeniedError, READ_ACTIONS, RequestedClaimsPermissionSet, ResourceRegistry, ResourceType, ServerError, SeverityNumber, SpanStatusCode, TunnelError, ValidationError, WRITE_ACTIONS, analytics, appKitServingTypesPlugin, appKitTypesPlugin, createApp, createLakebasePool, createLakebasePoolManager, createWorkspaceClient, defineManifest, extractServingEndpoints, files, findServerFile, generateDatabaseCredential, genie, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUsernameWithApiLookup, getWorkspaceClient, isSQLTypeMarker, jobs, lakebase, server, serving, sql, toPlugin };
45
+ export { ApiError, AppKitError, AuthenticationError, CacheManager, ConfigurationError, ConnectionError, DatabaseValidationError, ExecutionError, InitializationError, Plugin, PolicyDeniedError, READ_ACTIONS, RequestedClaimsPermissionSet, ResourceRegistry, ResourceType, ServerError, SeverityNumber, SpanStatusCode, TunnelError, ValidationError, WRITE_ACTIONS, analytics, appKitServingTypesPlugin, appKitTypesPlugin, createApp, createLakebasePool, createLakebasePoolManager, createWorkspaceClient, defineManifest, extractServingEndpoints, files, findServerFile, generateDatabaseCredential, genie, getCurrentActorId, getCurrentPrincipalKey, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUsernameWithApiLookup, getWarehouseId, getWorkspaceClient, isSQLTypeMarker, jobs, lakebase, server, serving, sql, toPlugin };
@@ -215,7 +215,7 @@ declare abstract class Plugin<TConfig extends BasePluginConfig = BasePluginConfi
215
215
  *
216
216
  * Returns the `x-forwarded-user` header when present. In development mode
217
217
  * (`NODE_ENV=development`) falls back to the current context user ID so
218
- * that callers outside an active `runInUserContext` scope still get a
218
+ * that callers outside an active `runInCallerContext` scope still get a
219
219
  * consistent value.
220
220
  *
221
221
  * @throws AuthenticationError in production when no user header is present.
@@ -237,7 +237,7 @@ declare abstract class Plugin<TConfig extends BasePluginConfig = BasePluginConfi
237
237
  * in the result of `exports()` — runs inside `wrapCall`.
238
238
  *
239
239
  * `wrapCall` decides the per-call scope. Two strategies are used today:
240
- * - real OBO: fn => (...args) => runInUserContext(userContext, () => fn(...args))
240
+ * - real OBO: fn => (...args) => runInCallerContext(userContext, () => fn(...args))
241
241
  * - dev fallback: fn => (...args) => otelContext.with(DEV_OBO_FALLBACK_KEY=true, () => fn(...args))
242
242
  *
243
243
  * `exports` is intercepted because methods captured in the returned
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","names":[],"sources":["../../src/plugin/plugin.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBA0NsB,MAAA,iBACJ,gBAAA,GAAmB,gBAAA,aACxB,UAAA;EAAA,UA4BW,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;EA0SO;EAAA,QAvSnB,mBAAA;EAwSG;EAAA,QArSH,oBAAA;EAsSN;;;;;;EAAA,OA9RK,KAAA,EAAO,WAAA;EA+W0B;;;EA1WxC,IAAA;cAEsB,MAAA,EAAQ,OAAA;EAAA,QAoBtB,gBAAA;EAuVG;;;;;;;;EAlUX,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;EAgbyB;;;;;;;;;;;;;;;;;;;;;;;;EApZzB,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,UAoCQ,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"}
1
+ {"version":3,"file":"plugin.d.ts","names":[],"sources":["../../src/plugin/plugin.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBA8NsB,MAAA,iBACJ,gBAAA,GAAmB,gBAAA,aACxB,UAAA;EAAA,UA4BW,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;EA0SO;EAAA,QAvSnB,mBAAA;EAwSG;EAAA,QArSH,oBAAA;EAsSN;;;;;;EAAA,OA9RK,KAAA,EAAO,WAAA;EA+W0B;;;EA1WxC,IAAA;cAEsB,MAAA,EAAQ,OAAA;EAAA,QAoBtB,gBAAA;EAuVG;;;;;;;;EAlUX,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;EAgbyB;;;;;;;;;;;;;;;;;;;;;;;;EApZzB,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,UAoCQ,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"}
@@ -2,9 +2,9 @@ import { camelToKebab } from "../shared/src/naming.js";
2
2
  import { AppKitError } from "../errors/base.js";
3
3
  import { AuthenticationError } from "../errors/authentication.js";
4
4
  import "../errors/index.js";
5
- import { ServiceContext } from "../context/service-context.js";
6
5
  import { createLogger } from "../logging/logger.js";
7
- import { getCurrentUserId, runInUserContext } from "../context/execution-context.js";
6
+ import { ServiceContext } from "../context/service-context.js";
7
+ import { getCurrentUserId, runInCallerContext } from "../context/execution-context.js";
8
8
  import { normalizeTelemetryOptions } from "../telemetry/config.js";
9
9
  import { TelemetryManager } from "../telemetry/telemetry-manager.js";
10
10
  import "../telemetry/index.js";
@@ -321,7 +321,7 @@ var Plugin = class {
321
321
  *
322
322
  * Returns the `x-forwarded-user` header when present. In development mode
323
323
  * (`NODE_ENV=development`) falls back to the current context user ID so
324
- * that callers outside an active `runInUserContext` scope still get a
324
+ * that callers outside an active `runInCallerContext` scope still get a
325
325
  * consistent value.
326
326
  *
327
327
  * @throws AuthenticationError in production when no user header is present.
@@ -357,15 +357,15 @@ var Plugin = class {
357
357
  if (!token) throw AuthenticationError.missingToken("user token");
358
358
  if (!userId && !isDev) throw AuthenticationError.missingUserId();
359
359
  const effectiveUserId = userId || "dev-user";
360
- const userContext = ServiceContext.createUserContext(token, effectiveUserId, void 0, userEmail ?? void 0);
361
- return this._createAsUserProxy((fn) => (...args) => runInUserContext(userContext, () => fn(...args)));
360
+ const userContext = ServiceContext.createCallerContext(token, effectiveUserId, void 0, userEmail ?? void 0);
361
+ return this._createAsUserProxy((fn) => (...args) => runInCallerContext(userContext, () => fn(...args)));
362
362
  }
363
363
  /**
364
364
  * Creates a proxy of `this` where every method call — and every function
365
365
  * in the result of `exports()` — runs inside `wrapCall`.
366
366
  *
367
367
  * `wrapCall` decides the per-call scope. Two strategies are used today:
368
- * - real OBO: fn => (...args) => runInUserContext(userContext, () => fn(...args))
368
+ * - real OBO: fn => (...args) => runInCallerContext(userContext, () => fn(...args))
369
369
  * - dev fallback: fn => (...args) => otelContext.with(DEV_OBO_FALLBACK_KEY=true, () => fn(...args))
370
370
  *
371
371
  * `exports` is intercepted because methods captured in the returned