@databricks/appkit 0.81.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 (116) hide show
  1. package/CLAUDE.md +7 -1
  2. package/dist/appkit/package.js +1 -1
  3. package/dist/cli/commands/plugin/create/create.js +20 -8
  4. package/dist/cli/commands/plugin/create/create.js.map +1 -1
  5. package/dist/cli/commands/plugin/create/scaffold.js +3 -1
  6. package/dist/cli/commands/plugin/create/scaffold.js.map +1 -1
  7. package/dist/cli/commands/registry/add.js +2 -7
  8. package/dist/cli/commands/registry/add.js.map +1 -1
  9. package/dist/cli/package-manager.js +65 -0
  10. package/dist/cli/package-manager.js.map +1 -0
  11. package/dist/connectors/lakebase/index.js +1 -1
  12. package/dist/connectors/lakebase/routing-pool.d.ts +1 -1
  13. package/dist/connectors/lakebase/routing-pool.js +3 -3
  14. package/dist/connectors/lakebase/routing-pool.js.map +1 -1
  15. package/dist/context/caller-context.d.ts +22 -0
  16. package/dist/context/caller-context.d.ts.map +1 -0
  17. package/dist/context/caller-context.js +21 -0
  18. package/dist/context/caller-context.js.map +1 -0
  19. package/dist/context/deprecation.js +14 -0
  20. package/dist/context/deprecation.js.map +1 -0
  21. package/dist/context/execution-context.d.ts +12 -4
  22. package/dist/context/execution-context.d.ts.map +1 -1
  23. package/dist/context/execution-context.js +36 -34
  24. package/dist/context/execution-context.js.map +1 -1
  25. package/dist/context/index.d.ts +3 -2
  26. package/dist/context/index.js +2 -1
  27. package/dist/context/service-context.d.ts +18 -12
  28. package/dist/context/service-context.d.ts.map +1 -1
  29. package/dist/context/service-context.js +44 -54
  30. package/dist/context/service-context.js.map +1 -1
  31. package/dist/context/user-context.d.ts +5 -8
  32. package/dist/context/user-context.d.ts.map +1 -1
  33. package/dist/context/user-context.js +33 -6
  34. package/dist/context/user-context.js.map +1 -1
  35. package/dist/core/appkit.d.ts.map +1 -1
  36. package/dist/core/appkit.js +4 -2
  37. package/dist/core/appkit.js.map +1 -1
  38. package/dist/index.d.ts +7 -4
  39. package/dist/index.js +4 -2
  40. package/dist/plugin/plugin.d.ts +2 -2
  41. package/dist/plugin/plugin.d.ts.map +1 -1
  42. package/dist/plugin/plugin.js +6 -6
  43. package/dist/plugin/plugin.js.map +1 -1
  44. package/dist/plugins/agents/agents.d.ts +1 -1
  45. package/dist/plugins/ai-search/ai-search.d.ts +1 -1
  46. package/dist/plugins/analytics/analytics.d.ts +1 -1
  47. package/dist/plugins/analytics/analytics.d.ts.map +1 -1
  48. package/dist/plugins/analytics/analytics.js +3 -1
  49. package/dist/plugins/analytics/analytics.js.map +1 -1
  50. package/dist/plugins/database/database.d.ts +1 -1
  51. package/dist/plugins/files/plugin.d.ts +11 -11
  52. package/dist/plugins/files/plugin.js +17 -17
  53. package/dist/plugins/files/plugin.js.map +1 -1
  54. package/dist/plugins/genie/genie.d.ts +1 -1
  55. package/dist/plugins/jobs/plugin.d.ts +1 -1
  56. package/dist/plugins/lakebase/lakebase.d.ts +1 -1
  57. package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
  58. package/dist/plugins/lakebase/lakebase.js +4 -4
  59. package/dist/plugins/lakebase/lakebase.js.map +1 -1
  60. package/dist/plugins/server/index.d.ts +1 -1
  61. package/dist/plugins/server/index.js +2 -2
  62. package/dist/plugins/server/index.js.map +1 -1
  63. package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js +3 -3
  64. package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js.map +1 -1
  65. package/dist/plugins/server/static-server.js +3 -3
  66. package/dist/plugins/server/static-server.js.map +1 -1
  67. package/dist/plugins/server/utils.js +3 -3
  68. package/dist/plugins/server/utils.js.map +1 -1
  69. package/dist/plugins/server/vite-dev-server.js +4 -4
  70. package/dist/plugins/server/vite-dev-server.js.map +1 -1
  71. package/dist/plugins/serving/serving.d.ts +1 -1
  72. package/dist/resources/index.d.ts +1 -0
  73. package/dist/resources/index.js +3 -0
  74. package/dist/resources/warehouse.d.ts +16 -0
  75. package/dist/resources/warehouse.d.ts.map +1 -0
  76. package/dist/resources/warehouse.js +93 -0
  77. package/dist/resources/warehouse.js.map +1 -0
  78. package/dist/schemas/manifest.d.ts +9 -0
  79. package/dist/schemas/manifest.d.ts.map +1 -1
  80. package/dist/schemas/manifest.js +11 -0
  81. package/dist/schemas/manifest.js.map +1 -1
  82. package/dist/shared/src/schemas/manifest.d.ts +42 -33
  83. package/dist/shared/src/schemas/manifest.d.ts.map +1 -1
  84. package/dist/shared/src/schemas/manifest.js +11 -0
  85. package/dist/shared/src/schemas/manifest.js.map +1 -1
  86. package/dist/testing/create-test-app.js +2 -2
  87. package/dist/testing/create-test-app.js.map +1 -1
  88. package/dist/testing/fixtures.d.ts +3 -2
  89. package/dist/testing/fixtures.d.ts.map +1 -1
  90. package/dist/testing/fixtures.js +21 -14
  91. package/dist/testing/fixtures.js.map +1 -1
  92. package/dist/testing/reset-singletons.js +1 -1
  93. package/dist/type-generator/database/generate.js +3 -3
  94. package/dist/type-generator/database/generate.js.map +1 -1
  95. package/dist/type-generator/migration.js +2 -2
  96. package/dist/type-generator/migration.js.map +1 -1
  97. package/dist/type-generator/serving/server-file-extractor.js +3 -3
  98. package/dist/type-generator/serving/server-file-extractor.js.map +1 -1
  99. package/docs/api/appkit/Class.Plugin.md +1 -1
  100. package/docs/api/appkit/Function.getCurrentActorId.md +12 -0
  101. package/docs/api/appkit/Function.getCurrentPrincipalKey.md +12 -0
  102. package/docs/api/appkit/Function.getExecutionContext.md +5 -3
  103. package/docs/api/appkit/Function.getWarehouseId.md +20 -0
  104. package/docs/api/appkit/Interface.CallerContext.md +41 -0
  105. package/docs/api/appkit/Interface.PluginManifest.md +25 -2
  106. package/docs/api/appkit/TypeAlias.CallerPrincipal.md +13 -0
  107. package/docs/api/appkit/TypeAlias.ExecutionContext.md +6 -0
  108. package/docs/api/appkit.md +6 -0
  109. package/docs/development/templates.md +12 -11
  110. package/docs/plugins/agents.md +3 -1
  111. package/docs/plugins/analytics.md +8 -0
  112. package/docs/plugins/execution-context.md +16 -0
  113. package/docs/plugins/model-serving.md +4 -0
  114. package/llms.txt +7 -1
  115. package/package.json +1 -1
  116. package/sbom.cdx.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-manager.js","names":[],"sources":["../../src/cli/package-manager.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\ntype PackageManager = \"pnpm\" | \"npm\" | \"yarn\" | \"bun\";\n\nfunction isPackageManager(value: string): value is PackageManager {\n return (\n value === \"pnpm\" || value === \"npm\" || value === \"yarn\" || value === \"bun\"\n );\n}\n\n/**\n * Prefers the project's packageManager field, then its lockfiles, then\n * npm_config_user_agent. The launcher (e.g. npx) may use a different manager.\n */\nexport function detectPackageManager(\n cwd: string,\n fallback: PackageManager = \"pnpm\",\n): PackageManager {\n try {\n const pkg = JSON.parse(\n fs.readFileSync(path.join(cwd, \"package.json\"), \"utf-8\"),\n ) as { packageManager?: unknown } | null;\n if (typeof pkg?.packageManager === \"string\") {\n const name = pkg.packageManager.split(\"@\")[0];\n if (isPackageManager(name)) return name;\n }\n } catch {\n // A missing or unreadable manifest still permits lockfile detection.\n }\n\n if (fs.existsSync(path.join(cwd, \"pnpm-lock.yaml\"))) return \"pnpm\";\n if (fs.existsSync(path.join(cwd, \"yarn.lock\"))) return \"yarn\";\n if (\n fs.existsSync(path.join(cwd, \"bun.lock\")) ||\n fs.existsSync(path.join(cwd, \"bun.lockb\"))\n ) {\n return \"bun\";\n }\n if (\n fs.existsSync(path.join(cwd, \"package-lock.json\")) ||\n fs.existsSync(path.join(cwd, \"npm-shrinkwrap.json\"))\n ) {\n return \"npm\";\n }\n\n const userAgent = process.env.npm_config_user_agent;\n if (userAgent) {\n const firstToken = userAgent.split(\"/\")[0];\n if (isPackageManager(firstToken)) return firstToken;\n }\n\n return fallback;\n}\n\n/**\n * Command strings for each package manager.\n * Per-PM forms for: install, build (script), add, exec.\n * Scripts always use explicit `run` (e.g., `pnpm run build`).\n */\nexport const PM_COMMANDS: Record<\n PackageManager,\n {\n install: string;\n build: string;\n add: (pkgs: string) => string;\n exec: string;\n }\n> = {\n pnpm: {\n install: \"pnpm install\",\n build: \"pnpm run build\",\n add: (pkgs) => `pnpm add ${pkgs}`,\n exec: \"pnpm exec\",\n },\n npm: {\n install: \"npm install\",\n build: \"npm run build\",\n add: (pkgs) => `npm install ${pkgs}`,\n exec: \"npx\",\n },\n yarn: {\n install: \"yarn install\",\n build: \"yarn run build\",\n add: (pkgs) => `yarn add ${pkgs}`,\n exec: \"yarn dlx\",\n },\n bun: {\n install: \"bun install\",\n build: \"bun run build\",\n add: (pkgs) => `bun add ${pkgs}`,\n exec: \"bun x\",\n },\n};\n\nexport type { PackageManager };\n"],"mappings":";;;;AAKA,SAAS,iBAAiB,OAAwC;AAChE,QACE,UAAU,UAAU,UAAU,SAAS,UAAU,UAAU,UAAU;;;;;;AAQzE,SAAgB,qBACd,KACA,WAA2B,QACX;AAChB,KAAI;EACF,MAAM,MAAM,KAAK,MACf,GAAG,aAAa,KAAK,KAAK,KAAK,eAAe,EAAE,QAAQ,CACzD;AACD,MAAI,OAAO,KAAK,mBAAmB,UAAU;GAC3C,MAAM,OAAO,IAAI,eAAe,MAAM,IAAI,CAAC;AAC3C,OAAI,iBAAiB,KAAK,CAAE,QAAO;;SAE/B;AAIR,KAAI,GAAG,WAAW,KAAK,KAAK,KAAK,iBAAiB,CAAC,CAAE,QAAO;AAC5D,KAAI,GAAG,WAAW,KAAK,KAAK,KAAK,YAAY,CAAC,CAAE,QAAO;AACvD,KACE,GAAG,WAAW,KAAK,KAAK,KAAK,WAAW,CAAC,IACzC,GAAG,WAAW,KAAK,KAAK,KAAK,YAAY,CAAC,CAE1C,QAAO;AAET,KACE,GAAG,WAAW,KAAK,KAAK,KAAK,oBAAoB,CAAC,IAClD,GAAG,WAAW,KAAK,KAAK,KAAK,sBAAsB,CAAC,CAEpD,QAAO;CAGT,MAAM,YAAY,QAAQ,IAAI;AAC9B,KAAI,WAAW;EACb,MAAM,aAAa,UAAU,MAAM,IAAI,CAAC;AACxC,MAAI,iBAAiB,WAAW,CAAE,QAAO;;AAG3C,QAAO;;;;;;;AAQT,MAAa,cAQT;CACF,MAAM;EACJ,SAAS;EACT,OAAO;EACP,MAAM,SAAS,YAAY;EAC3B,MAAM;EACP;CACD,KAAK;EACH,SAAS;EACT,OAAO;EACP,MAAM,SAAS,eAAe;EAC9B,MAAM;EACP;CACD,MAAM;EACJ,SAAS;EACT,OAAO;EACP,MAAM,SAAS,YAAY;EAC3B,MAAM;EACP;CACD,KAAK;EACH,SAAS;EACT,OAAO;EACP,MAAM,SAAS,WAAW;EAC1B,MAAM;EACP;CACF"}
@@ -2,8 +2,8 @@ import { createWorkspaceClient } from "../../shared/src/workspace-client/factory
2
2
  import { getClientOptions } from "../../context/client-options.js";
3
3
  import { ConfigurationError } from "../../errors/configuration.js";
4
4
  import "../../errors/index.js";
5
- import { ServiceContext } from "../../context/service-context.js";
6
5
  import { createLogger } from "../../logging/logger.js";
6
+ import { ServiceContext } from "../../context/service-context.js";
7
7
  import { createLakebasePoolManager } from "./pool-manager.js";
8
8
  import { RoutingPool } from "./routing-pool.js";
9
9
  import { RequestedClaimsPermissionSet, createLakebasePool, generateDatabaseCredential, getLakebaseOrmConfig, getLakebasePgConfig, getUsernameWithApiLookup, getUsernameWithApiLookup as getUsernameWithApiLookup$1, getWorkspaceClient } from "@databricks/lakebase";
@@ -1,4 +1,4 @@
1
- import "../../context/user-context.js";
1
+ import "../../context/caller-context.js";
2
2
  import { Pool, PoolClient, QueryResult, QueryResultRow } from "pg";
3
3
 
4
4
  //#region src/connectors/lakebase/routing-pool.d.ts
@@ -1,11 +1,11 @@
1
- import { getUserContext } from "../../context/execution-context.js";
1
+ import { getCallerContext } from "../../context/execution-context.js";
2
2
 
3
3
  //#region src/connectors/lakebase/routing-pool.ts
4
4
  /**
5
5
  * A `pg.Pool`-like wrapper that routes queries to the appropriate pool
6
6
  * based on the current execution context.
7
7
  *
8
- * When called inside `runInUserContext()` (set up by `Plugin.asUser(req)`),
8
+ * When called inside `runInCallerContext()` (set up by `Plugin.asUser(req)`),
9
9
  * queries route to the per-user pool returned by `resolveUserPool`.
10
10
  * Otherwise, queries route to the service-principal pool.
11
11
  *
@@ -19,7 +19,7 @@ var RoutingPool = class {
19
19
  this.resolveUserPool = resolveUserPool;
20
20
  }
21
21
  activePool() {
22
- const userCtx = getUserContext();
22
+ const userCtx = getCallerContext();
23
23
  return userCtx ? this.resolveUserPool(userCtx) : this.spPool;
24
24
  }
25
25
  query(text, values) {
@@ -1 +1 @@
1
- {"version":3,"file":"routing-pool.js","names":[],"sources":["../../../src/connectors/lakebase/routing-pool.ts"],"sourcesContent":["import type { Pool, PoolClient, QueryResult, QueryResultRow } from \"pg\";\n\nimport { getUserContext } from \"../../context/execution-context\";\nimport type { UserContext } from \"../../context/user-context\";\n\n/**\n * Subset of `pg.Pool` exposed by the Lakebase plugin.\n *\n * RoutingPool does not extend EventEmitter — event listener methods\n * like `on('error', ...)` are not available. Use `query()`, `connect()`,\n * and `end()` for all pool operations.\n */\nexport interface LakebasePool {\n query<T extends QueryResultRow = any>(\n text: string,\n values?: unknown[],\n ): Promise<QueryResult<T>>;\n connect(): Promise<PoolClient>;\n end(): Promise<void>;\n readonly totalCount: number;\n readonly idleCount: number;\n readonly waitingCount: number;\n}\n\n/**\n * A `pg.Pool`-like wrapper that routes queries to the appropriate pool\n * based on the current execution context.\n *\n * When called inside `runInUserContext()` (set up by `Plugin.asUser(req)`),\n * queries route to the per-user pool returned by `resolveUserPool`.\n * Otherwise, queries route to the service-principal pool.\n *\n * This enables OBO (On-Behalf-Of) without custom `asUser()` overrides —\n * the base class sets up AsyncLocalStorage context, and the RoutingPool\n * reads it transparently.\n */\nexport class RoutingPool implements LakebasePool {\n constructor(\n private spPool: Pool,\n private resolveUserPool: (ctx: UserContext) => Pool,\n ) {}\n\n private activePool(): Pool {\n const userCtx = getUserContext();\n return userCtx ? this.resolveUserPool(userCtx) : this.spPool;\n }\n\n query<T extends QueryResultRow = any>(\n text: string,\n values?: unknown[],\n ): Promise<QueryResult<T>> {\n return this.activePool().query<T>(text, values);\n }\n\n connect(): Promise<PoolClient> {\n return this.activePool().connect();\n }\n\n async end(): Promise<void> {\n await this.spPool.end();\n }\n\n get totalCount() {\n return this.spPool.totalCount;\n }\n get idleCount() {\n return this.spPool.idleCount;\n }\n get waitingCount() {\n return this.spPool.waitingCount;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAoCA,IAAa,cAAb,MAAiD;CAC/C,YACE,AAAQ,QACR,AAAQ,iBACR;EAFQ;EACA;;CAGV,AAAQ,aAAmB;EACzB,MAAM,UAAU,gBAAgB;AAChC,SAAO,UAAU,KAAK,gBAAgB,QAAQ,GAAG,KAAK;;CAGxD,MACE,MACA,QACyB;AACzB,SAAO,KAAK,YAAY,CAAC,MAAS,MAAM,OAAO;;CAGjD,UAA+B;AAC7B,SAAO,KAAK,YAAY,CAAC,SAAS;;CAGpC,MAAM,MAAqB;AACzB,QAAM,KAAK,OAAO,KAAK;;CAGzB,IAAI,aAAa;AACf,SAAO,KAAK,OAAO;;CAErB,IAAI,YAAY;AACd,SAAO,KAAK,OAAO;;CAErB,IAAI,eAAe;AACjB,SAAO,KAAK,OAAO"}
1
+ {"version":3,"file":"routing-pool.js","names":[],"sources":["../../../src/connectors/lakebase/routing-pool.ts"],"sourcesContent":["import type { Pool, PoolClient, QueryResult, QueryResultRow } from \"pg\";\n\nimport type { CallerContext } from \"../../context/caller-context\";\nimport { getCallerContext } from \"../../context/execution-context\";\n\n/**\n * Subset of `pg.Pool` exposed by the Lakebase plugin.\n *\n * RoutingPool does not extend EventEmitter — event listener methods\n * like `on('error', ...)` are not available. Use `query()`, `connect()`,\n * and `end()` for all pool operations.\n */\nexport interface LakebasePool {\n query<T extends QueryResultRow = any>(\n text: string,\n values?: unknown[],\n ): Promise<QueryResult<T>>;\n connect(): Promise<PoolClient>;\n end(): Promise<void>;\n readonly totalCount: number;\n readonly idleCount: number;\n readonly waitingCount: number;\n}\n\n/**\n * A `pg.Pool`-like wrapper that routes queries to the appropriate pool\n * based on the current execution context.\n *\n * When called inside `runInCallerContext()` (set up by `Plugin.asUser(req)`),\n * queries route to the per-user pool returned by `resolveUserPool`.\n * Otherwise, queries route to the service-principal pool.\n *\n * This enables OBO (On-Behalf-Of) without custom `asUser()` overrides —\n * the base class sets up AsyncLocalStorage context, and the RoutingPool\n * reads it transparently.\n */\nexport class RoutingPool implements LakebasePool {\n constructor(\n private spPool: Pool,\n private resolveUserPool: (ctx: CallerContext) => Pool,\n ) {}\n\n private activePool(): Pool {\n const userCtx = getCallerContext();\n return userCtx ? this.resolveUserPool(userCtx) : this.spPool;\n }\n\n query<T extends QueryResultRow = any>(\n text: string,\n values?: unknown[],\n ): Promise<QueryResult<T>> {\n return this.activePool().query<T>(text, values);\n }\n\n connect(): Promise<PoolClient> {\n return this.activePool().connect();\n }\n\n async end(): Promise<void> {\n await this.spPool.end();\n }\n\n get totalCount() {\n return this.spPool.totalCount;\n }\n get idleCount() {\n return this.spPool.idleCount;\n }\n get waitingCount() {\n return this.spPool.waitingCount;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAoCA,IAAa,cAAb,MAAiD;CAC/C,YACE,AAAQ,QACR,AAAQ,iBACR;EAFQ;EACA;;CAGV,AAAQ,aAAmB;EACzB,MAAM,UAAU,kBAAkB;AAClC,SAAO,UAAU,KAAK,gBAAgB,QAAQ,GAAG,KAAK;;CAGxD,MACE,MACA,QACyB;AACzB,SAAO,KAAK,YAAY,CAAC,MAAS,MAAM,OAAO;;CAGjD,UAA+B;AAC7B,SAAO,KAAK,YAAY,CAAC,SAAS;;CAGpC,MAAM,MAAqB;AACzB,QAAM,KAAK,OAAO,KAAK;;CAGzB,IAAI,aAAa;AACf,SAAO,KAAK,OAAO;;CAErB,IAAI,YAAY;AACd,SAAO,KAAK,OAAO;;CAErB,IAAI,eAAe;AACjB,SAAO,KAAK,OAAO"}
@@ -0,0 +1,22 @@
1
+ import { ServiceContextState } from "./service-context.js";
2
+
3
+ //#region src/context/caller-context.d.ts
4
+ /** The caller identity whose permissions authorize execution, not its resources. */
5
+ type CallerPrincipal = Readonly<{
6
+ type: "user";
7
+ userId: string;
8
+ userName?: string;
9
+ userEmail?: string;
10
+ }>;
11
+ /** Caller identity and workspace for one immutable execution scope. */
12
+ interface CallerContext {
13
+ readonly client: ServiceContextState["client"];
14
+ readonly principal: CallerPrincipal;
15
+ /** Truncated SHA-256 hash of the caller token, used to detect rotation. */
16
+ readonly tokenFingerprint?: string;
17
+ readonly workspaceId: Promise<string>;
18
+ }
19
+ type ExecutionContext = ServiceContextState | CallerContext;
20
+ //#endregion
21
+ export { CallerContext, CallerPrincipal, ExecutionContext };
22
+ //# sourceMappingURL=caller-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caller-context.d.ts","names":[],"sources":["../../src/context/caller-context.ts"],"mappings":";;;;KAGY,eAAA,GAAkB,QAAA;EAC5B,IAAA;EACA,MAAA;EACA,QAAA;EACA,SAAA;AAAA;;UAIe,aAAA;EAAA,SACN,MAAA,EAAQ,mBAAA;EAAA,SACR,SAAA,EAAW,eAAA;EANX;EAAA,SAQA,gBAAA;EAAA,SACA,WAAA,EAAa,OAAA;AAAA;AAAA,KAkBZ,gBAAA,GAAmB,mBAAA,GAAsB,aAAA"}
@@ -0,0 +1,21 @@
1
+ //#region src/context/caller-context.ts
2
+ const snapshots = /* @__PURE__ */ new WeakSet();
3
+ /** Snapshot identity without freezing the SDK client's internal lifecycle. */
4
+ function snapshotCallerContext(ctx) {
5
+ if (snapshots.has(ctx)) return ctx;
6
+ const snapshot = Object.freeze({
7
+ client: ctx.client,
8
+ principal: Object.freeze({ ...ctx.principal }),
9
+ tokenFingerprint: ctx.tokenFingerprint,
10
+ workspaceId: ctx.workspaceId
11
+ });
12
+ snapshots.add(snapshot);
13
+ return snapshot;
14
+ }
15
+ function isCallerContext(ctx) {
16
+ return "principal" in ctx && ctx.principal.type === "user";
17
+ }
18
+
19
+ //#endregion
20
+ export { isCallerContext, snapshotCallerContext };
21
+ //# sourceMappingURL=caller-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caller-context.js","names":[],"sources":["../../src/context/caller-context.ts"],"sourcesContent":["import type { ServiceContextState } from \"./service-context\";\n\n/** The caller identity whose permissions authorize execution, not its resources. */\nexport type CallerPrincipal = Readonly<{\n type: \"user\";\n userId: string;\n userName?: string;\n userEmail?: string;\n}>;\n\n/** Caller identity and workspace for one immutable execution scope. */\nexport interface CallerContext {\n readonly client: ServiceContextState[\"client\"];\n readonly principal: CallerPrincipal;\n /** Truncated SHA-256 hash of the caller token, used to detect rotation. */\n readonly tokenFingerprint?: string;\n readonly workspaceId: Promise<string>;\n}\n\nconst snapshots = new WeakSet<CallerContext>();\n\n/** Snapshot identity without freezing the SDK client's internal lifecycle. */\nexport function snapshotCallerContext(ctx: CallerContext): CallerContext {\n if (snapshots.has(ctx)) return ctx;\n const snapshot = Object.freeze({\n client: ctx.client,\n principal: Object.freeze({ ...ctx.principal }),\n tokenFingerprint: ctx.tokenFingerprint,\n workspaceId: ctx.workspaceId,\n });\n snapshots.add(snapshot);\n return snapshot;\n}\n\nexport type ExecutionContext = ServiceContextState | CallerContext;\n\nexport function isCallerContext(ctx: ExecutionContext): ctx is CallerContext {\n return \"principal\" in ctx && ctx.principal.type === \"user\";\n}\n"],"mappings":";AAmBA,MAAM,4BAAY,IAAI,SAAwB;;AAG9C,SAAgB,sBAAsB,KAAmC;AACvE,KAAI,UAAU,IAAI,IAAI,CAAE,QAAO;CAC/B,MAAM,WAAW,OAAO,OAAO;EAC7B,QAAQ,IAAI;EACZ,WAAW,OAAO,OAAO,EAAE,GAAG,IAAI,WAAW,CAAC;EAC9C,kBAAkB,IAAI;EACtB,aAAa,IAAI;EAClB,CAAC;AACF,WAAU,IAAI,SAAS;AACvB,QAAO;;AAKT,SAAgB,gBAAgB,KAA6C;AAC3E,QAAO,eAAe,OAAO,IAAI,UAAU,SAAS"}
@@ -0,0 +1,14 @@
1
+ import { createLogger } from "../logging/logger.js";
2
+
3
+ //#region src/context/deprecation.ts
4
+ const logger = createLogger("execution-context");
5
+ const warned = /* @__PURE__ */ new Set();
6
+ function warnContextDeprecation(name, replacement) {
7
+ if (warned.has(name)) return;
8
+ warned.add(name);
9
+ logger.warn(`${name} is deprecated. Use ${replacement} instead.`);
10
+ }
11
+
12
+ //#endregion
13
+ export { warnContextDeprecation };
14
+ //# sourceMappingURL=deprecation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deprecation.js","names":[],"sources":["../../src/context/deprecation.ts"],"sourcesContent":["import { createLogger } from \"../logging/logger\";\n\nconst logger = createLogger(\"execution-context\");\nconst warned = new Set<string>();\n\nexport function warnContextDeprecation(\n name: string,\n replacement: string,\n): void {\n if (warned.has(name)) return;\n warned.add(name);\n logger.warn(`${name} is deprecated. Use ${replacement} instead.`);\n}\n"],"mappings":";;;AAEA,MAAM,SAAS,aAAa,oBAAoB;AAChD,MAAM,yBAAS,IAAI,KAAa;AAEhC,SAAgB,uBACd,MACA,aACM;AACN,KAAI,OAAO,IAAI,KAAK,CAAE;AACtB,QAAO,IAAI,KAAK;AAChB,QAAO,KAAK,GAAG,KAAK,sBAAsB,YAAY,WAAW"}
@@ -1,15 +1,23 @@
1
- import { ExecutionContext } from "./user-context.js";
1
+ import { UserContext } from "./user-context.js";
2
+ import { ServiceContextState } from "./service-context.js";
3
+ import { CallerContext } from "./caller-context.js";
2
4
 
3
5
  //#region src/context/execution-context.d.ts
4
6
  /**
5
7
  * Get the current execution context.
6
8
  *
7
- * - If running inside a user context (via asUser), returns the user context
9
+ * - If running inside a caller context (via asUser), returns the caller context
8
10
  * - Otherwise, returns the service context
9
11
  *
10
12
  * @throws Error if ServiceContext is not initialized
11
13
  */
12
- declare function getExecutionContext(): ExecutionContext;
14
+ declare function getExecutionContext(): ServiceContextState | (CallerContext & UserContext);
15
+ /**
16
+ * Get the principal key for future cache keying: `app` or `user:<id>`.
17
+ */
18
+ declare function getCurrentPrincipalKey(): string;
19
+ /** The initiating user in a caller scope; no user actor exists in service scope. */
20
+ declare function getCurrentActorId(): string | undefined;
13
21
  //#endregion
14
- export { getExecutionContext };
22
+ export { getCurrentActorId, getCurrentPrincipalKey, getExecutionContext };
15
23
  //# sourceMappingURL=execution-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"execution-context.d.ts","names":[],"sources":["../../src/context/execution-context.ts"],"mappings":";;;;;;;;;;;iBAoCgB,mBAAA,CAAA,GAAuB,gBAAA"}
1
+ {"version":3,"file":"execution-context.d.ts","names":[],"sources":["../../src/context/execution-context.ts"],"mappings":";;;;;;;;;AAkFA;;;;iBAbgB,mBAAA,CAAA,GACZ,mBAAA,IACC,aAAA,GAAgB,WAAA;AAiBrB;;;AAAA,iBANgB,sBAAA,CAAA;;iBAMA,iBAAA,CAAA"}
@@ -1,68 +1,70 @@
1
- import { ConfigurationError } from "../errors/configuration.js";
2
- import "../errors/index.js";
1
+ import { captureWarehouseId, runWithResourceBindings } from "../resources/warehouse.js";
2
+ import { snapshotCallerContext } from "./caller-context.js";
3
+ import { warnContextDeprecation } from "./deprecation.js";
4
+ import { legacyUserContext } from "./user-context.js";
3
5
  import { ServiceContext } from "./service-context.js";
4
- import { isUserContext } from "./user-context.js";
5
6
  import { AsyncLocalStorage } from "node:async_hooks";
6
7
 
7
8
  //#region src/context/execution-context.ts
8
- /**
9
- * AsyncLocalStorage for execution context.
10
- * Used to pass user context through the call stack without explicit parameters.
11
- */
12
9
  const executionContextStorage = new AsyncLocalStorage();
10
+ function runInCallerScope(callerContext, fn, legacyResources) {
11
+ const caller = snapshotCallerContext(callerContext);
12
+ return runWithResourceBindings(legacyResources, () => executionContextStorage.run(caller, fn));
13
+ }
13
14
  /**
14
- * Run a function in the context of a user.
15
- * All calls within the function will have access to the user context.
15
+ * Run a function with an immutable snapshot of the caller context.
16
+ * Nested and concurrent scopes keep their own identities.
16
17
  *
17
- * @param userContext - The user context to use
18
+ * @param callerContext - The caller context to use
18
19
  * @param fn - The function to run
19
20
  * @returns The result of the function
20
21
  */
21
- function runInUserContext(userContext, fn) {
22
- return executionContextStorage.run(userContext, fn);
22
+ function runInCallerContext(callerContext, fn) {
23
+ return runInCallerScope(callerContext, fn);
23
24
  }
24
25
  /**
25
26
  * Get the current execution context.
26
27
  *
27
- * - If running inside a user context (via asUser), returns the user context
28
+ * - If running inside a caller context (via asUser), returns the caller context
28
29
  * - Otherwise, returns the service context
29
30
  *
30
31
  * @throws Error if ServiceContext is not initialized
31
32
  */
32
33
  function getExecutionContext() {
33
- const userContext = executionContextStorage.getStore();
34
- if (userContext) return userContext;
34
+ const callerContext = executionContextStorage.getStore();
35
+ if (callerContext) return legacyUserContext(callerContext, captureWarehouseId());
35
36
  return ServiceContext.get();
36
37
  }
37
38
  /**
38
- * Get the current user ID for cache keying and telemetry.
39
- *
40
- * Returns the user ID if in user context, otherwise the service user ID.
39
+ * Get the principal key for future cache keying: `app` or `user:<id>`.
41
40
  */
42
- function getCurrentUserId() {
43
- const ctx = getExecutionContext();
44
- if (isUserContext(ctx)) return ctx.userId;
45
- return ctx.serviceUserId;
41
+ function getCurrentPrincipalKey() {
42
+ const caller = getCallerContext();
43
+ return caller ? `user:${caller.principal.userId}` : "app";
44
+ }
45
+ /** The initiating user in a caller scope; no user actor exists in service scope. */
46
+ function getCurrentActorId() {
47
+ return getCallerContext()?.principal.userId;
46
48
  }
47
49
  /**
48
- * Get the WorkspaceClient for the current execution context.
50
+ * @deprecated Use getCurrentPrincipalKey for new cache keys or getCurrentActorId
51
+ * for audit. Preserves the bare user or service ID for existing callers.
49
52
  */
50
- function getWorkspaceClient() {
51
- return getExecutionContext().client;
53
+ function getCurrentUserId() {
54
+ warnContextDeprecation("getCurrentUserId", "getCurrentPrincipalKey (cache) or getCurrentActorId (audit)");
55
+ return getCurrentActorId() ?? ServiceContext.get().serviceUserId;
52
56
  }
53
57
  /**
54
- * Get the warehouse ID promise.
58
+ * Get the WorkspaceClient for the current execution context.
55
59
  */
56
- function getWarehouseId() {
57
- const ctx = getExecutionContext();
58
- if (!ctx.warehouseId) throw ConfigurationError.resourceNotFound("Warehouse ID", "No plugin requires a SQL Warehouse. Add a sql_warehouse resource to your plugin manifest, or set DATABRICKS_WAREHOUSE_ID");
59
- return ctx.warehouseId;
60
+ function getWorkspaceClient() {
61
+ return (getCallerContext() ?? ServiceContext.get()).client;
60
62
  }
61
63
  /**
62
64
  * Get the workspace ID promise.
63
65
  */
64
66
  function getWorkspaceId() {
65
- return getExecutionContext().workspaceId;
67
+ return (getCallerContext() ?? ServiceContext.get()).workspaceId;
66
68
  }
67
69
  /**
68
70
  * Check if currently running in a user context.
@@ -71,14 +73,14 @@ function isInUserContext() {
71
73
  return executionContextStorage.getStore() !== void 0;
72
74
  }
73
75
  /**
74
- * Get the user context if one is active, otherwise `undefined`.
76
+ * Get the caller context if one is active, otherwise `undefined`.
75
77
  * Unlike `getExecutionContext()`, this does not require `ServiceContext`
76
78
  * to be initialized and never throws.
77
79
  */
78
- function getUserContext() {
80
+ function getCallerContext() {
79
81
  return executionContextStorage.getStore();
80
82
  }
81
83
 
82
84
  //#endregion
83
- export { getCurrentUserId, getExecutionContext, getUserContext, getWarehouseId, getWorkspaceClient, getWorkspaceId, isInUserContext, runInUserContext };
85
+ export { getCallerContext, getCurrentActorId, getCurrentPrincipalKey, getCurrentUserId, getExecutionContext, getWorkspaceClient, getWorkspaceId, isInUserContext, runInCallerContext };
84
86
  //# sourceMappingURL=execution-context.js.map
@@ -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 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\nconst executionContextStorage = new AsyncLocalStorage<CallerContext>();\n\nfunction runInCallerScope<T>(\n callerContext: CallerContext,\n fn: () => T,\n legacyResources?: WarehouseBinding,\n): T {\n const caller = snapshotCallerContext(callerContext);\n return runWithResourceBindings(legacyResources, () =>\n executionContextStorage.run(caller, fn),\n );\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/** @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 );\n }\n return runInCallerContext(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 = executionContextStorage.getStore();\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/**\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 getCurrentActorId() ?? ServiceContext.get().serviceUserId;\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 * 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();\n}\n\n/** @deprecated Use getCallerContext and its principal field. */\nexport function getUserContext(): (CallerContext & UserContext) | undefined {\n warnContextDeprecation(\"getUserContext\", \"getCallerContext\");\n const scope = executionContextStorage.getStore();\n return scope ? legacyUserContext(scope, captureWarehouseId()) : undefined;\n}\n"],"mappings":";;;;;;;;AAiBA,MAAM,0BAA0B,IAAI,mBAAkC;AAEtE,SAAS,iBACP,eACA,IACA,iBACG;CACH,MAAM,SAAS,sBAAsB,cAAc;AACnD,QAAO,wBAAwB,uBAC7B,wBAAwB,IAAI,QAAQ,GAAG,CACxC;;;;;;;;;;AAWH,SAAgB,mBACd,eACA,IACG;AACH,QAAO,iBAAiB,eAAe,GAAG;;;;;;;;;;AA2B5C,SAAgB,sBAEkB;CAChC,MAAM,gBAAgB,wBAAwB,UAAU;AACxD,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;;;;;;AAOvC,SAAgB,mBAA2B;AACzC,wBACE,oBACA,8DACD;AACD,QAAO,mBAAmB,IAAI,eAAe,KAAK,CAAC;;;;;AAMrD,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,mBAA8C;AAC5D,QAAO,wBAAwB,UAAU"}
@@ -1,3 +1,4 @@
1
- import { ServiceContext } from "./service-context.js";
2
1
  import { UserContext } from "./user-context.js";
3
- import { getExecutionContext } from "./execution-context.js";
2
+ import { ServiceContext } from "./service-context.js";
3
+ import { CallerContext, CallerPrincipal, ExecutionContext } from "./caller-context.js";
4
+ import { getCurrentActorId, getCurrentPrincipalKey, getExecutionContext } from "./execution-context.js";
@@ -1,4 +1,5 @@
1
+ import { isCallerContext } from "./caller-context.js";
1
2
  import { ServiceContext } from "./service-context.js";
2
- import { getCurrentUserId, getExecutionContext, getWarehouseId, getWorkspaceClient, getWorkspaceId, runInUserContext } from "./execution-context.js";
3
+ import { getCallerContext, getCurrentActorId, getCurrentPrincipalKey, getCurrentUserId, getExecutionContext, getWorkspaceClient, getWorkspaceId, runInCallerContext } from "./execution-context.js";
3
4
 
4
5
  export { getWorkspaceClient };
@@ -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;EAuGZ;;;;;;;;EAAA,OA7FU,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;EA4DI;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
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