@databricks/appkit 0.31.0 → 0.32.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 +1 -0
- package/NOTICE.md +1 -0
- package/dist/appkit/package.js +1 -1
- package/dist/beta.d.ts +14 -1
- package/dist/beta.js +12 -1
- package/dist/connectors/index.js +3 -0
- package/dist/connectors/mcp/client.d.ts +60 -0
- package/dist/connectors/mcp/client.d.ts.map +1 -0
- package/dist/connectors/mcp/client.js +197 -0
- package/dist/connectors/mcp/client.js.map +1 -0
- package/dist/connectors/mcp/host-policy.d.ts +51 -0
- package/dist/connectors/mcp/host-policy.d.ts.map +1 -0
- package/dist/connectors/mcp/host-policy.js +168 -0
- package/dist/connectors/mcp/host-policy.js.map +1 -0
- package/dist/connectors/mcp/index.d.ts +3 -0
- package/dist/connectors/mcp/index.js +4 -0
- package/dist/connectors/mcp/types.d.ts +16 -0
- package/dist/connectors/mcp/types.d.ts.map +1 -0
- package/dist/context/index.js +1 -1
- package/dist/core/agent/build-toolkit.d.ts +2 -0
- package/dist/core/agent/build-toolkit.js +50 -0
- package/dist/core/agent/build-toolkit.js.map +1 -0
- package/dist/core/agent/consume-adapter-stream.js +33 -0
- package/dist/core/agent/consume-adapter-stream.js.map +1 -0
- package/dist/core/agent/create-agent.d.ts +27 -0
- package/dist/core/agent/create-agent.d.ts.map +1 -0
- package/dist/core/agent/create-agent.js +50 -0
- package/dist/core/agent/create-agent.js.map +1 -0
- package/dist/core/agent/load-agents.d.ts +67 -0
- package/dist/core/agent/load-agents.d.ts.map +1 -0
- package/dist/core/agent/load-agents.js +228 -0
- package/dist/core/agent/load-agents.js.map +1 -0
- package/dist/core/agent/normalize-result.js +39 -0
- package/dist/core/agent/normalize-result.js.map +1 -0
- package/dist/core/agent/run-agent.d.ts +34 -0
- package/dist/core/agent/run-agent.d.ts.map +1 -0
- package/dist/core/agent/run-agent.js +146 -0
- package/dist/core/agent/run-agent.js.map +1 -0
- package/dist/core/agent/system-prompt.js +38 -0
- package/dist/core/agent/system-prompt.js.map +1 -0
- package/dist/core/agent/tools/define-tool.d.ts +54 -0
- package/dist/core/agent/tools/define-tool.d.ts.map +1 -0
- package/dist/core/agent/tools/define-tool.js +50 -0
- package/dist/core/agent/tools/define-tool.js.map +1 -0
- package/dist/core/agent/tools/function-tool.d.ts +27 -0
- package/dist/core/agent/tools/function-tool.d.ts.map +1 -0
- package/dist/core/agent/tools/function-tool.js +21 -0
- package/dist/core/agent/tools/function-tool.js.map +1 -0
- package/dist/core/agent/tools/hosted-tools.d.ts +47 -0
- package/dist/core/agent/tools/hosted-tools.d.ts.map +1 -0
- package/dist/core/agent/tools/hosted-tools.js +67 -0
- package/dist/core/agent/tools/hosted-tools.js.map +1 -0
- package/dist/core/agent/tools/index.d.ts +5 -0
- package/dist/core/agent/tools/index.js +7 -0
- package/dist/core/agent/tools/json-schema.js +24 -0
- package/dist/core/agent/tools/json-schema.js.map +1 -0
- package/dist/core/agent/tools/sql-policy.js +256 -0
- package/dist/core/agent/tools/sql-policy.js.map +1 -0
- package/dist/core/agent/tools/tool.d.ts +34 -0
- package/dist/core/agent/tools/tool.d.ts.map +1 -0
- package/dist/core/agent/tools/tool.js +41 -0
- package/dist/core/agent/tools/tool.js.map +1 -0
- package/dist/core/agent/types.d.ts +214 -0
- package/dist/core/agent/types.d.ts.map +1 -0
- package/dist/core/agent/types.js +12 -0
- package/dist/core/agent/types.js.map +1 -0
- package/dist/core/appkit.d.ts +1 -0
- package/dist/core/appkit.d.ts.map +1 -1
- package/dist/core/appkit.js +31 -4
- package/dist/core/appkit.js.map +1 -1
- package/dist/core/plugin-context.d.ts +133 -0
- package/dist/core/plugin-context.d.ts.map +1 -0
- package/dist/core/plugin-context.js +220 -0
- package/dist/core/plugin-context.js.map +1 -0
- package/dist/index.d.ts +11 -11
- package/dist/internal-telemetry/appkit-log.js +19 -0
- package/dist/internal-telemetry/appkit-log.js.map +1 -0
- package/dist/internal-telemetry/config.js +15 -0
- package/dist/internal-telemetry/config.js.map +1 -0
- package/dist/internal-telemetry/index.js +4 -0
- package/dist/internal-telemetry/reporter.js +132 -0
- package/dist/internal-telemetry/reporter.js.map +1 -0
- package/dist/plugin/index.d.ts +1 -1
- package/dist/plugin/plugin.d.ts +18 -3
- package/dist/plugin/plugin.d.ts.map +1 -1
- package/dist/plugin/plugin.js +26 -2
- package/dist/plugin/plugin.js.map +1 -1
- package/dist/plugin/to-plugin.d.ts +15 -4
- package/dist/plugin/to-plugin.d.ts.map +1 -1
- package/dist/plugin/to-plugin.js +14 -4
- package/dist/plugin/to-plugin.js.map +1 -1
- package/dist/plugins/agents/agents.d.ts +4 -0
- package/dist/plugins/agents/agents.js +882 -0
- package/dist/plugins/agents/agents.js.map +1 -0
- package/dist/plugins/agents/defaults.js +13 -0
- package/dist/plugins/agents/defaults.js.map +1 -0
- package/dist/plugins/agents/event-channel.js +64 -0
- package/dist/plugins/agents/event-channel.js.map +1 -0
- package/dist/plugins/agents/event-translator.js +224 -0
- package/dist/plugins/agents/event-translator.js.map +1 -0
- package/dist/plugins/agents/index.d.ts +4 -0
- package/dist/plugins/agents/index.js +6 -0
- package/dist/plugins/agents/manifest.js +27 -0
- package/dist/plugins/agents/manifest.js.map +1 -0
- package/dist/plugins/agents/schemas.js +51 -0
- package/dist/plugins/agents/schemas.js.map +1 -0
- package/dist/plugins/agents/thread-store.js +58 -0
- package/dist/plugins/agents/thread-store.js.map +1 -0
- package/dist/plugins/agents/tool-approval-gate.js +75 -0
- package/dist/plugins/agents/tool-approval-gate.js.map +1 -0
- package/dist/plugins/analytics/analytics.d.ts +17 -2
- package/dist/plugins/analytics/analytics.d.ts.map +1 -1
- package/dist/plugins/analytics/analytics.js +33 -0
- package/dist/plugins/analytics/analytics.js.map +1 -1
- package/dist/plugins/files/plugin.d.ts +22 -3
- package/dist/plugins/files/plugin.d.ts.map +1 -1
- package/dist/plugins/files/plugin.js +102 -2
- package/dist/plugins/files/plugin.js.map +1 -1
- package/dist/plugins/genie/genie.d.ts +15 -2
- package/dist/plugins/genie/genie.d.ts.map +1 -1
- package/dist/plugins/genie/genie.js +45 -0
- package/dist/plugins/genie/genie.js.map +1 -1
- package/dist/plugins/jobs/plugin.d.ts +2 -1
- package/dist/plugins/jobs/plugin.d.ts.map +1 -1
- package/dist/plugins/jobs/plugin.js +1 -1
- package/dist/plugins/lakebase/index.d.ts +2 -2
- package/dist/plugins/lakebase/index.js +1 -1
- package/dist/plugins/lakebase/lakebase.d.ts +33 -4
- package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
- package/dist/plugins/lakebase/lakebase.js +77 -5
- package/dist/plugins/lakebase/lakebase.js.map +1 -1
- package/dist/plugins/lakebase/types.d.ts +38 -1
- package/dist/plugins/lakebase/types.d.ts.map +1 -1
- package/dist/plugins/server/index.d.ts +12 -1
- package/dist/plugins/server/index.d.ts.map +1 -1
- package/dist/plugins/server/index.js +39 -5
- package/dist/plugins/server/index.js.map +1 -1
- package/dist/plugins/server/types.d.ts +0 -3
- package/dist/plugins/server/types.d.ts.map +1 -1
- package/dist/plugins/serving/serving.d.ts +2 -1
- package/dist/plugins/serving/serving.d.ts.map +1 -1
- package/dist/shared/src/agent.d.ts +63 -1
- package/dist/shared/src/agent.d.ts.map +1 -1
- package/dist/shared/src/index.d.ts +1 -1
- package/dist/shared/src/plugin.d.ts +8 -0
- package/dist/shared/src/plugin.d.ts.map +1 -1
- package/docs/api/appkit/Class.Plugin.md +65 -23
- package/docs/api/appkit/Function.createApp.md +10 -8
- package/docs/privacy.md +41 -0
- package/llms.txt +1 -0
- package/package.json +4 -2
- package/sbom.cdx.json +1 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { buildAppkitPayload } from "./appkit-log.js";
|
|
2
|
+
|
|
3
|
+
//#region src/internal-telemetry/reporter.ts
|
|
4
|
+
const DEFAULT_HEARTBEAT_INTERVAL_MS = 300 * 1e3;
|
|
5
|
+
const DEFAULT_METRICS_FLUSH_INTERVAL_MS = 60 * 1e3;
|
|
6
|
+
function envIntervalMs(name, fallback) {
|
|
7
|
+
const raw = process.env[name];
|
|
8
|
+
if (!raw) return fallback;
|
|
9
|
+
const n = Number(raw);
|
|
10
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
11
|
+
}
|
|
12
|
+
var TelemetryReporter = class TelemetryReporter {
|
|
13
|
+
static #instance = null;
|
|
14
|
+
#workspaceIdPromise;
|
|
15
|
+
#client;
|
|
16
|
+
#appId;
|
|
17
|
+
#appkitVersion;
|
|
18
|
+
#heartbeatIntervalMs;
|
|
19
|
+
#metricsFlushIntervalMs;
|
|
20
|
+
#heartbeatTimer = null;
|
|
21
|
+
#metricsTimer = null;
|
|
22
|
+
#buckets = /* @__PURE__ */ new Map();
|
|
23
|
+
constructor(opts) {
|
|
24
|
+
this.#workspaceIdPromise = Promise.resolve(opts.workspaceId);
|
|
25
|
+
this.#workspaceIdPromise.catch(() => {});
|
|
26
|
+
this.#client = opts.client;
|
|
27
|
+
this.#appId = opts.appId;
|
|
28
|
+
this.#appkitVersion = opts.appkitVersion;
|
|
29
|
+
this.#heartbeatIntervalMs = opts.heartbeatIntervalMs ?? envIntervalMs("APPKIT_TELEMETRY_HEARTBEAT_INTERVAL_MS", DEFAULT_HEARTBEAT_INTERVAL_MS);
|
|
30
|
+
this.#metricsFlushIntervalMs = opts.metricsFlushIntervalMs ?? envIntervalMs("APPKIT_TELEMETRY_METRICS_FLUSH_INTERVAL_MS", DEFAULT_METRICS_FLUSH_INTERVAL_MS);
|
|
31
|
+
}
|
|
32
|
+
static initialize(opts) {
|
|
33
|
+
TelemetryReporter.#instance?.stop();
|
|
34
|
+
TelemetryReporter.#instance = new TelemetryReporter(opts);
|
|
35
|
+
return TelemetryReporter.#instance;
|
|
36
|
+
}
|
|
37
|
+
static getInstance() {
|
|
38
|
+
return TelemetryReporter.#instance;
|
|
39
|
+
}
|
|
40
|
+
/** @internal Test-only reset. */
|
|
41
|
+
static _reset() {
|
|
42
|
+
TelemetryReporter.#instance?.stop();
|
|
43
|
+
TelemetryReporter.#instance = null;
|
|
44
|
+
}
|
|
45
|
+
start() {
|
|
46
|
+
if (this.#heartbeatTimer || this.#metricsTimer) return;
|
|
47
|
+
this.#heartbeatTimer = setInterval(() => {
|
|
48
|
+
this.sendHeartbeat().catch(() => {});
|
|
49
|
+
}, this.#heartbeatIntervalMs);
|
|
50
|
+
this.#heartbeatTimer.unref?.();
|
|
51
|
+
this.#metricsTimer = setInterval(() => {
|
|
52
|
+
this.flushRequestMetrics().catch(() => {});
|
|
53
|
+
}, this.#metricsFlushIntervalMs);
|
|
54
|
+
this.#metricsTimer.unref?.();
|
|
55
|
+
}
|
|
56
|
+
stop() {
|
|
57
|
+
if (this.#heartbeatTimer) clearInterval(this.#heartbeatTimer);
|
|
58
|
+
if (this.#metricsTimer) clearInterval(this.#metricsTimer);
|
|
59
|
+
this.#heartbeatTimer = null;
|
|
60
|
+
this.#metricsTimer = null;
|
|
61
|
+
}
|
|
62
|
+
recordRequest(method, routeTemplate, statusCode, latencyMs) {
|
|
63
|
+
if (!routeTemplate) return;
|
|
64
|
+
const key = `${method.toUpperCase()} ${routeTemplate}`;
|
|
65
|
+
const bucket = this.#buckets.get(key) ?? {
|
|
66
|
+
count: 0,
|
|
67
|
+
latencyMsTotal: 0,
|
|
68
|
+
http4xx: 0,
|
|
69
|
+
http5xx: 0
|
|
70
|
+
};
|
|
71
|
+
bucket.count += 1;
|
|
72
|
+
bucket.latencyMsTotal += Math.max(0, latencyMs);
|
|
73
|
+
if (statusCode >= 400 && statusCode < 500) bucket.http4xx += 1;
|
|
74
|
+
if (statusCode >= 500 && statusCode < 600) bucket.http5xx += 1;
|
|
75
|
+
this.#buckets.set(key, bucket);
|
|
76
|
+
}
|
|
77
|
+
async sendStartup() {
|
|
78
|
+
await this.#send([this.#wrap({
|
|
79
|
+
event_name: "APP_STARTUP",
|
|
80
|
+
app_startup_event: {}
|
|
81
|
+
})]);
|
|
82
|
+
}
|
|
83
|
+
async sendHeartbeat() {
|
|
84
|
+
await this.#send([this.#wrap({
|
|
85
|
+
event_name: "HEARTBEAT",
|
|
86
|
+
heartbeat_event: {}
|
|
87
|
+
})]);
|
|
88
|
+
}
|
|
89
|
+
async flushRequestMetrics() {
|
|
90
|
+
if (this.#buckets.size === 0) return;
|
|
91
|
+
const drained = this.#buckets;
|
|
92
|
+
this.#buckets = /* @__PURE__ */ new Map();
|
|
93
|
+
const logs = [];
|
|
94
|
+
for (const [endpoint, bucket] of drained) {
|
|
95
|
+
const event = {
|
|
96
|
+
endpoint,
|
|
97
|
+
request_count: bucket.count,
|
|
98
|
+
request_latency_ms_avg: Math.round(bucket.latencyMsTotal / bucket.count),
|
|
99
|
+
response_count_http4xx: bucket.http4xx,
|
|
100
|
+
response_count_http5xx: bucket.http5xx
|
|
101
|
+
};
|
|
102
|
+
logs.push(this.#wrap({
|
|
103
|
+
event_name: "REQUEST_METRICS",
|
|
104
|
+
request_metrics_event: event
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
await this.#send(logs);
|
|
108
|
+
}
|
|
109
|
+
#wrap(partial) {
|
|
110
|
+
return {
|
|
111
|
+
...partial,
|
|
112
|
+
app_id: this.#appId,
|
|
113
|
+
appkit_version: this.#appkitVersion
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async #send(logs) {
|
|
117
|
+
if (logs.length === 0) return;
|
|
118
|
+
const workspaceId = await this.#workspaceIdPromise;
|
|
119
|
+
await this.#client.apiClient.request({
|
|
120
|
+
path: "/telemetry-ext",
|
|
121
|
+
method: "POST",
|
|
122
|
+
query: { o: workspaceId },
|
|
123
|
+
headers: new Headers(),
|
|
124
|
+
payload: buildAppkitPayload(logs),
|
|
125
|
+
raw: false
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
//#endregion
|
|
131
|
+
export { TelemetryReporter };
|
|
132
|
+
//# sourceMappingURL=reporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporter.js","names":["#instance","#workspaceIdPromise","#client","#appId","#appkitVersion","#heartbeatIntervalMs","#metricsFlushIntervalMs","#heartbeatTimer","#metricsTimer","#buckets","#send","#wrap"],"sources":["../../src/internal-telemetry/reporter.ts"],"sourcesContent":["import type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport {\n type AppkitLog,\n buildAppkitPayload,\n type RequestMetricsEvent,\n} from \"./appkit-log\";\n\nconst DEFAULT_HEARTBEAT_INTERVAL_MS = 5 * 60 * 1000;\nconst DEFAULT_METRICS_FLUSH_INTERVAL_MS = 60 * 1000;\n\ninterface ReporterOptions {\n workspaceId: Promise<string> | string;\n client: WorkspaceClient;\n appId: string;\n appkitVersion: string;\n heartbeatIntervalMs?: number;\n metricsFlushIntervalMs?: number;\n}\n\ninterface RequestBucket {\n count: number;\n latencyMsTotal: number;\n http4xx: number;\n http5xx: number;\n}\n\nfunction envIntervalMs(name: string, fallback: number): number {\n const raw = process.env[name];\n if (!raw) return fallback;\n const n = Number(raw);\n return Number.isFinite(n) && n > 0 ? n : fallback;\n}\n\nexport class TelemetryReporter {\n static #instance: TelemetryReporter | null = null;\n\n readonly #workspaceIdPromise: Promise<string>;\n readonly #client: WorkspaceClient;\n readonly #appId: string;\n readonly #appkitVersion: string;\n readonly #heartbeatIntervalMs: number;\n readonly #metricsFlushIntervalMs: number;\n\n #heartbeatTimer: NodeJS.Timeout | null = null;\n #metricsTimer: NodeJS.Timeout | null = null;\n #buckets: Map<string, RequestBucket> = new Map();\n\n private constructor(opts: ReporterOptions) {\n this.#workspaceIdPromise = Promise.resolve(opts.workspaceId);\n // Mark the rejection (if any) as handled so a misconfigured workspaceId\n // doesn't trigger an unhandled-rejection warning before the first #send\n // awaits it. The original promise still rejects when awaited.\n this.#workspaceIdPromise.catch(() => {});\n this.#client = opts.client;\n this.#appId = opts.appId;\n this.#appkitVersion = opts.appkitVersion;\n this.#heartbeatIntervalMs =\n opts.heartbeatIntervalMs ??\n envIntervalMs(\n \"APPKIT_TELEMETRY_HEARTBEAT_INTERVAL_MS\",\n DEFAULT_HEARTBEAT_INTERVAL_MS,\n );\n this.#metricsFlushIntervalMs =\n opts.metricsFlushIntervalMs ??\n envIntervalMs(\n \"APPKIT_TELEMETRY_METRICS_FLUSH_INTERVAL_MS\",\n DEFAULT_METRICS_FLUSH_INTERVAL_MS,\n );\n }\n\n static initialize(opts: ReporterOptions): TelemetryReporter {\n TelemetryReporter.#instance?.stop();\n TelemetryReporter.#instance = new TelemetryReporter(opts);\n return TelemetryReporter.#instance;\n }\n\n static getInstance(): TelemetryReporter | null {\n return TelemetryReporter.#instance;\n }\n\n /** @internal Test-only reset. */\n static _reset(): void {\n TelemetryReporter.#instance?.stop();\n TelemetryReporter.#instance = null;\n }\n\n start(): void {\n if (this.#heartbeatTimer || this.#metricsTimer) return;\n this.#heartbeatTimer = setInterval(() => {\n this.sendHeartbeat().catch(() => {});\n }, this.#heartbeatIntervalMs);\n this.#heartbeatTimer.unref?.();\n\n this.#metricsTimer = setInterval(() => {\n this.flushRequestMetrics().catch(() => {});\n }, this.#metricsFlushIntervalMs);\n this.#metricsTimer.unref?.();\n }\n\n stop(): void {\n if (this.#heartbeatTimer) clearInterval(this.#heartbeatTimer);\n if (this.#metricsTimer) clearInterval(this.#metricsTimer);\n this.#heartbeatTimer = null;\n this.#metricsTimer = null;\n }\n\n recordRequest(\n method: string,\n routeTemplate: string,\n statusCode: number,\n latencyMs: number,\n ): void {\n if (!routeTemplate) return;\n const key = `${method.toUpperCase()} ${routeTemplate}`;\n const bucket = this.#buckets.get(key) ?? {\n count: 0,\n latencyMsTotal: 0,\n http4xx: 0,\n http5xx: 0,\n };\n bucket.count += 1;\n bucket.latencyMsTotal += Math.max(0, latencyMs);\n if (statusCode >= 400 && statusCode < 500) bucket.http4xx += 1;\n if (statusCode >= 500 && statusCode < 600) bucket.http5xx += 1;\n this.#buckets.set(key, bucket);\n }\n\n async sendStartup(): Promise<void> {\n await this.#send([\n this.#wrap({ event_name: \"APP_STARTUP\", app_startup_event: {} }),\n ]);\n }\n\n async sendHeartbeat(): Promise<void> {\n await this.#send([\n this.#wrap({ event_name: \"HEARTBEAT\", heartbeat_event: {} }),\n ]);\n }\n\n async flushRequestMetrics(): Promise<void> {\n if (this.#buckets.size === 0) return;\n const drained = this.#buckets;\n this.#buckets = new Map();\n\n const logs: AppkitLog[] = [];\n for (const [endpoint, bucket] of drained) {\n const event: RequestMetricsEvent = {\n endpoint,\n request_count: bucket.count,\n request_latency_ms_avg: Math.round(\n bucket.latencyMsTotal / bucket.count,\n ),\n response_count_http4xx: bucket.http4xx,\n response_count_http5xx: bucket.http5xx,\n };\n logs.push(\n this.#wrap({\n event_name: \"REQUEST_METRICS\",\n request_metrics_event: event,\n }),\n );\n }\n await this.#send(logs);\n }\n\n #wrap(partial: AppkitLog): AppkitLog {\n return {\n ...partial,\n app_id: this.#appId,\n appkit_version: this.#appkitVersion,\n };\n }\n\n async #send(logs: AppkitLog[]): Promise<void> {\n if (logs.length === 0) return;\n const workspaceId = await this.#workspaceIdPromise;\n await this.#client.apiClient.request({\n path: \"/telemetry-ext\",\n method: \"POST\",\n query: { o: workspaceId },\n headers: new Headers(),\n payload: buildAppkitPayload(logs),\n raw: false,\n });\n }\n}\n"],"mappings":"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"}
|
package/dist/plugin/index.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ import { ToPlugin } from "../shared/src/plugin.js";
|
|
|
2
2
|
import "../shared/src/index.js";
|
|
3
3
|
import { ExecutionResult } from "./execution-result.js";
|
|
4
4
|
import { Plugin } from "./plugin.js";
|
|
5
|
-
import { toPlugin } from "./to-plugin.js";
|
|
5
|
+
import { NamedPluginFactory, toPlugin } from "./to-plugin.js";
|
package/dist/plugin/plugin.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { BasePlugin, BasePluginConfig, IAppResponse, PluginEndpointMap, PluginPhase, RouteConfig } from "../shared/src/plugin.js";
|
|
2
2
|
import { PluginExecutionSettings, StreamExecuteHandler, StreamExecutionSettings } from "../shared/src/execute.js";
|
|
3
3
|
import "../shared/src/index.js";
|
|
4
|
-
import { CacheManager } from "../cache/index.js";
|
|
5
|
-
import { ITelemetry } from "../telemetry/types.js";
|
|
6
|
-
import "../telemetry/index.js";
|
|
7
4
|
import { ExecutionResult } from "./execution-result.js";
|
|
8
5
|
import { AppManager } from "../app/index.js";
|
|
6
|
+
import { CacheManager } from "../cache/index.js";
|
|
7
|
+
import { PluginContext } from "../core/plugin-context.js";
|
|
9
8
|
import { StreamManager } from "../stream/stream-manager.js";
|
|
10
9
|
import "../stream/index.js";
|
|
10
|
+
import { ITelemetry } from "../telemetry/types.js";
|
|
11
|
+
import "../telemetry/index.js";
|
|
11
12
|
import { DevFileReader } from "./dev-reader.js";
|
|
12
13
|
import express from "express";
|
|
13
14
|
|
|
@@ -103,6 +104,7 @@ declare abstract class Plugin<TConfig extends BasePluginConfig = BasePluginConfi
|
|
|
103
104
|
protected devFileReader: DevFileReader;
|
|
104
105
|
protected streamManager: StreamManager;
|
|
105
106
|
protected telemetry: ITelemetry;
|
|
107
|
+
protected context?: PluginContext;
|
|
106
108
|
/** Registered endpoints for this plugin */
|
|
107
109
|
private registeredEndpoints;
|
|
108
110
|
/** Paths that opt out of JSON body parsing (e.g. file upload routes) */
|
|
@@ -119,6 +121,19 @@ declare abstract class Plugin<TConfig extends BasePluginConfig = BasePluginConfi
|
|
|
119
121
|
*/
|
|
120
122
|
name: string;
|
|
121
123
|
constructor(config: TConfig);
|
|
124
|
+
private tryAttachContext;
|
|
125
|
+
/**
|
|
126
|
+
* Binds runtime dependencies (telemetry provider, cache, plugin context) to
|
|
127
|
+
* this plugin. Called by `AppKit._createApp` after construction and before
|
|
128
|
+
* `setup()`. Idempotent: safe to call if the constructor already bound them
|
|
129
|
+
* eagerly. Kept separate so factories can eagerly construct plugin instances
|
|
130
|
+
* without running this before `TelemetryManager.initialize()` /
|
|
131
|
+
* `CacheManager.getInstance()` have run.
|
|
132
|
+
*/
|
|
133
|
+
attachContext(deps?: {
|
|
134
|
+
context?: unknown;
|
|
135
|
+
telemetryConfig?: BasePluginConfig["telemetry"];
|
|
136
|
+
}): void;
|
|
122
137
|
injectRoutes(_: express.Router): void;
|
|
123
138
|
setup(): Promise<void>;
|
|
124
139
|
getEndpoints(): PluginEndpointMap;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","names":[],"sources":["../../src/plugin/plugin.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","names":[],"sources":["../../src/plugin/plugin.ts"],"mappings":";;;;;;;;;;;;;;;;AAiLA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAAsB,MAAA,iBACJ,gBAAA,GAAmB,gBAAA,aACxB,UAAA;EAAA,UA6BW,MAAA,EAAQ,OAAA;EAAA,UA3BpB,OAAA;EAAA,UACA,KAAA,EAAQ,YAAA;EAAA,UACR,GAAA,EAAK,UAAA;EAAA,UACL,aAAA,EAAe,aAAA;EAAA,UACf,aAAA,EAAe,aAAA;EAAA,UACf,SAAA,EAAY,UAAA;EAAA,UACZ,OAAA,GAAU,aAAA;EA6MR;EAAA,QA1MJ,mBAAA;EA0MD;EAAA,QAvMC,oBAAA;EAmRQ;;;;;;EAAA,OA3QT,KAAA,EAAO,WAAA;EA8QH;;;EAzQX,IAAA;cAEsB,MAAA,EAAQ,OAAA;EAAA,QAoBtB,gBAAA;EAqUQ;;;;;;;;EAhThB,aAAA,CACE,IAAA;IACE,OAAA;IACA,eAAA,GAAkB,gBAAA;EAAA;EAgBtB,YAAA,CAAa,CAAA,EAAG,OAAA,CAAQ,MAAA;EAIlB,KAAA,CAAA,GAAK,OAAA;EAEX,YAAA,CAAA,GAAgB,iBAAA;EAIhB,uBAAA,CAAA,GAA2B,WAAA;EAI3B,qBAAA,CAAA;EA0UkB;;;;;;;;;;;;;;;;;;;;;;;;EA9SlB,OAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiDA,YAAA,CAAA,GAAgB,MAAA;;;;;;;;;;;YAcN,aAAA,CAAc,GAAA,EAAK,OAAA,CAAQ,OAAA;;;;;;;;;;;EAmBrC,MAAA,CAAO,GAAA,EAAK,OAAA,CAAQ,OAAA;;;;;;UAuDZ,uBAAA;EAAA,UAqBQ,aAAA,GAAA,CACd,GAAA,EAAK,YAAA,EACL,EAAA,EAAI,oBAAA,CAAqB,CAAA,GACzB,OAAA,EAAS,uBAAA,EACT,OAAA,YAAgB,OAAA;;;;;;;;;;YAgFF,OAAA,GAAA,CACd,EAAA,GAAK,MAAA,GAAS,WAAA,KAAgB,OAAA,CAAQ,CAAA,GACtC,OAAA,EAAS,uBAAA,EACT,OAAA,YACC,OAAA,CAAQ,eAAA,CAAgB,CAAA;EAAA,UAmDjB,gBAAA,CAAiB,IAAA,UAAc,IAAA;EAAA,UAI/B,KAAA,YAAA,CACR,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAChB,MAAA,EAAQ,WAAA;EAAA,QAeF,qBAAA;EAAA,QAaA,kBAAA;EAAA,QAqCM,wBAAA;EAAA,QAqBN,iBAAA;AAAA"}
|
package/dist/plugin/plugin.js
CHANGED
|
@@ -51,6 +51,7 @@ function hasHttpStatusCode(error) {
|
|
|
51
51
|
const EXCLUDED_FROM_PROXY = new Set([
|
|
52
52
|
"setup",
|
|
53
53
|
"shutdown",
|
|
54
|
+
"attachContext",
|
|
54
55
|
"injectRoutes",
|
|
55
56
|
"getEndpoints",
|
|
56
57
|
"getSkipBodyParsingPaths",
|
|
@@ -149,6 +150,7 @@ var Plugin = class {
|
|
|
149
150
|
devFileReader;
|
|
150
151
|
streamManager;
|
|
151
152
|
telemetry;
|
|
153
|
+
context;
|
|
152
154
|
/** Registered endpoints for this plugin */
|
|
153
155
|
registeredEndpoints = {};
|
|
154
156
|
/** Paths that opt out of JSON body parsing (e.g. file upload routes) */
|
|
@@ -167,11 +169,33 @@ var Plugin = class {
|
|
|
167
169
|
constructor(config) {
|
|
168
170
|
this.config = config;
|
|
169
171
|
this.name = config.name ?? this.constructor.manifest?.name ?? "plugin";
|
|
170
|
-
this.telemetry = TelemetryManager.getProvider(this.name, config.telemetry);
|
|
171
172
|
this.streamManager = new StreamManager();
|
|
172
|
-
this.cache = CacheManager.getInstanceSync();
|
|
173
173
|
this.app = new AppManager();
|
|
174
174
|
this.devFileReader = DevFileReader.getInstance();
|
|
175
|
+
this.context = config.context;
|
|
176
|
+
this.tryAttachContext();
|
|
177
|
+
}
|
|
178
|
+
tryAttachContext() {
|
|
179
|
+
try {
|
|
180
|
+
this.cache = CacheManager.getInstanceSync();
|
|
181
|
+
} catch {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
this.telemetry = TelemetryManager.getProvider(this.name, this.config.telemetry);
|
|
185
|
+
this.isReady = true;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Binds runtime dependencies (telemetry provider, cache, plugin context) to
|
|
189
|
+
* this plugin. Called by `AppKit._createApp` after construction and before
|
|
190
|
+
* `setup()`. Idempotent: safe to call if the constructor already bound them
|
|
191
|
+
* eagerly. Kept separate so factories can eagerly construct plugin instances
|
|
192
|
+
* without running this before `TelemetryManager.initialize()` /
|
|
193
|
+
* `CacheManager.getInstance()` have run.
|
|
194
|
+
*/
|
|
195
|
+
attachContext(deps = {}) {
|
|
196
|
+
if (!this.cache) this.cache = CacheManager.getInstanceSync();
|
|
197
|
+
this.telemetry = TelemetryManager.getProvider(this.name, deps.telemetryConfig ?? this.config.telemetry);
|
|
198
|
+
if (deps.context !== void 0) this.context = deps.context;
|
|
175
199
|
this.isReady = true;
|
|
176
200
|
}
|
|
177
201
|
injectRoutes(_) {}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","names":["otelContext","context"],"sources":["../../src/plugin/plugin.ts"],"sourcesContent":["import { createContextKey, context as otelContext } from \"@opentelemetry/api\";\nimport type express from \"express\";\nimport type {\n BasePlugin,\n BasePluginConfig,\n IAppResponse,\n PluginEndpointMap,\n PluginExecuteConfig,\n PluginExecutionSettings,\n PluginPhase,\n RouteConfig,\n StreamExecuteHandler,\n StreamExecutionSettings,\n} from \"shared\";\nimport { AppManager } from \"../app\";\nimport { CacheManager } from \"../cache\";\nimport {\n getCurrentUserId,\n runInUserContext,\n ServiceContext,\n type UserContext,\n} from \"../context\";\nimport { AppKitError, AuthenticationError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\nimport { StreamManager } from \"../stream\";\nimport {\n type ITelemetry,\n normalizeTelemetryOptions,\n TelemetryManager,\n} from \"../telemetry\";\nimport { deepMerge } from \"../utils\";\nimport { DevFileReader } from \"./dev-reader\";\nimport type { ExecutionResult } from \"./execution-result\";\nimport { CacheInterceptor } from \"./interceptors/cache\";\nimport { RetryInterceptor } from \"./interceptors/retry\";\nimport { TelemetryInterceptor } from \"./interceptors/telemetry\";\nimport { TimeoutInterceptor } from \"./interceptors/timeout\";\nimport type {\n ExecutionInterceptor,\n InterceptorContext,\n} from \"./interceptors/types\";\n\nconst logger = createLogger(\"plugin\");\n\n/**\n * OTel context key for marking OBO dev mode fallback.\n * Set when asUser() is called in development mode without a user token.\n */\nconst DEV_OBO_FALLBACK_KEY = createContextKey(\"appkit.devOboFallback\");\n\n/**\n * Returns true if the current execution is an OBO dev mode fallback\n * (asUser() was called but fell back to service principal due to missing token).\n */\nexport function isDevOboFallback(): boolean {\n return otelContext.active().getValue(DEV_OBO_FALLBACK_KEY) === true;\n}\n\n/**\n * Narrow an unknown thrown value to an Error that carries a numeric\n * `statusCode` property (e.g. `ApiError` from `@databricks/sdk-experimental`).\n */\nfunction hasHttpStatusCode(\n error: unknown,\n): error is Error & { statusCode: number } {\n return (\n error instanceof Error &&\n \"statusCode\" in error &&\n typeof (error as Record<string, unknown>).statusCode === \"number\"\n );\n}\n\n/**\n * Methods that should not be proxied by asUser().\n * These are lifecycle/internal methods that don't make sense\n * to execute in a user context.\n */\nconst EXCLUDED_FROM_PROXY = new Set([\n // Lifecycle methods\n \"setup\",\n \"shutdown\",\n \"injectRoutes\",\n \"getEndpoints\",\n \"getSkipBodyParsingPaths\",\n \"abortActiveOperations\",\n \"clientConfig\",\n // asUser itself - prevent chaining like .asUser().asUser()\n \"asUser\",\n // Internal methods\n \"constructor\",\n]);\n\n/**\n * Base abstract class for creating AppKit plugins.\n *\n * All plugins must declare a static `manifest` property with their metadata\n * and resource requirements. The manifest defines:\n * - `required` resources: Always needed for the plugin to function\n * - `optional` resources: May be needed depending on plugin configuration\n *\n * ## Static vs Runtime Resource Requirements\n *\n * The manifest is static and doesn't know the plugin's runtime configuration.\n * For resources that become required based on config options, plugins can\n * implement a static `getResourceRequirements(config)` method.\n *\n * At runtime, this method is called with the actual config to determine\n * which \"optional\" resources should be treated as \"required\".\n *\n * @example Basic plugin with static requirements\n * ```typescript\n * import { Plugin, toPlugin, PluginManifest, ResourceType } from '@databricks/appkit';\n *\n * const myManifest: PluginManifest = {\n * name: 'myPlugin',\n * displayName: 'My Plugin',\n * description: 'Does something awesome',\n * resources: {\n * required: [\n * { type: ResourceType.SQL_WAREHOUSE, alias: 'warehouse', ... }\n * ],\n * optional: []\n * }\n * };\n *\n * class MyPlugin extends Plugin<MyConfig> {\n * static manifest = myManifest;\n * }\n * ```\n *\n * @example Plugin with config-dependent resources\n * ```typescript\n * interface MyConfig extends BasePluginConfig {\n * enableCaching?: boolean;\n * }\n *\n * const myManifest: PluginManifest = {\n * name: 'myPlugin',\n * resources: {\n * required: [\n * { type: ResourceType.SQL_WAREHOUSE, alias: 'warehouse', ... }\n * ],\n * optional: [\n * // Database is optional in the static manifest\n * { type: ResourceType.DATABASE, alias: 'cache', description: 'Required if caching enabled', ... }\n * ]\n * }\n * };\n *\n * class MyPlugin extends Plugin<MyConfig> {\n * static manifest = myManifest<\"myPlugin\">;\n *\n * // Runtime method: converts optional resources to required based on config\n * static getResourceRequirements(config: MyConfig) {\n * const resources = [];\n * if (config.enableCaching) {\n * // When caching is enabled, Database becomes required\n * resources.push({\n * type: ResourceType.DATABASE,\n * alias: 'cache',\n * resourceKey: 'database',\n * description: 'Cache storage for query results',\n * permission: 'CAN_CONNECT_AND_CREATE',\n * fields: {\n * instance_name: { env: 'DATABRICKS_CACHE_INSTANCE' },\n * database_name: { env: 'DATABRICKS_CACHE_DB' },\n * },\n * required: true // Mark as required at runtime\n * });\n * }\n * return resources;\n * }\n * }\n * ```\n */\nexport abstract class Plugin<\n TConfig extends BasePluginConfig = BasePluginConfig,\n> implements BasePlugin\n{\n protected isReady = false;\n protected cache: CacheManager;\n protected app: AppManager;\n protected devFileReader: DevFileReader;\n protected streamManager: StreamManager;\n protected telemetry: ITelemetry;\n\n /** Registered endpoints for this plugin */\n private registeredEndpoints: PluginEndpointMap = {};\n\n /** Paths that opt out of JSON body parsing (e.g. file upload routes) */\n private skipBodyParsingPaths: Set<string> = new Set();\n\n /**\n * Plugin initialization phase.\n * - 'core': Initialized first (e.g., config plugins)\n * - 'normal': Initialized second (most plugins)\n * - 'deferred': Initialized last (e.g., server plugin)\n */\n static phase: PluginPhase = \"normal\";\n\n /**\n * Plugin name identifier.\n */\n name: string;\n\n constructor(protected config: TConfig) {\n this.name =\n config.name ??\n (this.constructor as { manifest?: { name: string } }).manifest?.name ??\n \"plugin\";\n this.telemetry = TelemetryManager.getProvider(this.name, config.telemetry);\n this.streamManager = new StreamManager();\n this.cache = CacheManager.getInstanceSync();\n this.app = new AppManager();\n this.devFileReader = DevFileReader.getInstance();\n\n this.isReady = true;\n }\n\n injectRoutes(_: express.Router) {\n return;\n }\n\n async setup() {}\n\n getEndpoints(): PluginEndpointMap {\n return this.registeredEndpoints;\n }\n\n getSkipBodyParsingPaths(): ReadonlySet<string> {\n return this.skipBodyParsingPaths;\n }\n\n abortActiveOperations(): void {\n this.streamManager.abortAll();\n }\n\n /**\n * Returns the public exports for this plugin.\n * Override this to define a custom public API.\n * By default, returns an empty object.\n *\n * The returned object becomes the plugin's public API on the AppKit instance\n * (e.g. `appkit.myPlugin.method()`). AppKit automatically binds method context\n * and adds `asUser(req)` for user-scoped execution.\n *\n * @example\n * ```ts\n * class MyPlugin extends Plugin {\n * private getData() { return []; }\n *\n * exports() {\n * return { getData: this.getData };\n * }\n * }\n *\n * // After registration:\n * const appkit = await createApp({ plugins: [myPlugin()] });\n * appkit.myPlugin.getData();\n * ```\n */\n exports(): unknown {\n return {};\n }\n\n /**\n * Returns startup config to expose to the client.\n * Override this to surface server-side values that are safe to publish to the\n * frontend, such as feature flags, resource IDs, or other app boot settings.\n *\n * This runs once when the server starts, so it should not depend on\n * request-scoped or user-specific state.\n *\n * String values that match non-public environment variables are redacted\n * unless you intentionally expose them via a matching `PUBLIC_APPKIT_` env var.\n *\n * Values must be JSON-serializable plain data (no functions, Dates, classes,\n * Maps, Sets, BigInts, or circular references).\n * By default returns an empty object (plugin contributes nothing to client config).\n *\n * On the client, read the config with the `usePluginClientConfig` hook\n * (React) or the `getPluginClientConfig` function (vanilla JS), both\n * from `@databricks/appkit-ui`.\n *\n * @example\n * ```ts\n * // Server — plugin definition\n * class MyPlugin extends Plugin<MyConfig> {\n * clientConfig() {\n * return {\n * warehouseId: this.config.warehouseId,\n * features: { darkMode: true },\n * };\n * }\n * }\n *\n * // Client — React component\n * import { usePluginClientConfig } from \"@databricks/appkit-ui/react\";\n *\n * interface MyPluginConfig { warehouseId: string; features: { darkMode: boolean } }\n *\n * const config = usePluginClientConfig<MyPluginConfig>(\"myPlugin\");\n * config.warehouseId; // \"abc-123\"\n *\n * // Client — vanilla JS\n * import { getPluginClientConfig } from \"@databricks/appkit-ui/js\";\n *\n * const config = getPluginClientConfig<MyPluginConfig>(\"myPlugin\");\n * ```\n */\n clientConfig(): Record<string, unknown> {\n return {};\n }\n\n /**\n * Resolve the effective user ID from a request.\n *\n * Returns the `x-forwarded-user` header when present. In development mode\n * (`NODE_ENV=development`) falls back to the current context user ID so\n * that callers outside an active `runInUserContext` scope still get a\n * consistent value.\n *\n * @throws AuthenticationError in production when no user header is present.\n */\n protected resolveUserId(req: express.Request): string {\n const userId = req.header(\"x-forwarded-user\");\n if (userId) return userId;\n if (process.env.NODE_ENV === \"development\") return getCurrentUserId();\n throw AuthenticationError.missingToken(\n \"Missing x-forwarded-user header. Cannot resolve user ID.\",\n );\n }\n\n /**\n * Execute operations using the user's identity from the request.\n * Returns a proxy of this plugin where all method calls execute\n * with the user's Databricks credentials instead of the service principal.\n *\n * @param req - The Express request containing the user token in headers\n * @returns A proxied plugin instance that executes as the user\n * @throws AuthenticationError if user token is not available in request headers (production only).\n * In development mode (`NODE_ENV=development`), skips user impersonation instead of throwing.\n */\n asUser(req: express.Request): this {\n const token = req.header(\"x-forwarded-access-token\");\n const userId = req.header(\"x-forwarded-user\");\n const isDev = process.env.NODE_ENV === \"development\";\n\n // In local development, skip user impersonation\n // since there's no user token available\n if (!token && isDev) {\n logger.warn(\n \"asUser() called without user token in development mode. Skipping user impersonation.\",\n );\n\n // Return a proxy that marks execution as OBO dev fallback via OTel context,\n // so telemetry spans can distinguish intended OBO calls from regular SP calls\n return new Proxy(this, {\n get: (target, prop, receiver) => {\n const value = Reflect.get(target, prop, receiver);\n if (typeof value !== \"function\") return value;\n if (typeof prop === \"string\" && EXCLUDED_FROM_PROXY.has(prop))\n return value;\n\n return (...args: unknown[]) => {\n const ctx = otelContext\n .active()\n .setValue(DEV_OBO_FALLBACK_KEY, true);\n return otelContext.with(ctx, () => value.apply(target, args));\n };\n },\n }) as this;\n }\n\n if (!token) {\n throw AuthenticationError.missingToken(\"user token\");\n }\n\n if (!userId && !isDev) {\n throw AuthenticationError.missingUserId();\n }\n\n const effectiveUserId = userId || \"dev-user\";\n\n const userContext = ServiceContext.createUserContext(\n token,\n effectiveUserId,\n );\n\n // Return a proxy that wraps method calls in user context\n return this._createUserContextProxy(userContext);\n }\n\n /**\n * Creates a proxy that wraps method calls in a user context.\n * This allows all plugin methods to automatically use the user's\n * Databricks credentials.\n */\n private _createUserContextProxy(userContext: UserContext): this {\n return new Proxy(this, {\n get: (target, prop, receiver) => {\n const value = Reflect.get(target, prop, receiver);\n\n if (typeof value !== \"function\") {\n return value;\n }\n\n if (typeof prop === \"string\" && EXCLUDED_FROM_PROXY.has(prop)) {\n return value;\n }\n\n return (...args: unknown[]) => {\n return runInUserContext(userContext, () => value.apply(target, args));\n };\n },\n }) as this;\n }\n\n // streaming execution with interceptors\n protected async executeStream<T>(\n res: IAppResponse,\n fn: StreamExecuteHandler<T>,\n options: StreamExecutionSettings,\n userKey?: string,\n ) {\n // destructure options\n const {\n stream: streamConfig,\n default: defaultConfig,\n user: userConfig,\n } = options;\n\n // build execution options\n const executeConfig = this._buildExecutionConfig({\n default: defaultConfig,\n user: userConfig,\n });\n\n // get user key from context if not provided\n const effectiveUserKey = userKey ?? getCurrentUserId();\n\n const self = this;\n // capture the active OTel context (HTTP span) before entering the async generator,\n // where it would otherwise be lost across the async boundary\n const parentOtelContext = otelContext.active();\n\n // wrapper function to ensure it returns a generator\n const asyncWrapperFn = async function* (streamSignal?: AbortSignal) {\n // build execution context\n const context: InterceptorContext = {\n signal: streamSignal,\n metadata: new Map(),\n userKey: effectiveUserKey,\n };\n\n // build interceptors\n const interceptors = self._buildInterceptors(executeConfig);\n\n // wrap the function to ensure it returns a promise\n const wrappedFn = async () => {\n const result = await fn(context.signal);\n return result;\n };\n\n // execute the function with interceptors, restoring the parent OTel context\n // so telemetry spans are linked as children of the HTTP request span\n const result = await otelContext.with(parentOtelContext, () =>\n self._executeWithInterceptors(\n wrappedFn as (signal?: AbortSignal) => Promise<T>,\n interceptors,\n context,\n ),\n );\n\n // check if result is a generator\n if (self._checkIfGenerator(result)) {\n yield* result;\n } else {\n yield result;\n }\n };\n\n // stream the result to the client. The effective user key is forwarded\n // to the stream manager so that reconnections to existing streamIds are\n // bound to the original creator (prevents cross-user stream takeover via\n // guessed/leaked IDs).\n await this.streamManager.stream(\n res,\n asyncWrapperFn,\n streamConfig,\n effectiveUserKey,\n );\n }\n\n /**\n * Execute a function with the plugin's interceptor chain.\n *\n * Returns an {@link ExecutionResult} discriminated union:\n * - `{ ok: true, data: T }` on success\n * - `{ ok: false, status: number, message: string }` on failure\n *\n * Errors are never thrown — the method is production-safe.\n */\n protected async execute<T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n options: PluginExecutionSettings,\n userKey?: string,\n ): Promise<ExecutionResult<T>> {\n const executeConfig = this._buildExecutionConfig(options);\n\n const interceptors = this._buildInterceptors(executeConfig);\n\n // get user key from context if not provided\n const effectiveUserKey = userKey ?? getCurrentUserId();\n\n const context: InterceptorContext = {\n metadata: new Map(),\n userKey: effectiveUserKey,\n };\n\n try {\n const data = await this._executeWithInterceptors(\n fn,\n interceptors,\n context,\n );\n return { ok: true, data };\n } catch (error) {\n logger.error(\"Plugin execution failed\", { error, plugin: this.name });\n\n if (error instanceof AppKitError) {\n return {\n ok: false,\n status: error.statusCode,\n message: error.message,\n };\n }\n\n if (hasHttpStatusCode(error)) {\n const isDev = process.env.NODE_ENV !== \"production\";\n const isClientError = error.statusCode >= 400 && error.statusCode < 500;\n return {\n ok: false,\n status: error.statusCode,\n message: isDev || isClientError ? error.message : \"Server error\",\n };\n }\n\n const isDev = process.env.NODE_ENV !== \"production\";\n return {\n ok: false,\n status: 500,\n message:\n isDev && error instanceof Error ? error.message : \"Server error\",\n };\n }\n }\n\n protected registerEndpoint(name: string, path: string): void {\n this.registeredEndpoints[name] = path;\n }\n\n protected route<_TResponse>(\n router: express.Router,\n config: RouteConfig,\n ): void {\n const { name, method, path, handler } = config;\n\n router[method](path, handler);\n\n const fullPath = `/api/${this.name}${path}`;\n this.registerEndpoint(name, fullPath);\n\n if (config.skipBodyParsing) {\n this.skipBodyParsingPaths.add(fullPath);\n }\n }\n\n // build execution options by merging defaults, plugin config, and user overrides\n private _buildExecutionConfig(\n options: PluginExecutionSettings,\n ): PluginExecuteConfig {\n const { default: methodDefaults, user: userOverride } = options;\n\n // Merge: method defaults <- plugin config <- user override (highest priority)\n return deepMerge(\n deepMerge(methodDefaults, this.config),\n userOverride ?? {},\n ) as PluginExecuteConfig;\n }\n\n // build interceptors based on execute options\n private _buildInterceptors(\n options: PluginExecuteConfig,\n ): ExecutionInterceptor[] {\n const interceptors: ExecutionInterceptor[] = [];\n\n // order matters: telemetry → timeout → retry → cache (innermost to outermost)\n\n const telemetryConfig = normalizeTelemetryOptions(this.config.telemetry);\n if (\n telemetryConfig.traces &&\n (options.telemetryInterceptor?.enabled ?? true)\n ) {\n interceptors.push(\n new TelemetryInterceptor(this.telemetry, options.telemetryInterceptor),\n );\n }\n\n if (options.timeout && options.timeout > 0) {\n interceptors.push(new TimeoutInterceptor(options.timeout));\n }\n\n if (\n options.retry?.enabled &&\n options.retry.attempts &&\n options.retry.attempts > 1\n ) {\n interceptors.push(new RetryInterceptor(options.retry));\n }\n\n if (options.cache?.enabled && options.cache.cacheKey?.length) {\n interceptors.push(new CacheInterceptor(this.cache, options.cache));\n }\n\n return interceptors;\n }\n\n // execute method wrapped with interceptors\n private async _executeWithInterceptors<T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n interceptors: ExecutionInterceptor[],\n context: InterceptorContext,\n ): Promise<T> {\n // no interceptors, execute directly\n if (interceptors.length === 0) {\n return fn(context.signal);\n }\n // build nested execution chain from interceptors\n let wrappedFn = () => fn(context.signal);\n\n // wrap each interceptor around the previous function\n for (const interceptor of interceptors) {\n const previousFn = wrappedFn;\n wrappedFn = () => interceptor.intercept(previousFn, context);\n }\n\n return wrappedFn();\n }\n\n private _checkIfGenerator(\n result: any,\n ): result is AsyncGenerator<any, void, unknown> {\n return (\n result && typeof result === \"object\" && Symbol.asyncIterator in result\n );\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.js","names":["otelContext","context"],"sources":["../../src/plugin/plugin.ts"],"sourcesContent":["import { createContextKey, context as otelContext } from \"@opentelemetry/api\";\nimport type express from \"express\";\nimport type {\n BasePlugin,\n BasePluginConfig,\n IAppResponse,\n PluginEndpointMap,\n PluginExecuteConfig,\n PluginExecutionSettings,\n PluginPhase,\n RouteConfig,\n StreamExecuteHandler,\n StreamExecutionSettings,\n} from \"shared\";\nimport { AppManager } from \"../app\";\nimport { CacheManager } from \"../cache\";\nimport {\n getCurrentUserId,\n runInUserContext,\n ServiceContext,\n type UserContext,\n} from \"../context\";\nimport type { PluginContext } from \"../core/plugin-context\";\nimport { AppKitError, AuthenticationError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\nimport { StreamManager } from \"../stream\";\nimport {\n type ITelemetry,\n normalizeTelemetryOptions,\n TelemetryManager,\n} from \"../telemetry\";\nimport { deepMerge } from \"../utils\";\nimport { DevFileReader } from \"./dev-reader\";\nimport type { ExecutionResult } from \"./execution-result\";\nimport { CacheInterceptor } from \"./interceptors/cache\";\nimport { RetryInterceptor } from \"./interceptors/retry\";\nimport { TelemetryInterceptor } from \"./interceptors/telemetry\";\nimport { TimeoutInterceptor } from \"./interceptors/timeout\";\nimport type {\n ExecutionInterceptor,\n InterceptorContext,\n} from \"./interceptors/types\";\n\nconst logger = createLogger(\"plugin\");\n\n/**\n * OTel context key for marking OBO dev mode fallback.\n * Set when asUser() is called in development mode without a user token.\n */\nconst DEV_OBO_FALLBACK_KEY = createContextKey(\"appkit.devOboFallback\");\n\n/**\n * Returns true if the current execution is an OBO dev mode fallback\n * (asUser() was called but fell back to service principal due to missing token).\n */\nexport function isDevOboFallback(): boolean {\n return otelContext.active().getValue(DEV_OBO_FALLBACK_KEY) === true;\n}\n\n/**\n * Narrow an unknown thrown value to an Error that carries a numeric\n * `statusCode` property (e.g. `ApiError` from `@databricks/sdk-experimental`).\n */\nfunction hasHttpStatusCode(\n error: unknown,\n): error is Error & { statusCode: number } {\n return (\n error instanceof Error &&\n \"statusCode\" in error &&\n typeof (error as Record<string, unknown>).statusCode === \"number\"\n );\n}\n\n/**\n * Methods that should not be proxied by asUser().\n * These are lifecycle/internal methods that don't make sense\n * to execute in a user context.\n */\nconst EXCLUDED_FROM_PROXY = new Set([\n // Lifecycle methods\n \"setup\",\n \"shutdown\",\n \"attachContext\",\n \"injectRoutes\",\n \"getEndpoints\",\n \"getSkipBodyParsingPaths\",\n \"abortActiveOperations\",\n \"clientConfig\",\n // asUser itself - prevent chaining like .asUser().asUser()\n \"asUser\",\n // Internal methods\n \"constructor\",\n]);\n\n/**\n * Base abstract class for creating AppKit plugins.\n *\n * All plugins must declare a static `manifest` property with their metadata\n * and resource requirements. The manifest defines:\n * - `required` resources: Always needed for the plugin to function\n * - `optional` resources: May be needed depending on plugin configuration\n *\n * ## Static vs Runtime Resource Requirements\n *\n * The manifest is static and doesn't know the plugin's runtime configuration.\n * For resources that become required based on config options, plugins can\n * implement a static `getResourceRequirements(config)` method.\n *\n * At runtime, this method is called with the actual config to determine\n * which \"optional\" resources should be treated as \"required\".\n *\n * @example Basic plugin with static requirements\n * ```typescript\n * import { Plugin, toPlugin, PluginManifest, ResourceType } from '@databricks/appkit';\n *\n * const myManifest: PluginManifest = {\n * name: 'myPlugin',\n * displayName: 'My Plugin',\n * description: 'Does something awesome',\n * resources: {\n * required: [\n * { type: ResourceType.SQL_WAREHOUSE, alias: 'warehouse', ... }\n * ],\n * optional: []\n * }\n * };\n *\n * class MyPlugin extends Plugin<MyConfig> {\n * static manifest = myManifest;\n * }\n * ```\n *\n * @example Plugin with config-dependent resources\n * ```typescript\n * interface MyConfig extends BasePluginConfig {\n * enableCaching?: boolean;\n * }\n *\n * const myManifest: PluginManifest = {\n * name: 'myPlugin',\n * resources: {\n * required: [\n * { type: ResourceType.SQL_WAREHOUSE, alias: 'warehouse', ... }\n * ],\n * optional: [\n * // Database is optional in the static manifest\n * { type: ResourceType.DATABASE, alias: 'cache', description: 'Required if caching enabled', ... }\n * ]\n * }\n * };\n *\n * class MyPlugin extends Plugin<MyConfig> {\n * static manifest = myManifest<\"myPlugin\">;\n *\n * // Runtime method: converts optional resources to required based on config\n * static getResourceRequirements(config: MyConfig) {\n * const resources = [];\n * if (config.enableCaching) {\n * // When caching is enabled, Database becomes required\n * resources.push({\n * type: ResourceType.DATABASE,\n * alias: 'cache',\n * resourceKey: 'database',\n * description: 'Cache storage for query results',\n * permission: 'CAN_CONNECT_AND_CREATE',\n * fields: {\n * instance_name: { env: 'DATABRICKS_CACHE_INSTANCE' },\n * database_name: { env: 'DATABRICKS_CACHE_DB' },\n * },\n * required: true // Mark as required at runtime\n * });\n * }\n * return resources;\n * }\n * }\n * ```\n */\nexport abstract class Plugin<\n TConfig extends BasePluginConfig = BasePluginConfig,\n> implements BasePlugin\n{\n protected isReady = false;\n protected cache!: CacheManager;\n protected app: AppManager;\n protected devFileReader: DevFileReader;\n protected streamManager: StreamManager;\n protected telemetry!: ITelemetry;\n protected context?: PluginContext;\n\n /** Registered endpoints for this plugin */\n private registeredEndpoints: PluginEndpointMap = {};\n\n /** Paths that opt out of JSON body parsing (e.g. file upload routes) */\n private skipBodyParsingPaths: Set<string> = new Set();\n\n /**\n * Plugin initialization phase.\n * - 'core': Initialized first (e.g., config plugins)\n * - 'normal': Initialized second (most plugins)\n * - 'deferred': Initialized last (e.g., server plugin)\n */\n static phase: PluginPhase = \"normal\";\n\n /**\n * Plugin name identifier.\n */\n name: string;\n\n constructor(protected config: TConfig) {\n this.name =\n config.name ??\n (this.constructor as { manifest?: { name: string } }).manifest?.name ??\n \"plugin\";\n this.streamManager = new StreamManager();\n this.app = new AppManager();\n this.devFileReader = DevFileReader.getInstance();\n this.context = (config as Record<string, unknown>).context as\n | PluginContext\n | undefined;\n\n // Eagerly bind telemetry + cache if the core services have already been\n // initialized (normal createApp path, or tests that mock CacheManager).\n // If they haven't, we leave these undefined and rely on `attachContext`\n // being called later — this lets factories eagerly construct plugin\n // instances at module top-level before `createApp` has run.\n this.tryAttachContext();\n }\n\n private tryAttachContext(): void {\n try {\n this.cache = CacheManager.getInstanceSync();\n } catch {\n return;\n }\n this.telemetry = TelemetryManager.getProvider(\n this.name,\n this.config.telemetry,\n );\n this.isReady = true;\n }\n\n /**\n * Binds runtime dependencies (telemetry provider, cache, plugin context) to\n * this plugin. Called by `AppKit._createApp` after construction and before\n * `setup()`. Idempotent: safe to call if the constructor already bound them\n * eagerly. Kept separate so factories can eagerly construct plugin instances\n * without running this before `TelemetryManager.initialize()` /\n * `CacheManager.getInstance()` have run.\n */\n attachContext(\n deps: {\n context?: unknown;\n telemetryConfig?: BasePluginConfig[\"telemetry\"];\n } = {},\n ): void {\n if (!this.cache) {\n this.cache = CacheManager.getInstanceSync();\n }\n this.telemetry = TelemetryManager.getProvider(\n this.name,\n deps.telemetryConfig ?? this.config.telemetry,\n );\n if (deps.context !== undefined) {\n this.context = deps.context as PluginContext;\n }\n this.isReady = true;\n }\n\n injectRoutes(_: express.Router) {\n return;\n }\n\n async setup() {}\n\n getEndpoints(): PluginEndpointMap {\n return this.registeredEndpoints;\n }\n\n getSkipBodyParsingPaths(): ReadonlySet<string> {\n return this.skipBodyParsingPaths;\n }\n\n abortActiveOperations(): void {\n this.streamManager.abortAll();\n }\n\n /**\n * Returns the public exports for this plugin.\n * Override this to define a custom public API.\n * By default, returns an empty object.\n *\n * The returned object becomes the plugin's public API on the AppKit instance\n * (e.g. `appkit.myPlugin.method()`). AppKit automatically binds method context\n * and adds `asUser(req)` for user-scoped execution.\n *\n * @example\n * ```ts\n * class MyPlugin extends Plugin {\n * private getData() { return []; }\n *\n * exports() {\n * return { getData: this.getData };\n * }\n * }\n *\n * // After registration:\n * const appkit = await createApp({ plugins: [myPlugin()] });\n * appkit.myPlugin.getData();\n * ```\n */\n exports(): unknown {\n return {};\n }\n\n /**\n * Returns startup config to expose to the client.\n * Override this to surface server-side values that are safe to publish to the\n * frontend, such as feature flags, resource IDs, or other app boot settings.\n *\n * This runs once when the server starts, so it should not depend on\n * request-scoped or user-specific state.\n *\n * String values that match non-public environment variables are redacted\n * unless you intentionally expose them via a matching `PUBLIC_APPKIT_` env var.\n *\n * Values must be JSON-serializable plain data (no functions, Dates, classes,\n * Maps, Sets, BigInts, or circular references).\n * By default returns an empty object (plugin contributes nothing to client config).\n *\n * On the client, read the config with the `usePluginClientConfig` hook\n * (React) or the `getPluginClientConfig` function (vanilla JS), both\n * from `@databricks/appkit-ui`.\n *\n * @example\n * ```ts\n * // Server — plugin definition\n * class MyPlugin extends Plugin<MyConfig> {\n * clientConfig() {\n * return {\n * warehouseId: this.config.warehouseId,\n * features: { darkMode: true },\n * };\n * }\n * }\n *\n * // Client — React component\n * import { usePluginClientConfig } from \"@databricks/appkit-ui/react\";\n *\n * interface MyPluginConfig { warehouseId: string; features: { darkMode: boolean } }\n *\n * const config = usePluginClientConfig<MyPluginConfig>(\"myPlugin\");\n * config.warehouseId; // \"abc-123\"\n *\n * // Client — vanilla JS\n * import { getPluginClientConfig } from \"@databricks/appkit-ui/js\";\n *\n * const config = getPluginClientConfig<MyPluginConfig>(\"myPlugin\");\n * ```\n */\n clientConfig(): Record<string, unknown> {\n return {};\n }\n\n /**\n * Resolve the effective user ID from a request.\n *\n * Returns the `x-forwarded-user` header when present. In development mode\n * (`NODE_ENV=development`) falls back to the current context user ID so\n * that callers outside an active `runInUserContext` scope still get a\n * consistent value.\n *\n * @throws AuthenticationError in production when no user header is present.\n */\n protected resolveUserId(req: express.Request): string {\n const userId = req.header(\"x-forwarded-user\");\n if (userId) return userId;\n if (process.env.NODE_ENV === \"development\") return getCurrentUserId();\n throw AuthenticationError.missingToken(\n \"Missing x-forwarded-user header. Cannot resolve user ID.\",\n );\n }\n\n /**\n * Execute operations using the user's identity from the request.\n * Returns a proxy of this plugin where all method calls execute\n * with the user's Databricks credentials instead of the service principal.\n *\n * @param req - The Express request containing the user token in headers\n * @returns A proxied plugin instance that executes as the user\n * @throws AuthenticationError if user token is not available in request headers (production only).\n * In development mode (`NODE_ENV=development`), skips user impersonation instead of throwing.\n */\n asUser(req: express.Request): this {\n const token = req.header(\"x-forwarded-access-token\");\n const userId = req.header(\"x-forwarded-user\");\n const isDev = process.env.NODE_ENV === \"development\";\n\n // In local development, skip user impersonation\n // since there's no user token available\n if (!token && isDev) {\n logger.warn(\n \"asUser() called without user token in development mode. Skipping user impersonation.\",\n );\n\n // Return a proxy that marks execution as OBO dev fallback via OTel context,\n // so telemetry spans can distinguish intended OBO calls from regular SP calls\n return new Proxy(this, {\n get: (target, prop, receiver) => {\n const value = Reflect.get(target, prop, receiver);\n if (typeof value !== \"function\") return value;\n if (typeof prop === \"string\" && EXCLUDED_FROM_PROXY.has(prop))\n return value;\n\n return (...args: unknown[]) => {\n const ctx = otelContext\n .active()\n .setValue(DEV_OBO_FALLBACK_KEY, true);\n return otelContext.with(ctx, () => value.apply(target, args));\n };\n },\n }) as this;\n }\n\n if (!token) {\n throw AuthenticationError.missingToken(\"user token\");\n }\n\n if (!userId && !isDev) {\n throw AuthenticationError.missingUserId();\n }\n\n const effectiveUserId = userId || \"dev-user\";\n\n const userContext = ServiceContext.createUserContext(\n token,\n effectiveUserId,\n );\n\n // Return a proxy that wraps method calls in user context\n return this._createUserContextProxy(userContext);\n }\n\n /**\n * Creates a proxy that wraps method calls in a user context.\n * This allows all plugin methods to automatically use the user's\n * Databricks credentials.\n */\n private _createUserContextProxy(userContext: UserContext): this {\n return new Proxy(this, {\n get: (target, prop, receiver) => {\n const value = Reflect.get(target, prop, receiver);\n\n if (typeof value !== \"function\") {\n return value;\n }\n\n if (typeof prop === \"string\" && EXCLUDED_FROM_PROXY.has(prop)) {\n return value;\n }\n\n return (...args: unknown[]) => {\n return runInUserContext(userContext, () => value.apply(target, args));\n };\n },\n }) as this;\n }\n\n // streaming execution with interceptors\n protected async executeStream<T>(\n res: IAppResponse,\n fn: StreamExecuteHandler<T>,\n options: StreamExecutionSettings,\n userKey?: string,\n ) {\n // destructure options\n const {\n stream: streamConfig,\n default: defaultConfig,\n user: userConfig,\n } = options;\n\n // build execution options\n const executeConfig = this._buildExecutionConfig({\n default: defaultConfig,\n user: userConfig,\n });\n\n // get user key from context if not provided\n const effectiveUserKey = userKey ?? getCurrentUserId();\n\n const self = this;\n // capture the active OTel context (HTTP span) before entering the async generator,\n // where it would otherwise be lost across the async boundary\n const parentOtelContext = otelContext.active();\n\n // wrapper function to ensure it returns a generator\n const asyncWrapperFn = async function* (streamSignal?: AbortSignal) {\n // build execution context\n const context: InterceptorContext = {\n signal: streamSignal,\n metadata: new Map(),\n userKey: effectiveUserKey,\n };\n\n // build interceptors\n const interceptors = self._buildInterceptors(executeConfig);\n\n // wrap the function to ensure it returns a promise\n const wrappedFn = async () => {\n const result = await fn(context.signal);\n return result;\n };\n\n // execute the function with interceptors, restoring the parent OTel context\n // so telemetry spans are linked as children of the HTTP request span\n const result = await otelContext.with(parentOtelContext, () =>\n self._executeWithInterceptors(\n wrappedFn as (signal?: AbortSignal) => Promise<T>,\n interceptors,\n context,\n ),\n );\n\n // check if result is a generator\n if (self._checkIfGenerator(result)) {\n yield* result;\n } else {\n yield result;\n }\n };\n\n // stream the result to the client. The effective user key is forwarded\n // to the stream manager so that reconnections to existing streamIds are\n // bound to the original creator (prevents cross-user stream takeover via\n // guessed/leaked IDs).\n await this.streamManager.stream(\n res,\n asyncWrapperFn,\n streamConfig,\n effectiveUserKey,\n );\n }\n\n /**\n * Execute a function with the plugin's interceptor chain.\n *\n * Returns an {@link ExecutionResult} discriminated union:\n * - `{ ok: true, data: T }` on success\n * - `{ ok: false, status: number, message: string }` on failure\n *\n * Errors are never thrown — the method is production-safe.\n */\n protected async execute<T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n options: PluginExecutionSettings,\n userKey?: string,\n ): Promise<ExecutionResult<T>> {\n const executeConfig = this._buildExecutionConfig(options);\n\n const interceptors = this._buildInterceptors(executeConfig);\n\n // get user key from context if not provided\n const effectiveUserKey = userKey ?? getCurrentUserId();\n\n const context: InterceptorContext = {\n metadata: new Map(),\n userKey: effectiveUserKey,\n };\n\n try {\n const data = await this._executeWithInterceptors(\n fn,\n interceptors,\n context,\n );\n return { ok: true, data };\n } catch (error) {\n logger.error(\"Plugin execution failed\", { error, plugin: this.name });\n\n if (error instanceof AppKitError) {\n return {\n ok: false,\n status: error.statusCode,\n message: error.message,\n };\n }\n\n if (hasHttpStatusCode(error)) {\n const isDev = process.env.NODE_ENV !== \"production\";\n const isClientError = error.statusCode >= 400 && error.statusCode < 500;\n return {\n ok: false,\n status: error.statusCode,\n message: isDev || isClientError ? error.message : \"Server error\",\n };\n }\n\n const isDev = process.env.NODE_ENV !== \"production\";\n return {\n ok: false,\n status: 500,\n message:\n isDev && error instanceof Error ? error.message : \"Server error\",\n };\n }\n }\n\n protected registerEndpoint(name: string, path: string): void {\n this.registeredEndpoints[name] = path;\n }\n\n protected route<_TResponse>(\n router: express.Router,\n config: RouteConfig,\n ): void {\n const { name, method, path, handler } = config;\n\n router[method](path, handler);\n\n const fullPath = `/api/${this.name}${path}`;\n this.registerEndpoint(name, fullPath);\n\n if (config.skipBodyParsing) {\n this.skipBodyParsingPaths.add(fullPath);\n }\n }\n\n // build execution options by merging defaults, plugin config, and user overrides\n private _buildExecutionConfig(\n options: PluginExecutionSettings,\n ): PluginExecuteConfig {\n const { default: methodDefaults, user: userOverride } = options;\n\n // Merge: method defaults <- plugin config <- user override (highest priority)\n return deepMerge(\n deepMerge(methodDefaults, this.config),\n userOverride ?? {},\n ) as PluginExecuteConfig;\n }\n\n // build interceptors based on execute options\n private _buildInterceptors(\n options: PluginExecuteConfig,\n ): ExecutionInterceptor[] {\n const interceptors: ExecutionInterceptor[] = [];\n\n // order matters: telemetry → timeout → retry → cache (innermost to outermost)\n\n const telemetryConfig = normalizeTelemetryOptions(this.config.telemetry);\n if (\n telemetryConfig.traces &&\n (options.telemetryInterceptor?.enabled ?? true)\n ) {\n interceptors.push(\n new TelemetryInterceptor(this.telemetry, options.telemetryInterceptor),\n );\n }\n\n if (options.timeout && options.timeout > 0) {\n interceptors.push(new TimeoutInterceptor(options.timeout));\n }\n\n if (\n options.retry?.enabled &&\n options.retry.attempts &&\n options.retry.attempts > 1\n ) {\n interceptors.push(new RetryInterceptor(options.retry));\n }\n\n if (options.cache?.enabled && options.cache.cacheKey?.length) {\n interceptors.push(new CacheInterceptor(this.cache, options.cache));\n }\n\n return interceptors;\n }\n\n // execute method wrapped with interceptors\n private async _executeWithInterceptors<T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n interceptors: ExecutionInterceptor[],\n context: InterceptorContext,\n ): Promise<T> {\n // no interceptors, execute directly\n if (interceptors.length === 0) {\n return fn(context.signal);\n }\n // build nested execution chain from interceptors\n let wrappedFn = () => fn(context.signal);\n\n // wrap each interceptor around the previous function\n for (const interceptor of interceptors) {\n const previousFn = wrappedFn;\n wrappedFn = () => interceptor.intercept(previousFn, context);\n }\n\n return wrappedFn();\n }\n\n private _checkIfGenerator(\n result: any,\n ): result is AsyncGenerator<any, void, unknown> {\n return (\n result && typeof result === \"object\" && Symbol.asyncIterator in result\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -3,12 +3,23 @@ import "../shared/src/index.js";
|
|
|
3
3
|
|
|
4
4
|
//#region src/plugin/to-plugin.d.ts
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Factory function produced by {@link toPlugin}. Carries a static
|
|
7
|
+
* `pluginName` field so tooling (e.g. `fromPlugin`) can identify which
|
|
8
|
+
* plugin a factory references without constructing an instance.
|
|
9
|
+
*/
|
|
10
|
+
type NamedPluginFactory<Name extends string = string> = {
|
|
11
|
+
readonly pluginName: Name;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Wraps a plugin class so it can be passed to `createApp` with optional
|
|
15
|
+
* config. Infers the config type from the constructor and the plugin name
|
|
16
|
+
* from the static `manifest.name` property, and stamps `pluginName` onto
|
|
17
|
+
* the returned factory function so `fromPlugin` can identify the plugin
|
|
18
|
+
* without needing to construct it.
|
|
8
19
|
*
|
|
9
20
|
* @internal
|
|
10
21
|
*/
|
|
11
|
-
declare function toPlugin<T extends PluginConstructor>(plugin: T): ToPlugin<T, ConstructorParameters<T>[0], T["manifest"]["name"]>;
|
|
22
|
+
declare function toPlugin<T extends PluginConstructor>(plugin: T): ToPlugin<T, ConstructorParameters<T>[0], T["manifest"]["name"]> & NamedPluginFactory<T["manifest"]["name"]>;
|
|
12
23
|
//#endregion
|
|
13
|
-
export { toPlugin };
|
|
24
|
+
export { NamedPluginFactory, toPlugin };
|
|
14
25
|
//# sourceMappingURL=to-plugin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"to-plugin.d.ts","names":[],"sources":["../../src/plugin/to-plugin.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"to-plugin.d.ts","names":[],"sources":["../../src/plugin/to-plugin.ts"],"mappings":";;;;;;;AAOA;;KAAY,kBAAA;EAAA,SACD,UAAA,EAAY,IAAA;AAAA;;;;;AAYvB;;;;;iBAAgB,QAAA,WAAmB,iBAAA,CAAA,CACjC,MAAA,EAAQ,CAAA,GACP,QAAA,CAAS,CAAA,EAAG,qBAAA,CAAsB,CAAA,MAAO,CAAA,wBAC1C,kBAAA,CAAmB,CAAA"}
|
package/dist/plugin/to-plugin.js
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
//#region src/plugin/to-plugin.ts
|
|
2
2
|
/**
|
|
3
|
-
* Wraps a plugin class so it can be passed to createApp with optional
|
|
4
|
-
* Infers config type from the constructor and
|
|
3
|
+
* Wraps a plugin class so it can be passed to `createApp` with optional
|
|
4
|
+
* config. Infers the config type from the constructor and the plugin name
|
|
5
|
+
* from the static `manifest.name` property, and stamps `pluginName` onto
|
|
6
|
+
* the returned factory function so `fromPlugin` can identify the plugin
|
|
7
|
+
* without needing to construct it.
|
|
5
8
|
*
|
|
6
9
|
* @internal
|
|
7
10
|
*/
|
|
8
11
|
function toPlugin(plugin) {
|
|
9
|
-
|
|
12
|
+
const pluginName = plugin.manifest.name;
|
|
13
|
+
const factory = (config = {}) => ({
|
|
10
14
|
plugin,
|
|
11
15
|
config,
|
|
12
|
-
name:
|
|
16
|
+
name: pluginName
|
|
13
17
|
});
|
|
18
|
+
Object.defineProperty(factory, "pluginName", {
|
|
19
|
+
value: pluginName,
|
|
20
|
+
writable: false,
|
|
21
|
+
enumerable: true
|
|
22
|
+
});
|
|
23
|
+
return factory;
|
|
14
24
|
}
|
|
15
25
|
|
|
16
26
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"to-plugin.js","names":[],"sources":["../../src/plugin/to-plugin.ts"],"sourcesContent":["import type { PluginConstructor, PluginData, ToPlugin } from \"shared\";\n\n/**\n * Wraps a plugin class so it can be passed to createApp with optional
|
|
1
|
+
{"version":3,"file":"to-plugin.js","names":[],"sources":["../../src/plugin/to-plugin.ts"],"sourcesContent":["import type { PluginConstructor, PluginData, ToPlugin } from \"shared\";\n\n/**\n * Factory function produced by {@link toPlugin}. Carries a static\n * `pluginName` field so tooling (e.g. `fromPlugin`) can identify which\n * plugin a factory references without constructing an instance.\n */\nexport type NamedPluginFactory<Name extends string = string> = {\n readonly pluginName: Name;\n};\n\n/**\n * Wraps a plugin class so it can be passed to `createApp` with optional\n * config. Infers the config type from the constructor and the plugin name\n * from the static `manifest.name` property, and stamps `pluginName` onto\n * the returned factory function so `fromPlugin` can identify the plugin\n * without needing to construct it.\n *\n * @internal\n */\nexport function toPlugin<T extends PluginConstructor>(\n plugin: T,\n): ToPlugin<T, ConstructorParameters<T>[0], T[\"manifest\"][\"name\"]> &\n NamedPluginFactory<T[\"manifest\"][\"name\"]> {\n type Config = ConstructorParameters<T>[0];\n type Name = T[\"manifest\"][\"name\"];\n const pluginName = plugin.manifest.name as Name;\n const factory = (\n config: Config = {} as Config,\n ): PluginData<T, Config, Name> => ({\n plugin: plugin as T,\n config: config as Config,\n name: pluginName,\n });\n Object.defineProperty(factory, \"pluginName\", {\n value: pluginName,\n writable: false,\n enumerable: true,\n });\n return factory as ToPlugin<T, Config, Name> & NamedPluginFactory<Name>;\n}\n"],"mappings":";;;;;;;;;;AAoBA,SAAgB,SACd,QAE0C;CAG1C,MAAM,aAAa,OAAO,SAAS;CACnC,MAAM,WACJ,SAAiB,EAAE,MACc;EACzB;EACA;EACR,MAAM;EACP;AACD,QAAO,eAAe,SAAS,cAAc;EAC3C,OAAO;EACP,UAAU;EACV,YAAY;EACb,CAAC;AACF,QAAO"}
|