@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.
- package/CLAUDE.md +7 -1
- package/dist/appkit/package.js +1 -1
- package/dist/cli/commands/plugin/create/create.js +20 -8
- package/dist/cli/commands/plugin/create/create.js.map +1 -1
- package/dist/cli/commands/plugin/create/scaffold.js +3 -1
- package/dist/cli/commands/plugin/create/scaffold.js.map +1 -1
- package/dist/cli/commands/registry/add.js +2 -7
- package/dist/cli/commands/registry/add.js.map +1 -1
- package/dist/cli/package-manager.js +65 -0
- package/dist/cli/package-manager.js.map +1 -0
- package/dist/connectors/lakebase/index.js +1 -1
- package/dist/connectors/lakebase/routing-pool.d.ts +1 -1
- package/dist/connectors/lakebase/routing-pool.js +3 -3
- package/dist/connectors/lakebase/routing-pool.js.map +1 -1
- package/dist/context/caller-context.d.ts +22 -0
- package/dist/context/caller-context.d.ts.map +1 -0
- package/dist/context/caller-context.js +21 -0
- package/dist/context/caller-context.js.map +1 -0
- package/dist/context/deprecation.js +14 -0
- package/dist/context/deprecation.js.map +1 -0
- package/dist/context/execution-context.d.ts +12 -4
- package/dist/context/execution-context.d.ts.map +1 -1
- package/dist/context/execution-context.js +36 -34
- package/dist/context/execution-context.js.map +1 -1
- package/dist/context/index.d.ts +3 -2
- package/dist/context/index.js +2 -1
- package/dist/context/service-context.d.ts +18 -12
- package/dist/context/service-context.d.ts.map +1 -1
- package/dist/context/service-context.js +44 -54
- package/dist/context/service-context.js.map +1 -1
- package/dist/context/user-context.d.ts +5 -8
- package/dist/context/user-context.d.ts.map +1 -1
- package/dist/context/user-context.js +33 -6
- package/dist/context/user-context.js.map +1 -1
- package/dist/core/appkit.d.ts.map +1 -1
- package/dist/core/appkit.js +4 -2
- package/dist/core/appkit.js.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +4 -2
- package/dist/plugin/plugin.d.ts +2 -2
- package/dist/plugin/plugin.d.ts.map +1 -1
- package/dist/plugin/plugin.js +6 -6
- package/dist/plugin/plugin.js.map +1 -1
- package/dist/plugins/agents/agents.d.ts +1 -1
- package/dist/plugins/ai-search/ai-search.d.ts +1 -1
- package/dist/plugins/analytics/analytics.d.ts +1 -1
- package/dist/plugins/analytics/analytics.d.ts.map +1 -1
- package/dist/plugins/analytics/analytics.js +3 -1
- package/dist/plugins/analytics/analytics.js.map +1 -1
- package/dist/plugins/database/database.d.ts +1 -1
- package/dist/plugins/files/plugin.d.ts +11 -11
- package/dist/plugins/files/plugin.js +17 -17
- package/dist/plugins/files/plugin.js.map +1 -1
- package/dist/plugins/genie/genie.d.ts +1 -1
- package/dist/plugins/jobs/plugin.d.ts +1 -1
- package/dist/plugins/lakebase/lakebase.d.ts +1 -1
- package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
- package/dist/plugins/lakebase/lakebase.js +4 -4
- package/dist/plugins/lakebase/lakebase.js.map +1 -1
- package/dist/plugins/server/index.d.ts +1 -1
- package/dist/plugins/server/index.js +2 -2
- package/dist/plugins/server/index.js.map +1 -1
- package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js +3 -3
- package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js.map +1 -1
- package/dist/plugins/server/static-server.js +3 -3
- package/dist/plugins/server/static-server.js.map +1 -1
- package/dist/plugins/server/utils.js +3 -3
- package/dist/plugins/server/utils.js.map +1 -1
- package/dist/plugins/server/vite-dev-server.js +4 -4
- package/dist/plugins/server/vite-dev-server.js.map +1 -1
- package/dist/plugins/serving/serving.d.ts +1 -1
- package/dist/resources/index.d.ts +1 -0
- package/dist/resources/index.js +3 -0
- package/dist/resources/warehouse.d.ts +16 -0
- package/dist/resources/warehouse.d.ts.map +1 -0
- package/dist/resources/warehouse.js +93 -0
- package/dist/resources/warehouse.js.map +1 -0
- package/dist/schemas/manifest.d.ts +9 -0
- package/dist/schemas/manifest.d.ts.map +1 -1
- package/dist/schemas/manifest.js +11 -0
- package/dist/schemas/manifest.js.map +1 -1
- package/dist/shared/src/schemas/manifest.d.ts +42 -33
- package/dist/shared/src/schemas/manifest.d.ts.map +1 -1
- package/dist/shared/src/schemas/manifest.js +11 -0
- package/dist/shared/src/schemas/manifest.js.map +1 -1
- package/dist/testing/create-test-app.js +2 -2
- package/dist/testing/create-test-app.js.map +1 -1
- package/dist/testing/fixtures.d.ts +3 -2
- package/dist/testing/fixtures.d.ts.map +1 -1
- package/dist/testing/fixtures.js +21 -14
- package/dist/testing/fixtures.js.map +1 -1
- package/dist/testing/reset-singletons.js +1 -1
- package/dist/type-generator/database/generate.js +3 -3
- package/dist/type-generator/database/generate.js.map +1 -1
- package/dist/type-generator/migration.js +2 -2
- package/dist/type-generator/migration.js.map +1 -1
- package/dist/type-generator/serving/server-file-extractor.js +3 -3
- package/dist/type-generator/serving/server-file-extractor.js.map +1 -1
- package/docs/api/appkit/Class.Plugin.md +1 -1
- package/docs/api/appkit/Function.getCurrentActorId.md +12 -0
- package/docs/api/appkit/Function.getCurrentPrincipalKey.md +12 -0
- package/docs/api/appkit/Function.getExecutionContext.md +5 -3
- package/docs/api/appkit/Function.getWarehouseId.md +20 -0
- package/docs/api/appkit/Interface.CallerContext.md +41 -0
- package/docs/api/appkit/Interface.PluginManifest.md +25 -2
- package/docs/api/appkit/TypeAlias.CallerPrincipal.md +13 -0
- package/docs/api/appkit/TypeAlias.ExecutionContext.md +6 -0
- package/docs/api/appkit.md +6 -0
- package/docs/development/templates.md +12 -11
- package/docs/plugins/agents.md +3 -1
- package/docs/plugins/analytics.md +8 -0
- package/docs/plugins/execution-context.md +16 -0
- package/docs/plugins/model-serving.md +4 -0
- package/llms.txt +7 -1
- package/package.json +1 -1
- 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,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
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 `
|
|
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 =
|
|
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 {
|
|
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 {
|
|
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
|
|
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():
|
|
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":"
|
|
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 {
|
|
2
|
-
import "
|
|
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
|
|
15
|
-
*
|
|
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
|
|
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
|
|
22
|
-
return
|
|
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
|
|
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
|
|
34
|
-
if (
|
|
34
|
+
const callerContext = executionContextStorage.getStore();
|
|
35
|
+
if (callerContext) return legacyUserContext(callerContext, captureWarehouseId());
|
|
35
36
|
return ServiceContext.get();
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
38
|
-
* Get the
|
|
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
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
*
|
|
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
|
|
51
|
-
|
|
53
|
+
function getCurrentUserId() {
|
|
54
|
+
warnContextDeprecation("getCurrentUserId", "getCurrentPrincipalKey (cache) or getCurrentActorId (audit)");
|
|
55
|
+
return getCurrentActorId() ?? ServiceContext.get().serviceUserId;
|
|
52
56
|
}
|
|
53
57
|
/**
|
|
54
|
-
* Get the
|
|
58
|
+
* Get the WorkspaceClient for the current execution context.
|
|
55
59
|
*/
|
|
56
|
-
function
|
|
57
|
-
|
|
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
|
|
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
|
|
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
|
|
80
|
+
function getCallerContext() {
|
|
79
81
|
return executionContextStorage.getStore();
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
//#endregion
|
|
83
|
-
export {
|
|
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 {
|
|
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"}
|
package/dist/context/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import { ServiceContext } from "./service-context.js";
|
|
2
1
|
import { UserContext } from "./user-context.js";
|
|
3
|
-
import {
|
|
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";
|
package/dist/context/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import { isCallerContext } from "./caller-context.js";
|
|
1
2
|
import { ServiceContext } from "./service-context.js";
|
|
2
|
-
import { getCurrentUserId, getExecutionContext,
|
|
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
|
|
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
|
-
/**
|
|
15
|
-
|
|
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
|
|
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 -
|
|
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
|
|
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
|
|
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":"
|
|
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
|
|
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 -
|
|
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
|
|
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
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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,
|
|
104
|
+
const [resolvedWorkspaceId, currentUser, resolvedResources] = await Promise.all([
|
|
90
105
|
ServiceContext.getWorkspaceId(wsClient),
|
|
91
106
|
wsClient.currentUser.me(),
|
|
92
|
-
|
|
107
|
+
WarehouseResource.resolve(wsClient, options?.warehouseId)
|
|
93
108
|
]);
|
|
94
109
|
if (!currentUser.id) throw ConfigurationError.resourceNotFound("Service user ID");
|
|
95
|
-
const
|
|
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
|
|