@databricks/appkit 0.82.0 → 0.84.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 (225) hide show
  1. package/CLAUDE.md +21 -1
  2. package/dist/agents/databricks.d.ts +15 -3
  3. package/dist/agents/databricks.d.ts.map +1 -1
  4. package/dist/agents/databricks.js +25 -8
  5. package/dist/agents/databricks.js.map +1 -1
  6. package/dist/appkit/package.js +1 -1
  7. package/dist/beta.d.ts +2 -2
  8. package/dist/cache/index.d.ts.map +1 -1
  9. package/dist/cache/index.js +6 -1
  10. package/dist/cache/index.js.map +1 -1
  11. package/dist/cli/commands/agent/eval.js +1 -1
  12. package/dist/cli/commands/generate-types.js +1 -1
  13. package/dist/cli/commands/plugin/sync/sync.js +28 -15
  14. package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
  15. package/dist/cli/commands/registry/add.js +3 -11
  16. package/dist/cli/commands/registry/add.js.map +1 -1
  17. package/dist/cli/commands/registry/config-writer.js +1 -1
  18. package/dist/connectors/lakebase/index.js +1 -1
  19. package/dist/connectors/lakebase/routing-pool.d.ts +1 -1
  20. package/dist/connectors/lakebase/routing-pool.d.ts.map +1 -1
  21. package/dist/connectors/lakebase/routing-pool.js +6 -12
  22. package/dist/connectors/lakebase/routing-pool.js.map +1 -1
  23. package/dist/context/caller-context.d.ts +25 -0
  24. package/dist/context/caller-context.d.ts.map +1 -0
  25. package/dist/context/caller-context.js +21 -0
  26. package/dist/context/caller-context.js.map +1 -0
  27. package/dist/context/deprecation.js +14 -0
  28. package/dist/context/deprecation.js.map +1 -0
  29. package/dist/context/execution-context.d.ts +40 -4
  30. package/dist/context/execution-context.d.ts.map +1 -1
  31. package/dist/context/execution-context.js +87 -33
  32. package/dist/context/execution-context.js.map +1 -1
  33. package/dist/context/index.d.ts +3 -2
  34. package/dist/context/index.js +3 -1
  35. package/dist/context/request-scope.d.ts +2 -0
  36. package/dist/context/request-scope.js +39 -0
  37. package/dist/context/request-scope.js.map +1 -0
  38. package/dist/context/resource-capabilities.js +59 -0
  39. package/dist/context/resource-capabilities.js.map +1 -0
  40. package/dist/context/scoped-api.js +104 -0
  41. package/dist/context/scoped-api.js.map +1 -0
  42. package/dist/context/service-context.d.ts +18 -12
  43. package/dist/context/service-context.d.ts.map +1 -1
  44. package/dist/context/service-context.js +45 -55
  45. package/dist/context/service-context.js.map +1 -1
  46. package/dist/context/user-context.d.ts +16 -22
  47. package/dist/context/user-context.d.ts.map +1 -1
  48. package/dist/context/user-context.js +57 -3
  49. package/dist/context/user-context.js.map +1 -1
  50. package/dist/core/agent/load-agents.d.ts.map +1 -1
  51. package/dist/core/agent/load-agents.js +5 -2
  52. package/dist/core/agent/load-agents.js.map +1 -1
  53. package/dist/core/agent/run-agent.d.ts +19 -6
  54. package/dist/core/agent/run-agent.d.ts.map +1 -1
  55. package/dist/core/agent/run-agent.js +53 -17
  56. package/dist/core/agent/run-agent.js.map +1 -1
  57. package/dist/core/agent/types.d.ts +18 -1
  58. package/dist/core/agent/types.d.ts.map +1 -1
  59. package/dist/core/agent/types.js.map +1 -1
  60. package/dist/core/appkit.d.ts +4 -3
  61. package/dist/core/appkit.d.ts.map +1 -1
  62. package/dist/core/appkit.js +34 -11
  63. package/dist/core/appkit.js.map +1 -1
  64. package/dist/core/plugin-context.d.ts +14 -15
  65. package/dist/core/plugin-context.d.ts.map +1 -1
  66. package/dist/core/plugin-context.js +24 -14
  67. package/dist/core/plugin-context.js.map +1 -1
  68. package/dist/errors/base.d.ts +2 -2
  69. package/dist/errors/base.js +2 -2
  70. package/dist/errors/base.js.map +1 -1
  71. package/dist/errors/identity-expired.d.ts +14 -0
  72. package/dist/errors/identity-expired.d.ts.map +1 -0
  73. package/dist/errors/identity-expired.js +33 -0
  74. package/dist/errors/identity-expired.js.map +1 -0
  75. package/dist/errors/index.js +1 -0
  76. package/dist/index.d.ts +12 -6
  77. package/dist/index.js +7 -2
  78. package/dist/logging/logger.js +1 -1
  79. package/dist/plugin/execution-result.d.ts +4 -1
  80. package/dist/plugin/execution-result.d.ts.map +1 -1
  81. package/dist/plugin/interceptors/telemetry.js +6 -4
  82. package/dist/plugin/interceptors/telemetry.js.map +1 -1
  83. package/dist/plugin/plugin.d.ts +14 -22
  84. package/dist/plugin/plugin.d.ts.map +1 -1
  85. package/dist/plugin/plugin.js +42 -123
  86. package/dist/plugin/plugin.js.map +1 -1
  87. package/dist/plugins/agents/agents.d.ts +13 -1
  88. package/dist/plugins/agents/agents.d.ts.map +1 -1
  89. package/dist/plugins/agents/agents.js +65 -15
  90. package/dist/plugins/agents/agents.js.map +1 -1
  91. package/dist/plugins/agents/auth-mode.js +42 -0
  92. package/dist/plugins/agents/auth-mode.js.map +1 -0
  93. package/dist/plugins/agents/index.d.ts +1 -1
  94. package/dist/plugins/agents/mlflow.js +11 -3
  95. package/dist/plugins/agents/mlflow.js.map +1 -1
  96. package/dist/plugins/agents/tool-dispatch.js +9 -1
  97. package/dist/plugins/agents/tool-dispatch.js.map +1 -1
  98. package/dist/plugins/ai-search/ai-search.d.ts +1 -1
  99. package/dist/plugins/ai-search/ai-search.d.ts.map +1 -1
  100. package/dist/plugins/ai-search/ai-search.js +4 -4
  101. package/dist/plugins/ai-search/ai-search.js.map +1 -1
  102. package/dist/plugins/analytics/analytics.d.ts +3 -3
  103. package/dist/plugins/analytics/analytics.d.ts.map +1 -1
  104. package/dist/plugins/analytics/analytics.js +9 -7
  105. package/dist/plugins/analytics/analytics.js.map +1 -1
  106. package/dist/plugins/database/crud/contract.js +2 -2
  107. package/dist/plugins/database/crud/contract.js.map +1 -1
  108. package/dist/plugins/database/database.d.ts +1 -1
  109. package/dist/plugins/files/plugin.d.ts +21 -15
  110. package/dist/plugins/files/plugin.d.ts.map +1 -1
  111. package/dist/plugins/files/plugin.js +50 -29
  112. package/dist/plugins/files/plugin.js.map +1 -1
  113. package/dist/plugins/genie/genie.d.ts +10 -1
  114. package/dist/plugins/genie/genie.d.ts.map +1 -1
  115. package/dist/plugins/genie/genie.js +12 -3
  116. package/dist/plugins/genie/genie.js.map +1 -1
  117. package/dist/plugins/genie/manifest.js +1 -0
  118. package/dist/plugins/jobs/plugin.d.ts +1 -1
  119. package/dist/plugins/jobs/plugin.js +2 -2
  120. package/dist/plugins/jobs/plugin.js.map +1 -1
  121. package/dist/plugins/lakebase/lakebase.d.ts +11 -18
  122. package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
  123. package/dist/plugins/lakebase/lakebase.js +16 -21
  124. package/dist/plugins/lakebase/lakebase.js.map +1 -1
  125. package/dist/plugins/server/client-config-sanitizer.js +1 -4
  126. package/dist/plugins/server/client-config-sanitizer.js.map +1 -1
  127. package/dist/plugins/server/dev-obo-middleware.js +60 -0
  128. package/dist/plugins/server/dev-obo-middleware.js.map +1 -0
  129. package/dist/plugins/server/index.d.ts +1 -1
  130. package/dist/plugins/server/index.d.ts.map +1 -1
  131. package/dist/plugins/server/index.js +3 -0
  132. package/dist/plugins/server/index.js.map +1 -1
  133. package/dist/plugins/serving/manifest.js +1 -0
  134. package/dist/plugins/serving/serving.d.ts +1 -1
  135. package/dist/plugins/serving/serving.js +6 -6
  136. package/dist/plugins/serving/serving.js.map +1 -1
  137. package/dist/resources/index.d.ts +1 -0
  138. package/dist/resources/index.js +3 -0
  139. package/dist/resources/warehouse.d.ts +16 -0
  140. package/dist/resources/warehouse.d.ts.map +1 -0
  141. package/dist/resources/warehouse.js +93 -0
  142. package/dist/resources/warehouse.js.map +1 -0
  143. package/dist/schemas/manifest.d.ts +44 -1
  144. package/dist/schemas/manifest.d.ts.map +1 -1
  145. package/dist/schemas/manifest.js +122 -3
  146. package/dist/schemas/manifest.js.map +1 -1
  147. package/dist/shared/src/dev-obo.js +86 -0
  148. package/dist/shared/src/dev-obo.js.map +1 -0
  149. package/dist/shared/src/index.d.ts +1 -1
  150. package/dist/shared/src/plugin.d.ts +12 -1
  151. package/dist/shared/src/plugin.d.ts.map +1 -1
  152. package/dist/shared/src/schemas/manifest.d.ts +17 -0
  153. package/dist/shared/src/schemas/manifest.d.ts.map +1 -1
  154. package/dist/shared/src/schemas/manifest.js +53 -3
  155. package/dist/shared/src/schemas/manifest.js.map +1 -1
  156. package/dist/stream/stream-manager.d.ts.map +1 -1
  157. package/dist/stream/stream-manager.js +5 -2
  158. package/dist/stream/stream-manager.js.map +1 -1
  159. package/dist/telemetry/execution-span-processor.js +21 -0
  160. package/dist/telemetry/execution-span-processor.js.map +1 -0
  161. package/dist/telemetry/telemetry-manager.js +2 -1
  162. package/dist/telemetry/telemetry-manager.js.map +1 -1
  163. package/dist/testing/create-test-app.d.ts +2 -2
  164. package/dist/testing/create-test-app.js +2 -2
  165. package/dist/testing/create-test-app.js.map +1 -1
  166. package/dist/testing/fixtures.d.ts +3 -2
  167. package/dist/testing/fixtures.d.ts.map +1 -1
  168. package/dist/testing/fixtures.js +21 -14
  169. package/dist/testing/fixtures.js.map +1 -1
  170. package/dist/testing/reset-singletons.js +1 -1
  171. package/dist/testing/test-plugin-context.d.ts +3 -12
  172. package/dist/testing/test-plugin-context.d.ts.map +1 -1
  173. package/dist/testing/test-plugin-context.js +24 -16
  174. package/dist/testing/test-plugin-context.js.map +1 -1
  175. package/dist/utils/is-plain-object.js +11 -0
  176. package/dist/utils/is-plain-object.js.map +1 -0
  177. package/docs/api/appkit/Class.AppKitError.md +2 -1
  178. package/docs/api/appkit/Class.AuthenticationError.md +1 -1
  179. package/docs/api/appkit/Class.ConfigurationError.md +1 -1
  180. package/docs/api/appkit/Class.ConnectionError.md +1 -1
  181. package/docs/api/appkit/Class.DatabaseValidationError.md +1 -1
  182. package/docs/api/appkit/Class.ExecutionError.md +1 -1
  183. package/docs/api/appkit/Class.IdentityExpiredError.md +190 -0
  184. package/docs/api/appkit/Class.InitializationError.md +1 -1
  185. package/docs/api/appkit/Class.Plugin.md +9 -13
  186. package/docs/api/appkit/Class.ServerError.md +1 -1
  187. package/docs/api/appkit/Class.ServiceContext.md +169 -0
  188. package/docs/api/appkit/Class.TunnelError.md +1 -1
  189. package/docs/api/appkit/Class.ValidationError.md +1 -1
  190. package/docs/api/appkit/Function.createApp.md +12 -12
  191. package/docs/api/appkit/Function.getCallerContext.md +12 -0
  192. package/docs/api/appkit/Function.getCurrentActorId.md +12 -0
  193. package/docs/api/appkit/Function.getCurrentPrincipalKey.md +12 -0
  194. package/docs/api/appkit/Function.getCurrentUserId.md +14 -0
  195. package/docs/api/appkit/Function.getExecutionContext.md +5 -3
  196. package/docs/api/appkit/Function.getUserContext.md +16 -0
  197. package/docs/api/appkit/Function.getWarehouseId.md +20 -0
  198. package/docs/api/appkit/Function.isInUserContext.md +12 -0
  199. package/docs/api/appkit/Function.isUserContext.md +20 -0
  200. package/docs/api/appkit/Function.runAgent.md +1 -1
  201. package/docs/api/appkit/Function.runInCallerContext.md +27 -0
  202. package/docs/api/appkit/Function.runInUserContext.md +29 -0
  203. package/docs/api/appkit/Interface.AgentDefinition.md +11 -0
  204. package/docs/api/appkit/Interface.AgentsPluginConfig.md +11 -0
  205. package/docs/api/appkit/Interface.CallerContext.md +54 -0
  206. package/docs/api/appkit/Interface.IndexConfig.md +1 -1
  207. package/docs/api/appkit/Interface.PluginManifest.md +33 -2
  208. package/docs/api/appkit/Interface.RegisteredAgent.md +11 -0
  209. package/docs/api/appkit/Interface.RunAgentInput.md +45 -1
  210. package/docs/api/appkit/TypeAlias.AgentAuth.md +8 -0
  211. package/docs/api/appkit/TypeAlias.AppKitApi.md +35 -0
  212. package/docs/api/appkit/TypeAlias.CallerPrincipal.md +13 -0
  213. package/docs/api/appkit/TypeAlias.ExecutionContext.md +9 -0
  214. package/docs/api/appkit/TypeAlias.ExecutionResult.md +65 -0
  215. package/docs/api/appkit/TypeAlias.ScopedPluginMap.md +12 -0
  216. package/docs/api/appkit/TypeAlias.UserContext.md +109 -0
  217. package/docs/api/appkit/TypeAlias.UserScopedApp.md +39 -0
  218. package/docs/api/appkit.md +20 -0
  219. package/docs/plugins/agents.md +45 -0
  220. package/docs/plugins/analytics.md +8 -0
  221. package/docs/plugins/execution-context.md +101 -50
  222. package/docs/plugins/lakebase.md +6 -82
  223. package/llms.txt +21 -1
  224. package/package.json +1 -1
  225. package/sbom.cdx.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"execution-context.js","names":[],"sources":["../../src/context/execution-context.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\n\nimport { ConfigurationError } from \"../errors\";\nimport { ServiceContext } from \"./service-context\";\nimport {\n type ExecutionContext,\n isUserContext,\n type UserContext,\n} from \"./user-context\";\n\n/**\n * AsyncLocalStorage for execution context.\n * Used to pass user context through the call stack without explicit parameters.\n */\nconst executionContextStorage = new AsyncLocalStorage<UserContext>();\n\n/**\n * Run a function in the context of a user.\n * All calls within the function will have access to the user context.\n *\n * @param userContext - The user context to use\n * @param fn - The function to run\n * @returns The result of the function\n */\nexport function runInUserContext<T>(userContext: UserContext, fn: () => T): T {\n return executionContextStorage.run(userContext, fn);\n}\n\n/**\n * Get the current execution context.\n *\n * - If running inside a user context (via asUser), returns the user context\n * - Otherwise, returns the service context\n *\n * @throws Error if ServiceContext is not initialized\n */\nexport function getExecutionContext(): ExecutionContext {\n const userContext = executionContextStorage.getStore();\n if (userContext) {\n return userContext;\n }\n return ServiceContext.get();\n}\n\n/**\n * Get the current user ID for cache keying and telemetry.\n *\n * Returns the user ID if in user context, otherwise the service user ID.\n */\nexport function getCurrentUserId(): string {\n const ctx = getExecutionContext();\n if (isUserContext(ctx)) {\n return ctx.userId;\n }\n return ctx.serviceUserId;\n}\n\n/**\n * Get the WorkspaceClient for the current execution context.\n */\nexport function getWorkspaceClient() {\n return getExecutionContext().client;\n}\n\n/**\n * Get the warehouse ID promise.\n */\nexport function getWarehouseId(): Promise<string> {\n const ctx = getExecutionContext();\n if (!ctx.warehouseId) {\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n \"No plugin requires a SQL Warehouse. Add a sql_warehouse resource to your plugin manifest, or set DATABRICKS_WAREHOUSE_ID\",\n );\n }\n return ctx.warehouseId;\n}\n\n/**\n * Get the workspace ID promise.\n */\nexport function getWorkspaceId(): Promise<string> {\n return getExecutionContext().workspaceId;\n}\n\n/**\n * Check if currently running in a user context.\n */\nexport function isInUserContext(): boolean {\n const ctx = executionContextStorage.getStore();\n return ctx !== undefined;\n}\n\n/**\n * Get the user context if one is active, otherwise `undefined`.\n * Unlike `getExecutionContext()`, this does not require `ServiceContext`\n * to be initialized and never throws.\n */\nexport function getUserContext(): UserContext | undefined {\n return executionContextStorage.getStore();\n}\n"],"mappings":";;;;;;;;;;;AAcA,MAAM,0BAA0B,IAAI,mBAAgC;;;;;;;;;AAUpE,SAAgB,iBAAoB,aAA0B,IAAgB;AAC5E,QAAO,wBAAwB,IAAI,aAAa,GAAG;;;;;;;;;;AAWrD,SAAgB,sBAAwC;CACtD,MAAM,cAAc,wBAAwB,UAAU;AACtD,KAAI,YACF,QAAO;AAET,QAAO,eAAe,KAAK;;;;;;;AAQ7B,SAAgB,mBAA2B;CACzC,MAAM,MAAM,qBAAqB;AACjC,KAAI,cAAc,IAAI,CACpB,QAAO,IAAI;AAEb,QAAO,IAAI;;;;;AAMb,SAAgB,qBAAqB;AACnC,QAAO,qBAAqB,CAAC;;;;;AAM/B,SAAgB,iBAAkC;CAChD,MAAM,MAAM,qBAAqB;AACjC,KAAI,CAAC,IAAI,YACP,OAAM,mBAAmB,iBACvB,gBACA,2HACD;AAEH,QAAO,IAAI;;;;;AAMb,SAAgB,iBAAkC;AAChD,QAAO,qBAAqB,CAAC;;;;;AAM/B,SAAgB,kBAA2B;AAEzC,QADY,wBAAwB,UAAU,KAC/B;;;;;;;AAQjB,SAAgB,iBAA0C;AACxD,QAAO,wBAAwB,UAAU"}
1
+ {"version":3,"file":"execution-context.js","names":[],"sources":["../../src/context/execution-context.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\n\nimport {\n IdentityExpiredError,\n isUnauthorized,\n} from \"../errors/identity-expired\";\nimport {\n captureWarehouseId,\n getWarehouseId as getResourceWarehouseId,\n runWithResourceBindings,\n type WarehouseBinding,\n} from \"../resources/warehouse\";\nimport { type CallerContext, snapshotCallerContext } from \"./caller-context\";\nimport { warnContextDeprecation } from \"./deprecation\";\nimport { ServiceContext, type ServiceContextState } from \"./service-context\";\nimport {\n legacyUserContext,\n toCallerContext,\n type UserContext,\n} from \"./user-context\";\n\ninterface CallerScope {\n readonly caller: CallerContext;\n readonly legacyExecution: boolean;\n}\n\nconst executionContextStorage = new AsyncLocalStorage<CallerScope>();\n\nfunction runInCallerScope<T>(\n callerContext: CallerContext,\n fn: () => T,\n legacyResources?: WarehouseBinding,\n legacyExecution = false,\n): T {\n const scope = Object.freeze({\n caller: snapshotCallerContext(callerContext),\n // Deprecated entry points cannot disable guards in an existing new scope.\n legacyExecution:\n legacyExecution &&\n (executionContextStorage.getStore()?.legacyExecution ?? true),\n });\n return runWithResourceBindings(legacyResources, () =>\n executionContextStorage.run(scope, () => {\n try {\n const result = fn();\n if (result instanceof Promise) {\n return result.catch((error) => {\n throw normalizeIdentityError(error);\n }) as T;\n }\n return result;\n } catch (error) {\n throw normalizeIdentityError(error);\n }\n }),\n );\n}\n\n/** Convert downstream 401s before recording or surfacing caller failures. */\nexport function normalizeIdentityError(error: unknown): unknown {\n const caller = getCallerContext();\n if (\n !caller ||\n error instanceof IdentityExpiredError ||\n !isUnauthorized(error)\n )\n return error;\n return new IdentityExpiredError(caller.tokenFingerprint);\n}\n\n/**\n * Run a function with an immutable snapshot of the caller context.\n * Nested and concurrent scopes keep their own identities.\n *\n * @param callerContext - The caller context to use\n * @param fn - The function to run\n * @returns The result of the function\n */\nexport function runInCallerContext<T>(\n callerContext: CallerContext,\n fn: () => T,\n): T {\n return runInCallerScope(callerContext, fn);\n}\n\n/** @internal Preserve legacy resource behavior without weakening an enclosing scope. */\nexport function runInLegacyCallerContext<T>(\n caller: CallerContext,\n fn: () => T,\n): T {\n return runInCallerScope(caller, fn, undefined, true);\n}\n\n/** @internal Legacy user entry points retain their established resource routing. */\nexport function isLegacyCallerScope(): boolean {\n return executionContextStorage.getStore()?.legacyExecution === true;\n}\n\n/** @deprecated Use runInCallerContext. */\nexport function runInUserContext<T>(\n userContext: UserContext | (CallerContext & Pick<UserContext, \"warehouseId\">),\n fn: () => T,\n): T {\n warnContextDeprecation(\"runInUserContext\", \"runInCallerContext\");\n if (!(\"principal\" in userContext) || \"warehouseId\" in userContext) {\n return runInCallerScope(\n toCallerContext(userContext),\n fn,\n Object.freeze({ warehouseId: userContext.warehouseId }),\n true,\n );\n }\n return runInLegacyCallerContext(toCallerContext(userContext), fn);\n}\n\n/**\n * Get the current execution context.\n *\n * - If running inside a caller context (via asUser), returns the caller context\n * - Otherwise, returns the service context\n *\n * @throws Error if ServiceContext is not initialized\n */\nexport function getExecutionContext():\n | ServiceContextState\n | (CallerContext & UserContext) {\n const callerContext = getCallerContext();\n if (callerContext) {\n return legacyUserContext(callerContext, captureWarehouseId());\n }\n return ServiceContext.get();\n}\n\n/**\n * Get the principal key for future cache keying: `app` or `user:<id>`.\n */\nexport function getCurrentPrincipalKey(): string {\n const caller = getCallerContext();\n return caller ? `user:${caller.principal.userId}` : \"app\";\n}\n\n/** The initiating user in a caller scope; no user actor exists in service scope. */\nexport function getCurrentActorId(): string | undefined {\n return getCallerContext()?.principal.userId;\n}\n\n/** @internal Bare ID of the effective caller or service principal. */\nexport function getCurrentPrincipalId(): string {\n return getCurrentActorId() ?? ServiceContext.get().serviceUserId;\n}\n\n/**\n * @deprecated Use getCurrentPrincipalKey for new cache keys or getCurrentActorId\n * for audit. Preserves the bare user or service ID for existing callers.\n */\nexport function getCurrentUserId(): string {\n warnContextDeprecation(\n \"getCurrentUserId\",\n \"getCurrentPrincipalKey (cache) or getCurrentActorId (audit)\",\n );\n return getCurrentPrincipalId();\n}\n\n/**\n * Get the WorkspaceClient for the current execution context.\n */\nexport function getWorkspaceClient() {\n return (getCallerContext() ?? ServiceContext.get()).client;\n}\n\n/**\n * @deprecated Import getWarehouseId from @databricks/appkit instead of context.\n */\nexport function getWarehouseId(): Promise<string> {\n warnContextDeprecation(\n \"context.getWarehouseId\",\n \"getWarehouseId() from @databricks/appkit\",\n );\n return getResourceWarehouseId();\n}\n\n/**\n * Get the workspace ID promise.\n */\nexport function getWorkspaceId(): Promise<string> {\n return (getCallerContext() ?? ServiceContext.get()).workspaceId;\n}\n\n/**\n * Check if currently running in a user context.\n */\nexport function isInUserContext(): boolean {\n const ctx = executionContextStorage.getStore();\n return ctx !== undefined;\n}\n\n/**\n * @internal Run `fn` with no caller scope, so it executes as the app service\n * principal even inside an on-behalf-of-user run. Used for app-owned\n * state such as the agents thread store.\n */\nexport function runOutsideCallerScope<T>(fn: () => T): T {\n return executionContextStorage.exit(fn);\n}\n\n/**\n * Get the caller context if one is active, otherwise `undefined`.\n * Unlike `getExecutionContext()`, this does not require `ServiceContext`\n * to be initialized and never throws.\n */\nexport function getCallerContext(): CallerContext | undefined {\n return executionContextStorage.getStore()?.caller;\n}\n\n/** @deprecated Use getCallerContext and its principal field. */\nexport function getUserContext(): (CallerContext & UserContext) | undefined {\n warnContextDeprecation(\"getUserContext\", \"getCallerContext\");\n const caller = getCallerContext();\n return caller ? legacyUserContext(caller, captureWarehouseId()) : undefined;\n}\n"],"mappings":";;;;;;;;;AA0BA,MAAM,0BAA0B,IAAI,mBAAgC;AAEpE,SAAS,iBACP,eACA,IACA,iBACA,kBAAkB,OACf;CACH,MAAM,QAAQ,OAAO,OAAO;EAC1B,QAAQ,sBAAsB,cAAc;EAE5C,iBACE,oBACC,wBAAwB,UAAU,EAAE,mBAAmB;EAC3D,CAAC;AACF,QAAO,wBAAwB,uBAC7B,wBAAwB,IAAI,aAAa;AACvC,MAAI;GACF,MAAM,SAAS,IAAI;AACnB,OAAI,kBAAkB,QACpB,QAAO,OAAO,OAAO,UAAU;AAC7B,UAAM,uBAAuB,MAAM;KACnC;AAEJ,UAAO;WACA,OAAO;AACd,SAAM,uBAAuB,MAAM;;GAErC,CACH;;;AAIH,SAAgB,uBAAuB,OAAyB;CAC9D,MAAM,SAAS,kBAAkB;AACjC,KACE,CAAC,UACD,iBAAiB,wBACjB,CAAC,eAAe,MAAM,CAEtB,QAAO;AACT,QAAO,IAAI,qBAAqB,OAAO,iBAAiB;;;;;;;;;;AAW1D,SAAgB,mBACd,eACA,IACG;AACH,QAAO,iBAAiB,eAAe,GAAG;;;AAI5C,SAAgB,yBACd,QACA,IACG;AACH,QAAO,iBAAiB,QAAQ,IAAI,QAAW,KAAK;;;AAItD,SAAgB,sBAA+B;AAC7C,QAAO,wBAAwB,UAAU,EAAE,oBAAoB;;;AAIjE,SAAgB,iBACd,aACA,IACG;AACH,wBAAuB,oBAAoB,qBAAqB;AAChE,KAAI,EAAE,eAAe,gBAAgB,iBAAiB,YACpD,QAAO,iBACL,gBAAgB,YAAY,EAC5B,IACA,OAAO,OAAO,EAAE,aAAa,YAAY,aAAa,CAAC,EACvD,KACD;AAEH,QAAO,yBAAyB,gBAAgB,YAAY,EAAE,GAAG;;;;;;;;;;AAWnE,SAAgB,sBAEkB;CAChC,MAAM,gBAAgB,kBAAkB;AACxC,KAAI,cACF,QAAO,kBAAkB,eAAe,oBAAoB,CAAC;AAE/D,QAAO,eAAe,KAAK;;;;;AAM7B,SAAgB,yBAAiC;CAC/C,MAAM,SAAS,kBAAkB;AACjC,QAAO,SAAS,QAAQ,OAAO,UAAU,WAAW;;;AAItD,SAAgB,oBAAwC;AACtD,QAAO,kBAAkB,EAAE,UAAU;;;AAIvC,SAAgB,wBAAgC;AAC9C,QAAO,mBAAmB,IAAI,eAAe,KAAK,CAAC;;;;;;AAOrD,SAAgB,mBAA2B;AACzC,wBACE,oBACA,8DACD;AACD,QAAO,uBAAuB;;;;;AAMhC,SAAgB,qBAAqB;AACnC,SAAQ,kBAAkB,IAAI,eAAe,KAAK,EAAE;;;;;AAiBtD,SAAgB,iBAAkC;AAChD,SAAQ,kBAAkB,IAAI,eAAe,KAAK,EAAE;;;;;AAMtD,SAAgB,kBAA2B;AAEzC,QADY,wBAAwB,UAAU,KAC/B;;;;;;;AAQjB,SAAgB,sBAAyB,IAAgB;AACvD,QAAO,wBAAwB,KAAK,GAAG;;;;;;;AAQzC,SAAgB,mBAA8C;AAC5D,QAAO,wBAAwB,UAAU,EAAE;;;AAI7C,SAAgB,iBAA4D;AAC1E,wBAAuB,kBAAkB,mBAAmB;CAC5D,MAAM,SAAS,kBAAkB;AACjC,QAAO,SAAS,kBAAkB,QAAQ,oBAAoB,CAAC,GAAG"}
@@ -1,3 +1,4 @@
1
+ import { UserContext, isUserContext } from "./user-context.js";
1
2
  import { ServiceContext } from "./service-context.js";
2
- import { UserContext } from "./user-context.js";
3
- import { getExecutionContext } from "./execution-context.js";
3
+ import { CallerContext, CallerPrincipal, ExecutionContext } from "./caller-context.js";
4
+ import { getCallerContext, getCurrentActorId, getCurrentPrincipalKey, getCurrentUserId, getExecutionContext, getUserContext, isInUserContext, runInCallerContext, runInUserContext } from "./execution-context.js";
@@ -1,4 +1,6 @@
1
+ import { isCallerContext } from "./caller-context.js";
2
+ import { isUserContext } from "./user-context.js";
1
3
  import { ServiceContext } from "./service-context.js";
2
- import { getCurrentUserId, getExecutionContext, getWarehouseId, getWorkspaceClient, getWorkspaceId, runInUserContext } from "./execution-context.js";
4
+ import { getCallerContext, getCurrentActorId, getCurrentPrincipalId, getCurrentPrincipalKey, getCurrentUserId, getExecutionContext, getUserContext, getWorkspaceClient, getWorkspaceId, isInUserContext, runInCallerContext, runInUserContext } from "./execution-context.js";
3
5
 
4
6
  export { getWorkspaceClient };
@@ -0,0 +1,2 @@
1
+ import "./service-context.js";
2
+ import "express";
@@ -0,0 +1,39 @@
1
+ import { AuthenticationError } from "../errors/authentication.js";
2
+ import "../errors/index.js";
3
+ import { createLogger } from "../logging/logger.js";
4
+ import { ServiceContext } from "./service-context.js";
5
+ import { runInCallerContext, runInLegacyCallerContext } from "./execution-context.js";
6
+ import { hasOboResource } from "./resource-capabilities.js";
7
+ import { context, createContextKey } from "@opentelemetry/api";
8
+
9
+ //#region src/context/request-scope.ts
10
+ const logger = createLogger("execution-context");
11
+ const DEV_OBO_FALLBACK_KEY = createContextKey("appkit.devOboFallback");
12
+ function isDevOboFallback() {
13
+ return context.active().getValue(DEV_OBO_FALLBACK_KEY) === true;
14
+ }
15
+ /** Build the caller once from the trusted Apps proxy headers. */
16
+ function createRequestScope(req, resourceTypes = [], options = {}) {
17
+ const token = req.header("x-forwarded-access-token")?.trim();
18
+ const userId = req.header("x-forwarded-user")?.trim();
19
+ const userEmail = req.header("x-forwarded-email");
20
+ const isDev = process.env.NODE_ENV === "development";
21
+ if (!token && isDev) {
22
+ logger.warn("asUser() called without user token in development mode. Skipping user impersonation.");
23
+ return { run: (fn) => context.with(context.active().setValue(DEV_OBO_FALLBACK_KEY, true), fn) };
24
+ }
25
+ if (!token) {
26
+ if (hasOboResource(resourceTypes)) {
27
+ const message = "This resource is OBO-capable but no user token was forwarded. The app is likely deployed service-principal-only. Enable user authorization and forward x-forwarded-access-token.";
28
+ throw new AuthenticationError(message, { clientMessage: message });
29
+ }
30
+ throw AuthenticationError.missingToken("user token");
31
+ }
32
+ if (!userId && !isDev) throw AuthenticationError.missingUserId();
33
+ const caller = (options.createCaller ?? ServiceContext.createCallerContext)(token, userId || "dev-user", void 0, userEmail);
34
+ return { run: (fn) => options.legacy ? runInLegacyCallerContext(caller, fn) : runInCallerContext(caller, fn) };
35
+ }
36
+
37
+ //#endregion
38
+ export { createRequestScope, isDevOboFallback };
39
+ //# sourceMappingURL=request-scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-scope.js","names":["otelContext"],"sources":["../../src/context/request-scope.ts"],"sourcesContent":["import { createContextKey, context as otelContext } from \"@opentelemetry/api\";\nimport type { Request } from \"express\";\n\nimport { AuthenticationError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\nimport {\n runInCallerContext,\n runInLegacyCallerContext,\n} from \"./execution-context\";\nimport { hasOboResource } from \"./resource-capabilities\";\nimport { ServiceContext } from \"./service-context\";\n\nconst logger = createLogger(\"execution-context\");\nconst DEV_OBO_FALLBACK_KEY = createContextKey(\"appkit.devOboFallback\");\n\n/** Internal scope shared by shorthand calls and the whole run block. */\nexport interface RequestScope {\n run<T>(fn: () => T): T;\n}\n\nexport function isDevOboFallback(): boolean {\n return otelContext.active().getValue(DEV_OBO_FALLBACK_KEY) === true;\n}\n\n/** Build the caller once from the trusted Apps proxy headers. */\nexport function createRequestScope(\n req: Request,\n resourceTypes: readonly string[] = [],\n options: {\n createCaller?: typeof ServiceContext.createCallerContext;\n legacy?: boolean;\n } = {},\n): RequestScope {\n const token = req.header(\"x-forwarded-access-token\")?.trim();\n const userId = req.header(\"x-forwarded-user\")?.trim();\n const userEmail = req.header(\"x-forwarded-email\");\n const isDev = process.env.NODE_ENV === \"development\";\n\n if (!token && isDev) {\n logger.warn(\n \"asUser() called without user token in development mode. Skipping user impersonation.\",\n );\n return {\n run: (fn) =>\n otelContext.with(\n otelContext.active().setValue(DEV_OBO_FALLBACK_KEY, true),\n fn,\n ),\n };\n }\n if (!token) {\n if (hasOboResource(resourceTypes)) {\n const message =\n \"This resource is OBO-capable but no user token was forwarded. The app is likely deployed service-principal-only. Enable user authorization and forward x-forwarded-access-token.\";\n throw new AuthenticationError(message, { clientMessage: message });\n }\n throw AuthenticationError.missingToken(\"user token\");\n }\n if (!userId && !isDev) throw AuthenticationError.missingUserId();\n\n const caller = (options.createCaller ?? ServiceContext.createCallerContext)(\n token,\n userId || \"dev-user\",\n undefined,\n userEmail,\n );\n return {\n run: (fn) =>\n options.legacy\n ? runInLegacyCallerContext(caller, fn)\n : runInCallerContext(caller, fn),\n };\n}\n"],"mappings":";;;;;;;;;AAYA,MAAM,SAAS,aAAa,oBAAoB;AAChD,MAAM,uBAAuB,iBAAiB,wBAAwB;AAOtE,SAAgB,mBAA4B;AAC1C,QAAOA,QAAY,QAAQ,CAAC,SAAS,qBAAqB,KAAK;;;AAIjE,SAAgB,mBACd,KACA,gBAAmC,EAAE,EACrC,UAGI,EAAE,EACQ;CACd,MAAM,QAAQ,IAAI,OAAO,2BAA2B,EAAE,MAAM;CAC5D,MAAM,SAAS,IAAI,OAAO,mBAAmB,EAAE,MAAM;CACrD,MAAM,YAAY,IAAI,OAAO,oBAAoB;CACjD,MAAM,QAAQ,QAAQ,IAAI,aAAa;AAEvC,KAAI,CAAC,SAAS,OAAO;AACnB,SAAO,KACL,uFACD;AACD,SAAO,EACL,MAAM,OACJA,QAAY,KACVA,QAAY,QAAQ,CAAC,SAAS,sBAAsB,KAAK,EACzD,GACD,EACJ;;AAEH,KAAI,CAAC,OAAO;AACV,MAAI,eAAe,cAAc,EAAE;GACjC,MAAM,UACJ;AACF,SAAM,IAAI,oBAAoB,SAAS,EAAE,eAAe,SAAS,CAAC;;AAEpE,QAAM,oBAAoB,aAAa,aAAa;;AAEtD,KAAI,CAAC,UAAU,CAAC,MAAO,OAAM,oBAAoB,eAAe;CAEhE,MAAM,UAAU,QAAQ,gBAAgB,eAAe,qBACrD,OACA,UAAU,YACV,QACA,UACD;AACD,QAAO,EACL,MAAM,OACJ,QAAQ,SACJ,yBAAyB,QAAQ,GAAG,GACpC,mBAAmB,QAAQ,GAAG,EACrC"}
@@ -0,0 +1,59 @@
1
+ import { APP_ONLY_RESOURCE_TYPES, SCOPE_BY_TYPE } from "../shared/src/schemas/manifest.js";
2
+ import { AppKitError } from "../errors/base.js";
3
+ import { getCallerContext, isLegacyCallerScope } from "./execution-context.js";
4
+ import { scopeApi } from "./scoped-api.js";
5
+
6
+ //#region src/context/resource-capabilities.ts
7
+ var AppOnlyResourceError = class extends AppKitError {
8
+ code = "APP_ONLY_RESOURCE";
9
+ statusCode = 400;
10
+ isRetryable = false;
11
+ constructor(type, name = type) {
12
+ const message = `Resource "${name}" is app-only in this version of AppKit and does not support OBO (on-behalf-of-user) execution. It runs as the service principal. Resource type: ${type}.`;
13
+ super(message, {
14
+ clientMessage: message,
15
+ context: { resourceType: type }
16
+ });
17
+ }
18
+ };
19
+ /** Read the same required/bound resource metadata used by provisioning. */
20
+ function getPluginResources(plugin) {
21
+ const ctor = plugin.constructor;
22
+ const config = plugin.config;
23
+ const resources = ctor.manifest?.resources;
24
+ const optional = resources?.optional?.filter((resource) => Object.values(resource.fields).some((field) => field.env && process.env[field.env]?.trim())) ?? [];
25
+ const runtime = config && ctor.getResourceRequirements ? ctor.getResourceRequirements(config) : [];
26
+ return [
27
+ ...resources?.required ?? [],
28
+ ...optional,
29
+ ...runtime.filter((resource) => resource.required)
30
+ ];
31
+ }
32
+ function getPluginResourceTypes(plugin) {
33
+ return [...new Set(getPluginResources(plugin).map((resource) => resource.type))];
34
+ }
35
+ function hasOboResource(types) {
36
+ return types.some((type) => Object.hasOwn(SCOPE_BY_TYPE, type));
37
+ }
38
+ function isAppOnly(type) {
39
+ return APP_ONLY_RESOURCE_TYPES.has(type);
40
+ }
41
+ function assertResourceExecution(type) {
42
+ if (getCallerContext() && !isLegacyCallerScope() && isAppOnly(type)) throw new AppOnlyResourceError(type);
43
+ }
44
+ function assertPluginExecution(plugin, callerRequested = false) {
45
+ if (!callerRequested && (!getCallerContext() || isLegacyCallerScope())) return;
46
+ for (const resource of getPluginResources(plugin)) if (isAppOnly(resource.type)) throw new AppOnlyResourceError(resource.type, resource.alias?.trim() || resource.type);
47
+ }
48
+ /** Cached app handles still check the principal at invocation time. */
49
+ function guardPluginApi(plugin, value) {
50
+ if (!getPluginResourceTypes(plugin).some(isAppOnly)) return value;
51
+ return scopeApi(value, { run: (fn) => {
52
+ assertPluginExecution(plugin);
53
+ return fn();
54
+ } }, plugin, true);
55
+ }
56
+
57
+ //#endregion
58
+ export { assertPluginExecution, assertResourceExecution, getPluginResourceTypes, guardPluginApi, hasOboResource };
59
+ //# sourceMappingURL=resource-capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-capabilities.js","names":[],"sources":["../../src/context/resource-capabilities.ts"],"sourcesContent":["import {\n APP_ONLY_RESOURCE_TYPES,\n SCOPE_BY_TYPE,\n type BasePluginConfig,\n type PluginConstructor,\n} from \"shared\";\n\nimport { AppKitError } from \"../errors/base\";\nimport { getCallerContext, isLegacyCallerScope } from \"./execution-context\";\nimport { scopeApi } from \"./scoped-api\";\n\nclass AppOnlyResourceError extends AppKitError {\n readonly code = \"APP_ONLY_RESOURCE\";\n readonly statusCode = 400;\n readonly isRetryable = false;\n constructor(type: string, name = type) {\n const message = `Resource \"${name}\" is app-only in this version of AppKit and does not support OBO (on-behalf-of-user) execution. It runs as the service principal. Resource type: ${type}.`;\n super(message, { clientMessage: message, context: { resourceType: type } });\n }\n}\n\n/** Read the same required/bound resource metadata used by provisioning. */\nfunction getPluginResources(plugin: object) {\n const ctor = plugin.constructor as Partial<PluginConstructor>;\n const config = (plugin as { config?: BasePluginConfig }).config;\n const resources = ctor.manifest?.resources;\n const optional =\n resources?.optional?.filter((resource) =>\n Object.values(resource.fields).some(\n (field) => field.env && process.env[field.env]?.trim(),\n ),\n ) ?? [];\n const runtime =\n config && ctor.getResourceRequirements\n ? ctor.getResourceRequirements(config)\n : [];\n return [\n ...(resources?.required ?? []),\n ...optional,\n ...runtime.filter((resource) => resource.required),\n ];\n}\n\nexport function getPluginResourceTypes(plugin: object): string[] {\n return [\n ...new Set(getPluginResources(plugin).map((resource) => resource.type)),\n ];\n}\n\nexport function hasOboResource(types: readonly string[]): boolean {\n return types.some((type) => Object.hasOwn(SCOPE_BY_TYPE, type));\n}\n\nfunction isAppOnly(type: string): boolean {\n return (APP_ONLY_RESOURCE_TYPES as ReadonlySet<string>).has(type);\n}\n\nexport function assertResourceExecution(type: string): void {\n if (getCallerContext() && !isLegacyCallerScope() && isAppOnly(type))\n throw new AppOnlyResourceError(type);\n}\n\nexport function assertPluginExecution(\n plugin: object,\n callerRequested = false,\n): void {\n if (!callerRequested && (!getCallerContext() || isLegacyCallerScope()))\n return;\n for (const resource of getPluginResources(plugin)) {\n if (isAppOnly(resource.type)) {\n throw new AppOnlyResourceError(\n resource.type,\n resource.alias?.trim() || resource.type,\n );\n }\n }\n}\n\n/** Cached app handles still check the principal at invocation time. */\nexport function guardPluginApi<T>(plugin: object, value: T): T {\n if (!getPluginResourceTypes(plugin).some(isAppOnly)) return value;\n return scopeApi(\n value,\n {\n run: (fn) => {\n assertPluginExecution(plugin);\n return fn();\n },\n },\n plugin,\n true,\n );\n}\n"],"mappings":";;;;;;AAWA,IAAM,uBAAN,cAAmC,YAAY;CAC7C,AAAS,OAAO;CAChB,AAAS,aAAa;CACtB,AAAS,cAAc;CACvB,YAAY,MAAc,OAAO,MAAM;EACrC,MAAM,UAAU,aAAa,KAAK,mJAAmJ,KAAK;AAC1L,QAAM,SAAS;GAAE,eAAe;GAAS,SAAS,EAAE,cAAc,MAAM;GAAE,CAAC;;;;AAK/E,SAAS,mBAAmB,QAAgB;CAC1C,MAAM,OAAO,OAAO;CACpB,MAAM,SAAU,OAAyC;CACzD,MAAM,YAAY,KAAK,UAAU;CACjC,MAAM,WACJ,WAAW,UAAU,QAAQ,aAC3B,OAAO,OAAO,SAAS,OAAO,CAAC,MAC5B,UAAU,MAAM,OAAO,QAAQ,IAAI,MAAM,MAAM,MAAM,CACvD,CACF,IAAI,EAAE;CACT,MAAM,UACJ,UAAU,KAAK,0BACX,KAAK,wBAAwB,OAAO,GACpC,EAAE;AACR,QAAO;EACL,GAAI,WAAW,YAAY,EAAE;EAC7B,GAAG;EACH,GAAG,QAAQ,QAAQ,aAAa,SAAS,SAAS;EACnD;;AAGH,SAAgB,uBAAuB,QAA0B;AAC/D,QAAO,CACL,GAAG,IAAI,IAAI,mBAAmB,OAAO,CAAC,KAAK,aAAa,SAAS,KAAK,CAAC,CACxE;;AAGH,SAAgB,eAAe,OAAmC;AAChE,QAAO,MAAM,MAAM,SAAS,OAAO,OAAO,eAAe,KAAK,CAAC;;AAGjE,SAAS,UAAU,MAAuB;AACxC,QAAQ,wBAAgD,IAAI,KAAK;;AAGnE,SAAgB,wBAAwB,MAAoB;AAC1D,KAAI,kBAAkB,IAAI,CAAC,qBAAqB,IAAI,UAAU,KAAK,CACjE,OAAM,IAAI,qBAAqB,KAAK;;AAGxC,SAAgB,sBACd,QACA,kBAAkB,OACZ;AACN,KAAI,CAAC,oBAAoB,CAAC,kBAAkB,IAAI,qBAAqB,EACnE;AACF,MAAK,MAAM,YAAY,mBAAmB,OAAO,CAC/C,KAAI,UAAU,SAAS,KAAK,CAC1B,OAAM,IAAI,qBACR,SAAS,MACT,SAAS,OAAO,MAAM,IAAI,SAAS,KACpC;;;AAMP,SAAgB,eAAkB,QAAgB,OAAa;AAC7D,KAAI,CAAC,uBAAuB,OAAO,CAAC,KAAK,UAAU,CAAE,QAAO;AAC5D,QAAO,SACL,OACA,EACE,MAAM,OAAO;AACX,wBAAsB,OAAO;AAC7B,SAAO,IAAI;IAEd,EACD,QACA,KACD"}
@@ -0,0 +1,104 @@
1
+ import { isPlainObject } from "../utils/is-plain-object.js";
2
+ import { Readable } from "node:stream";
3
+
4
+ //#region src/context/scoped-api.ts
5
+ const EXCLUDED_FROM_PROXY = new Set([
6
+ "setup",
7
+ "shutdown",
8
+ "attachContext",
9
+ "injectRoutes",
10
+ "getEndpoints",
11
+ "getSkipBodyParsingPaths",
12
+ "abortActiveOperations",
13
+ "clientConfig",
14
+ "constructor"
15
+ ]);
16
+ const IDENTITY_METHODS = new Set(["asUser"]);
17
+ function isIdentityMethod(key) {
18
+ return IDENTITY_METHODS.has(key);
19
+ }
20
+ function isNativeStream(value) {
21
+ return typeof ReadableStream !== "undefined" && value instanceof ReadableStream || value instanceof Readable;
22
+ }
23
+ /**
24
+ * Whether a value can run code later and so must run in the caller scope:
25
+ * a function, a Promise, a non-native async iterable, or a plain object
26
+ * reaching one of those through its own properties. Getter properties count,
27
+ * and are not invoked here. Arrays and other objects are plain data, as before.
28
+ */
29
+ function needsScope(value, seen = /* @__PURE__ */ new Set()) {
30
+ if (typeof value === "function" || value instanceof Promise) return true;
31
+ if (!value || typeof value !== "object" || isNativeStream(value)) return false;
32
+ if (Symbol.asyncIterator in value) return true;
33
+ if (!isPlainObject(value) || seen.has(value)) return false;
34
+ seen.add(value);
35
+ return Reflect.ownKeys(value).some((key) => {
36
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
37
+ return descriptor?.get !== void 0 || needsScope(descriptor?.value, seen);
38
+ });
39
+ }
40
+ /** Preserve identity across callable exports, returned handles, and lazy streams. */
41
+ function scopeApi(value, scope, receiver, preserveIdentityMethods = false) {
42
+ if (typeof value === "function") return new Proxy(value, {
43
+ apply: (fn, thisArg, args) => {
44
+ const result = scope.run(() => Reflect.apply(fn, receiver ?? thisArg, args));
45
+ if (preserveIdentityMethods) return result;
46
+ return scopeApi(result, scope);
47
+ },
48
+ get: (fn, key) => !preserveIdentityMethods && isIdentityMethod(key) ? void 0 : scopeApi(Reflect.get(fn, key), scope, fn, preserveIdentityMethods)
49
+ });
50
+ if (value instanceof Promise) return value.then((result) => scopeApi(result, scope, void 0, preserveIdentityMethods));
51
+ if (isNativeStream(value)) return value;
52
+ if (value && typeof value === "object" && Symbol.asyncIterator in value) {
53
+ const iterable = value;
54
+ const wrapIterator = (iterator) => {
55
+ const finish = iterator.return?.bind(iterator);
56
+ const fail = iterator.throw?.bind(iterator);
57
+ return {
58
+ [Symbol.asyncIterator]() {
59
+ return this;
60
+ },
61
+ next: (...args) => scope.run(() => iterator.next(...args)),
62
+ ...finish && { return: (result) => scope.run(() => finish(result)) },
63
+ ...fail && { throw: (error) => scope.run(() => fail(error)) }
64
+ };
65
+ };
66
+ if ("next" in value && typeof value.next === "function") return wrapIterator(value);
67
+ return { [Symbol.asyncIterator]: () => wrapIterator(scope.run(() => iterable[Symbol.asyncIterator]())) };
68
+ }
69
+ if (isPlainObject(value)) {
70
+ if (!needsScope(value)) return value;
71
+ const result = Object.create(Object.getPrototypeOf(value));
72
+ for (const key of Reflect.ownKeys(value)) {
73
+ if (!preserveIdentityMethods && isIdentityMethod(key)) continue;
74
+ Object.defineProperty(result, key, {
75
+ enumerable: Object.getOwnPropertyDescriptor(value, key)?.enumerable,
76
+ configurable: true,
77
+ get: () => {
78
+ const member = scope.run(() => Reflect.get(value, key));
79
+ return needsScope(member) ? scopeApi(member, scope, receiver ?? value, preserveIdentityMethods) : member;
80
+ },
81
+ set: (next) => {
82
+ Reflect.set(value, key, next);
83
+ }
84
+ });
85
+ }
86
+ return result;
87
+ }
88
+ return value;
89
+ }
90
+ /** Compatibility adapter for direct Plugin.asUser callers. */
91
+ function scopePlugin(plugin, scope) {
92
+ return new Proxy(plugin, { get(target, key) {
93
+ if (isIdentityMethod(key)) return void 0;
94
+ const value = Reflect.get(target, key, target);
95
+ if (typeof key === "string" && EXCLUDED_FROM_PROXY.has(key)) return value;
96
+ if (key === "exports" && typeof value === "function") return () => scopeApi(scope.run(() => value.call(target)) ?? {}, scope, target);
97
+ if (typeof value === "function") return (...args) => scope.run(() => Reflect.apply(value, target, args));
98
+ return scopeApi(value, scope, target);
99
+ } });
100
+ }
101
+
102
+ //#endregion
103
+ export { scopeApi, scopePlugin };
104
+ //# sourceMappingURL=scoped-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scoped-api.js","names":[],"sources":["../../src/context/scoped-api.ts"],"sourcesContent":["import { Readable } from \"node:stream\";\n\nimport { isPlainObject } from \"../utils/is-plain-object\";\nimport type { RequestScope } from \"./request-scope\";\n\nconst EXCLUDED_FROM_PROXY = new Set([\n \"setup\",\n \"shutdown\",\n \"attachContext\",\n \"injectRoutes\",\n \"getEndpoints\",\n \"getSkipBodyParsingPaths\",\n \"abortActiveOperations\",\n \"clientConfig\",\n \"constructor\",\n]);\n\n// Methods that would re-scope a scoped surface to another identity. Only\n// these are hidden; other `as*` names (asCsv, asArrow) are ordinary exports.\nconst IDENTITY_METHODS: ReadonlySet<PropertyKey> = new Set([\"asUser\"]);\n\nfunction isIdentityMethod(key: PropertyKey): boolean {\n return IDENTITY_METHODS.has(key);\n}\n\nfunction isNativeStream(value: unknown): boolean {\n return (\n (typeof ReadableStream !== \"undefined\" &&\n value instanceof ReadableStream) ||\n value instanceof Readable\n );\n}\n\n/**\n * Whether a value can run code later and so must run in the caller scope:\n * a function, a Promise, a non-native async iterable, or a plain object\n * reaching one of those through its own properties. Getter properties count,\n * and are not invoked here. Arrays and other objects are plain data, as before.\n */\nfunction needsScope(value: unknown, seen = new Set<object>()): boolean {\n if (typeof value === \"function\" || value instanceof Promise) return true;\n if (!value || typeof value !== \"object\" || isNativeStream(value)) {\n return false;\n }\n if (Symbol.asyncIterator in value) return true;\n if (!isPlainObject(value) || seen.has(value)) return false;\n seen.add(value);\n return Reflect.ownKeys(value).some((key) => {\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n return descriptor?.get !== undefined || needsScope(descriptor?.value, seen);\n });\n}\n\n/** Preserve identity across callable exports, returned handles, and lazy streams. */\nexport function scopeApi<T>(\n value: T,\n scope: RequestScope,\n receiver?: unknown,\n preserveIdentityMethods = false,\n): T {\n if (typeof value === \"function\") {\n return new Proxy(value, {\n apply: (fn, thisArg, args) => {\n const result = scope.run(() =>\n Reflect.apply(fn, receiver ?? thisArg, args),\n );\n // Ambient resource guards must not change ordinary SP result objects.\n if (preserveIdentityMethods) return result;\n return scopeApi(result, scope);\n },\n get: (fn, key) =>\n !preserveIdentityMethods && isIdentityMethod(key)\n ? undefined\n : scopeApi(Reflect.get(fn, key), scope, fn, preserveIdentityMethods),\n });\n }\n if (value instanceof Promise) {\n return value.then((result) =>\n scopeApi(result, scope, undefined, preserveIdentityMethods),\n ) as T;\n }\n // Native streams pass through unchanged. The authenticated request already\n // ran inside the caller scope; reading the body is pure data with no\n // deferred identity work, and wrapping would strip getReader/pipe/cancel.\n if (isNativeStream(value)) {\n return value;\n }\n if (value && typeof value === \"object\" && Symbol.asyncIterator in value) {\n const iterable = value as AsyncIterable<unknown>;\n const wrapIterator = (\n iterator: AsyncIterator<unknown>,\n ): AsyncIterableIterator<unknown> => {\n const finish = iterator.return?.bind(iterator);\n const fail = iterator.throw?.bind(iterator);\n return {\n [Symbol.asyncIterator]() {\n return this;\n },\n next: (...args: [] | [unknown]) =>\n scope.run(() => iterator.next(...args)),\n ...(finish && {\n return: (result?: unknown) => scope.run(() => finish(result)),\n }),\n ...(fail && {\n throw: (error?: unknown) => scope.run(() => fail(error)),\n }),\n };\n };\n if (\"next\" in value && typeof value.next === \"function\") {\n return wrapIterator(value as AsyncIterableIterator<unknown>) as T;\n }\n return {\n [Symbol.asyncIterator]: () =>\n wrapIterator(scope.run(() => iterable[Symbol.asyncIterator]())),\n } as T;\n }\n if (isPlainObject(value)) {\n // Plain data results come back unchanged: mutable and identity-stable.\n if (!needsScope(value)) return value;\n const result = Object.create(Object.getPrototypeOf(value));\n for (const key of Reflect.ownKeys(value)) {\n if (!preserveIdentityMethods && isIdentityMethod(key)) continue;\n Object.defineProperty(result, key, {\n enumerable: Object.getOwnPropertyDescriptor(value, key)?.enumerable,\n configurable: true,\n get: () => {\n const member = scope.run(() => Reflect.get(value, key));\n // Data members are returned as-is so repeated reads are identical.\n return needsScope(member)\n ? scopeApi(\n member,\n scope,\n receiver ?? value,\n preserveIdentityMethods,\n )\n : member;\n },\n set: (next: unknown) => {\n Reflect.set(value, key, next);\n },\n });\n }\n return result;\n }\n return value;\n}\n\n/** Compatibility adapter for direct Plugin.asUser callers. */\nexport function scopePlugin<T extends object>(\n plugin: T,\n scope: RequestScope,\n): T {\n return new Proxy(plugin, {\n get(target, key) {\n if (isIdentityMethod(key)) return undefined;\n const value = Reflect.get(target, key, target);\n if (typeof key === \"string\" && EXCLUDED_FROM_PROXY.has(key)) return value;\n if (key === \"exports\" && typeof value === \"function\") {\n return () =>\n scopeApi(scope.run(() => value.call(target)) ?? {}, scope, target);\n }\n if (typeof value === \"function\") {\n return (...args: unknown[]) =>\n scope.run(() => Reflect.apply(value, target, args));\n }\n return scopeApi(value, scope, target);\n },\n });\n}\n"],"mappings":"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"}
@@ -1,24 +1,28 @@
1
1
  import { ClientOptions, WorkspaceClient } from "../workspace-client/index.js";
2
2
  import { UserContext } from "./user-context.js";
3
+ import { CallerContext } from "./caller-context.js";
3
4
 
4
5
  //#region src/context/service-context.d.ts
5
6
  /**
6
- * Service context holds the service principal client and shared resources.
7
+ * Service context holds the service principal identity and workspace client.
7
8
  * This is initialized once at app startup and shared across all requests.
8
9
  */
9
10
  interface ServiceContextState {
10
11
  /** WorkspaceClient authenticated as the service principal */
11
- client: WorkspaceClient;
12
+ readonly client: WorkspaceClient;
12
13
  /** The service principal's user ID */
13
- serviceUserId: string;
14
- /** Promise that resolves to the warehouse ID (only present when a plugin requires `SQL_WAREHOUSE` resource) */
15
- warehouseId?: Promise<string>;
14
+ readonly serviceUserId: string;
15
+ /**
16
+ * @deprecated Use getWarehouseId() from @databricks/appkit.
17
+ * Retained for backward compatibility.
18
+ */
19
+ readonly warehouseId?: Promise<string>;
16
20
  /** Promise that resolves to the workspace ID */
17
- workspaceId: Promise<string>;
21
+ readonly workspaceId: Promise<string>;
18
22
  }
19
23
  /**
20
24
  * ServiceContext is a singleton that manages the service principal's
21
- * WorkspaceClient and shared resources like warehouse/workspace IDs.
25
+ * WorkspaceClient and workspace ID. WarehouseResource owns warehouse bindings.
22
26
  *
23
27
  * It's initialized once at app startup and provides the foundation
24
28
  * for both service principal and user context execution.
@@ -30,12 +34,12 @@ declare class ServiceContext {
30
34
  * Initialize the service context. Should be called once at app startup.
31
35
  * Safe to call multiple times - will return the same instance.
32
36
  *
33
- * @param options - Which shared resources to resolve (derived from plugin manifests).
37
+ * @param options - A resolved warehouse ID, or a boolean enabling discovery.
34
38
  * @param client - Optional pre-configured WorkspaceClient to use instead
35
39
  * of creating one from environment credentials.
36
40
  */
37
41
  static initialize(options?: {
38
- warehouseId?: boolean;
42
+ warehouseId?: boolean | string;
39
43
  }, client?: WorkspaceClient): Promise<ServiceContextState>;
40
44
  /**
41
45
  * Get the initialized service context.
@@ -47,14 +51,17 @@ declare class ServiceContext {
47
51
  */
48
52
  static isInitialized(): boolean;
49
53
  /**
50
- * Create a user context from request headers.
54
+ * Create an immutable caller context from the existing user request headers.
51
55
  *
52
56
  * @param token - The user's access token from x-forwarded-access-token header
53
57
  * @param userId - The user's ID from x-forwarded-user header
54
58
  * @param userName - Optional user name
59
+ * @param userEmail - Optional email from x-forwarded-email
55
60
  * @throws Error if token is not provided
56
61
  */
57
- static createUserContext(token: string, userId: string, userName?: string, userEmail?: string): UserContext;
62
+ static createCallerContext(token: string, userId: string, userName?: string, userEmail?: string): CallerContext;
63
+ /** @deprecated Use ServiceContext.createCallerContext. */
64
+ static createUserContext(token: string, userId: string, userName?: string, userEmail?: string): CallerContext & UserContext;
58
65
  /**
59
66
  * Get the client options for WorkspaceClient.
60
67
  * Exposed for testing purposes.
@@ -62,7 +69,6 @@ declare class ServiceContext {
62
69
  static getClientOptions(): ClientOptions;
63
70
  private static createContext;
64
71
  private static getWorkspaceId;
65
- private static getWarehouseId;
66
72
  /**
67
73
  * Reset the service context. Only for testing purposes.
68
74
  */
@@ -1 +1 @@
1
- {"version":3,"file":"service-context.d.ts","names":[],"sources":["../../src/context/service-context.ts"],"mappings":";;;;;;AAqBA;;UAAiB,mBAAA;EAEP;EAAR,MAAA,EAAQ,eAAA;EAMK;EAJb,aAAA;EAIoB;EAFpB,WAAA,GAAc,OAAA;EAJN;EAMR,WAAA,EAAa,OAAA;AAAA;;;;;;AAUf;;cAAa,cAAA;EAAA,eACI,QAAA;EAAA,eACA,WAAA;EAaZ;;;;;;;;EAAA,OAHU,UAAA,CACX,OAAA;IAAY,WAAA;EAAA,GACZ,MAAA,GAAS,eAAA,GACR,OAAA,CAAQ,mBAAA;EADA;;;;EAAA,OAmBJ,GAAA,CAAA,GAAO,mBAAA;EAAA;;;EAAA,OAaP,aAAA,CAAA;EAcL;;;;;;;;EAAA,OAFK,iBAAA,CACL,KAAA,UACA,MAAA,UACA,QAAA,WACA,SAAA,YACC,WAAA;EAqLI;;;;EAAA,OA1IA,gBAAA,CAAA,GAAoB,aAAA;EAAA,eAIN,aAAA;EAAA,eA2CA,cAAA;EAAA,eAuBA,cAAA;;;;SAoEd,KAAA,CAAA;AAAA"}
1
+ {"version":3,"file":"service-context.d.ts","names":[],"sources":["../../src/context/service-context.ts"],"mappings":";;;;;;;AAuBA;;UAAiB,mBAAA;EAEE;EAAA,SAAR,MAAA,EAAQ,eAAA;EASK;EAAA,SAPb,aAAA;EAOoB;;;;EAAA,SAFpB,WAAA,GAAc,OAAA;EAAA;EAAA,SAEd,WAAA,EAAa,OAAA;AAAA;;;AAUxB;;;;;cAAa,cAAA;EAAA,eACI,QAAA;EAAA,eACA,WAAA;EA4GZ;;;;;;;;EAAA,OAlGU,UAAA,CACX,OAAA;IAAY,WAAA;EAAA,GACZ,MAAA,GAAS,eAAA,GACR,OAAA,CAAQ,mBAAA;EADT;;;;EAAA,OAmBK,GAAA,CAAA,GAAO,mBAAA;EAaP;;;EAAA,OAAA,aAAA,CAAA;EAgBL;;;;;;;;;EAAA,OAHK,mBAAA,CACL,KAAA,UACA,MAAA,UACA,QAAA,WACA,SAAA,YACC,aAAA;EAiEI;EAAA,OAxBA,iBAAA,CACL,KAAA,UACA,MAAA,UACA,QAAA,WACA,SAAA,YACC,aAAA,GAAgB,WAAA;EAuBE;;;;EAAA,OAJd,gBAAA,CAAA,GAAoB,aAAA;EAAA,eAIN,aAAA;EAAA,eA4CA,cAAA;;;;SA0Bd,KAAA,CAAA;AAAA"}
@@ -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
- const host = process.env.DATABRICKS_HOST;
65
+ const host = process.env.DATABRICKS_HOST || (process.env.NODE_ENV === "development" && ServiceContext.isInitialized() ? ServiceContext.get().client.config?.host : void 0);
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 // Local templates can configure only a profile, whose host the SDK resolved.\n const host =\n process.env.DATABRICKS_HOST ||\n (process.env.NODE_ENV === \"development\" && ServiceContext.isInitialized()\n ? ServiceContext.get().client.config?.host\n : undefined);\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":"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"}