@databricks/appkit 0.26.1 → 0.27.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 -0
- package/dist/appkit/package.js +1 -1
- package/dist/connectors/index.js +2 -0
- package/dist/connectors/jobs/client.d.ts +2 -0
- package/dist/connectors/jobs/client.js +132 -0
- package/dist/connectors/jobs/client.js.map +1 -0
- package/dist/connectors/jobs/index.d.ts +2 -0
- package/dist/connectors/jobs/index.js +3 -0
- package/dist/connectors/jobs/types.d.ts +10 -0
- package/dist/connectors/jobs/types.d.ts.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.d.ts +3 -0
- package/dist/plugins/index.js +2 -0
- package/dist/plugins/jobs/defaults.js +45 -0
- package/dist/plugins/jobs/defaults.js.map +1 -0
- package/dist/plugins/jobs/index.d.ts +2 -0
- package/dist/plugins/jobs/index.js +3 -0
- package/dist/plugins/jobs/manifest.js +40 -0
- package/dist/plugins/jobs/manifest.js.map +1 -0
- package/dist/plugins/jobs/params.js +35 -0
- package/dist/plugins/jobs/params.js.map +1 -0
- package/dist/plugins/jobs/plugin.d.ts +66 -0
- package/dist/plugins/jobs/plugin.d.ts.map +1 -0
- package/dist/plugins/jobs/plugin.js +531 -0
- package/dist/plugins/jobs/plugin.js.map +1 -0
- package/dist/plugins/jobs/types.d.ts +84 -0
- package/dist/plugins/jobs/types.d.ts.map +1 -0
- package/dist/registry/manifest-loader.d.ts +2 -2
- package/dist/registry/manifest-loader.d.ts.map +1 -1
- package/dist/stream/stream-manager.d.ts.map +1 -1
- package/dist/stream/stream-manager.js +2 -0
- package/dist/stream/stream-manager.js.map +1 -1
- package/docs/api/appkit/Interface.BasePluginConfig.md +4 -0
- package/docs/api/appkit/Interface.IJobsConfig.md +86 -0
- package/docs/api/appkit/Interface.JobAPI.md +163 -0
- package/docs/api/appkit/Interface.JobConfig.md +36 -0
- package/docs/api/appkit/Interface.JobsConnectorConfig.md +10 -0
- package/docs/api/appkit/TypeAlias.JobHandle.md +29 -0
- package/docs/api/appkit/TypeAlias.JobsExport.md +34 -0
- package/docs/api/appkit.md +6 -0
- package/docs/plugins/jobs.md +252 -0
- package/docs/plugins.md +2 -1
- package/llms.txt +7 -0
- package/package.json +2 -1
- package/sbom.cdx.json +1 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { BasePluginConfig, IAppRequest } from "../../shared/src/plugin.js";
|
|
2
|
+
import "../../shared/src/index.js";
|
|
3
|
+
import { ExecutionResult } from "../../plugin/execution-result.js";
|
|
4
|
+
import "../../plugin/index.js";
|
|
5
|
+
import { jobs } from "@databricks/sdk-experimental";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
//#region src/plugins/jobs/types.d.ts
|
|
9
|
+
/** Supported task types for job parameter mapping. */
|
|
10
|
+
type TaskType = "notebook" | "python_wheel" | "python_script" | "spark_jar" | "sql" | "dbt";
|
|
11
|
+
/** Per-job configuration options. */
|
|
12
|
+
interface JobConfig {
|
|
13
|
+
/** Maximum time (ms) to poll in runAndWait before giving up. Defaults to 600 000 (10 min). */
|
|
14
|
+
waitTimeout?: number;
|
|
15
|
+
/** The type of task this job runs. Determines how params are mapped to the SDK request. */
|
|
16
|
+
taskType?: TaskType;
|
|
17
|
+
/** Optional Zod schema for validating job parameters at runtime. */
|
|
18
|
+
params?: z.ZodType<Record<string, unknown>>;
|
|
19
|
+
}
|
|
20
|
+
/** Status update yielded by runAndWait during polling. */
|
|
21
|
+
interface JobRunStatus {
|
|
22
|
+
status: string | undefined;
|
|
23
|
+
timestamp: number;
|
|
24
|
+
run: jobs.Run;
|
|
25
|
+
}
|
|
26
|
+
/** User-facing API for a single configured job. */
|
|
27
|
+
interface JobAPI {
|
|
28
|
+
/** Trigger the configured job with validated params. Returns the run response. */
|
|
29
|
+
runNow(params?: Record<string, unknown>): Promise<ExecutionResult<jobs.RunNowResponse>>;
|
|
30
|
+
/** Trigger and poll until completion, yielding status updates. */
|
|
31
|
+
runAndWait(params?: Record<string, unknown>, signal?: AbortSignal): AsyncGenerator<JobRunStatus, void, unknown>;
|
|
32
|
+
/** Get the most recent run for this job. */
|
|
33
|
+
lastRun(): Promise<ExecutionResult<jobs.BaseRun | undefined>>;
|
|
34
|
+
/** List runs for this job. */
|
|
35
|
+
listRuns(options?: {
|
|
36
|
+
limit?: number;
|
|
37
|
+
}): Promise<ExecutionResult<jobs.BaseRun[]>>;
|
|
38
|
+
/** Get a specific run by ID. */
|
|
39
|
+
getRun(runId: number): Promise<ExecutionResult<jobs.Run>>;
|
|
40
|
+
/** Get output of a specific run. */
|
|
41
|
+
getRunOutput(runId: number): Promise<ExecutionResult<jobs.RunOutput>>;
|
|
42
|
+
/** Cancel a specific run. */
|
|
43
|
+
cancelRun(runId: number): Promise<ExecutionResult<void>>;
|
|
44
|
+
/** Get the job definition. */
|
|
45
|
+
getJob(): Promise<ExecutionResult<jobs.Job>>;
|
|
46
|
+
}
|
|
47
|
+
/** Configuration for the Jobs plugin. */
|
|
48
|
+
interface IJobsConfig extends BasePluginConfig {
|
|
49
|
+
/** Operation timeout in milliseconds. Defaults to 60000. */
|
|
50
|
+
timeout?: number;
|
|
51
|
+
/** Poll interval for waitForRun in milliseconds. Defaults to 5000. */
|
|
52
|
+
pollIntervalMs?: number;
|
|
53
|
+
/** Named jobs to expose. Each key becomes a job accessor. */
|
|
54
|
+
jobs?: Record<string, JobConfig>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Job handle returned by `appkit.jobs("etl")`.
|
|
58
|
+
* Supports OBO access via `.asUser(req)`.
|
|
59
|
+
*/
|
|
60
|
+
type JobHandle = JobAPI & {
|
|
61
|
+
asUser: (req: IAppRequest) => JobAPI;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Public API shape of the jobs plugin.
|
|
65
|
+
* Callable to select a job by key.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* // Trigger a configured job
|
|
70
|
+
* const { run_id } = await appkit.jobs("etl").runNow();
|
|
71
|
+
*
|
|
72
|
+
* // Trigger and poll until completion
|
|
73
|
+
* for await (const status of appkit.jobs("etl").runAndWait()) {
|
|
74
|
+
* console.log(status.status, status.run);
|
|
75
|
+
* }
|
|
76
|
+
*
|
|
77
|
+
* // OBO access
|
|
78
|
+
* await appkit.jobs("etl").asUser(req).runNow();
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
type JobsExport = (jobKey: string) => JobHandle;
|
|
82
|
+
//#endregion
|
|
83
|
+
export { IJobsConfig, JobAPI, JobConfig, JobHandle, JobsExport };
|
|
84
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/plugins/jobs/types.ts"],"mappings":";;;;;;;;;KAMY,QAAA;;UASK,SAAA;EATL;EAWV,WAAA;;EAEA,QAAA,GAAW,QAAA;EAbO;EAelB,MAAA,GAAS,CAAA,CAAE,OAAA,CAAQ,MAAA;AAAA;;UAIJ,YAAA;EACf,MAAA;EACA,SAAA;EACA,GAAA,EAAK,IAAA,CAAK,GAAA;AAAA;;UAIK,MAAA;EAbJ;EAeX,MAAA,CACE,MAAA,GAAS,MAAA,oBACR,OAAA,CAAQ,eAAA,CAAgB,IAAA,CAAK,cAAA;EAfvB;EAiBT,UAAA,CACE,MAAA,GAAS,MAAA,mBACT,MAAA,GAAS,WAAA,GACR,cAAA,CAAe,YAAA;EApBC;EAsBnB,OAAA,IAAW,OAAA,CAAQ,eAAA,CAAgB,IAAA,CAAK,OAAA;EAtBf;EAwBzB,QAAA,CAAS,OAAA;IACP,KAAA;EAAA,IACE,OAAA,CAAQ,eAAA,CAAgB,IAAA,CAAK,OAAA;EAnBpB;EAqBb,MAAA,CAAO,KAAA,WAAgB,OAAA,CAAQ,eAAA,CAAgB,IAAA,CAAK,GAAA;EAtBpD;EAwBA,YAAA,CAAa,KAAA,WAAgB,OAAA,CAAQ,eAAA,CAAgB,IAAA,CAAK,SAAA;EAvBrD;EAyBL,SAAA,CAAU,KAAA,WAAgB,OAAA,CAAQ,eAAA;EAzBrB;EA2Bb,MAAA,IAAU,OAAA,CAAQ,eAAA,CAAgB,IAAA,CAAK,GAAA;AAAA;;UAIxB,WAAA,SAAoB,gBAAA;EAxBxB;EA0BX,OAAA;EAzBW;EA2BX,cAAA;EAxBW;EA0BX,IAAA,GAAO,MAAA,SAAe,SAAA;AAAA;;;;;KAOZ,SAAA,GAAY,MAAA;EACtB,MAAA,GAAS,GAAA,EAAK,WAAA,KAAgB,MAAA;AAAA;;;;;;;;;;;;;;;;;;;KAqBpB,UAAA,IAAc,MAAA,aAAmB,SAAA"}
|
|
@@ -35,11 +35,11 @@ declare function getPluginManifest(plugin: PluginConstructor): PluginManifest;
|
|
|
35
35
|
declare function getResourceRequirements(plugin: PluginConstructor): {
|
|
36
36
|
required: boolean;
|
|
37
37
|
description: string;
|
|
38
|
-
fields: Record<string, ResourceFieldEntry>;
|
|
39
38
|
type: ResourceType;
|
|
39
|
+
permission: ResourcePermission;
|
|
40
|
+
fields: Record<string, ResourceFieldEntry>;
|
|
40
41
|
alias: string;
|
|
41
42
|
resourceKey: string;
|
|
42
|
-
permission: ResourcePermission;
|
|
43
43
|
}[];
|
|
44
44
|
//#endregion
|
|
45
45
|
export { getPluginManifest, getResourceRequirements };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-loader.d.ts","names":[],"sources":["../../src/registry/manifest-loader.ts"],"mappings":";;;;;;;;;;;AA4DA;;;;iBAAgB,iBAAA,CAAkB,MAAA,EAAQ,iBAAA,GAAoB,cAAA;;;;;AA4F9D;;;;;;;;;;;;;;iBAAgB,uBAAA,CAAwB,MAAA,EAAQ,iBAAA
|
|
1
|
+
{"version":3,"file":"manifest-loader.d.ts","names":[],"sources":["../../src/registry/manifest-loader.ts"],"mappings":";;;;;;;;;;;AA4DA;;;;iBAAgB,iBAAA,CAAkB,MAAA,EAAQ,iBAAA,GAAoB,cAAA;;;;;AA4F9D;;;;;;;;;;;;;;iBAAgB,uBAAA,CAAwB,MAAA,EAAQ,iBAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-manager.d.ts","names":[],"sources":["../../src/stream/stream-manager.ts"],"mappings":";;;;;cAca,aAAA;EAAA,QACH,gBAAA;EAAA,QACA,cAAA;EAAA,QACA,SAAA;EAAA,QACA,YAAA;EAAA,QACA,SAAA;cAEI,OAAA,GAAU,YAAA;EAWhB,MAAA,CACJ,GAAA,EAAK,YAAA,EACL,OAAA,GAAU,MAAA,EAAQ,WAAA,KAAgB,cAAA,sBAClC,OAAA,GAAU,YAAA,GACT,OAAA;EAyBH,QAAA,CAAA;EAUA,cAAA,CAAA;EAAA,QAKc,uBAAA;EAAA,
|
|
1
|
+
{"version":3,"file":"stream-manager.d.ts","names":[],"sources":["../../src/stream/stream-manager.ts"],"mappings":";;;;;cAca,aAAA;EAAA,QACH,gBAAA;EAAA,QACA,cAAA;EAAA,QACA,SAAA;EAAA,QACA,YAAA;EAAA,QACA,SAAA;cAEI,OAAA,GAAU,YAAA;EAWhB,MAAA,CACJ,GAAA,EAAK,YAAA,EACL,OAAA,GAAU,MAAA,EAAQ,WAAA,KAAgB,cAAA,sBAClC,OAAA,GAAU,YAAA,GACT,OAAA;EAyBH,QAAA,CAAA;EAUA,cAAA,CAAA;EAAA,QAKc,uBAAA;EAAA,QAyEA,gBAAA;EAAA,QA2EA,6BAAA;EAAA,QAoFN,eAAA;EAAA,QA6BA,yBAAA;EAAA,QAaA,wBAAA;EAAA,QAkBA,gBAAA;EAAA,QASA,cAAA;EAAA,QAUA,gBAAA;AAAA"}
|
|
@@ -70,6 +70,7 @@ var StreamManager = class {
|
|
|
70
70
|
clearInterval(heartbeat);
|
|
71
71
|
streamEntry.clients.delete(res);
|
|
72
72
|
this.activeOperations.delete(streamOperation);
|
|
73
|
+
if (streamEntry.clients.size === 0 && !streamEntry.isCompleted) streamEntry.abortController.abort("All clients disconnected");
|
|
73
74
|
if (streamEntry.isCompleted && streamEntry.clients.size === 0) setTimeout(() => {
|
|
74
75
|
if (streamEntry.clients.size === 0) this.streamRegistry.remove(streamEntry.streamId);
|
|
75
76
|
}, this.bufferTTL);
|
|
@@ -113,6 +114,7 @@ var StreamManager = class {
|
|
|
113
114
|
clearInterval(heartbeat);
|
|
114
115
|
this.activeOperations.delete(streamOperation);
|
|
115
116
|
streamEntry.clients.delete(res);
|
|
117
|
+
if (streamEntry.clients.size === 0 && !streamEntry.isCompleted) abortController.abort("Client disconnected");
|
|
116
118
|
});
|
|
117
119
|
await this._processGeneratorInBackground(streamEntry);
|
|
118
120
|
clearInterval(heartbeat);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-manager.js","names":[],"sources":["../../src/stream/stream-manager.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { context } from \"@opentelemetry/api\";\nimport type { IAppResponse, StreamConfig } from \"shared\";\nimport { createLogger } from \"../logging/logger\";\nimport { EventRingBuffer } from \"./buffers\";\nimport { streamDefaults } from \"./defaults\";\nimport { SSEWriter } from \"./sse-writer\";\nimport { StreamRegistry } from \"./stream-registry\";\nimport { SSEErrorCode, type StreamEntry, type StreamOperation } from \"./types\";\nimport { StreamValidator } from \"./validator\";\n\nconst logger = createLogger(\"stream\");\n\n// main entry point for Server-Sent events streaming\nexport class StreamManager {\n private activeOperations: Set<StreamOperation>;\n private streamRegistry: StreamRegistry;\n private sseWriter: SSEWriter;\n private maxEventSize: number;\n private bufferTTL: number;\n\n constructor(options?: StreamConfig) {\n this.streamRegistry = new StreamRegistry(\n options?.maxActiveStreams ?? streamDefaults.maxActiveStreams,\n );\n this.sseWriter = new SSEWriter();\n this.maxEventSize = options?.maxEventSize ?? streamDefaults.maxEventSize;\n this.bufferTTL = options?.bufferTTL ?? streamDefaults.bufferTTL;\n this.activeOperations = new Set();\n }\n\n // main streaming method - handles new connection and reconnection\n async stream(\n res: IAppResponse,\n handler: (signal: AbortSignal) => AsyncGenerator<any, void, unknown>,\n options?: StreamConfig,\n ): Promise<void> {\n const { streamId } = options || {};\n\n // check if response is already closed\n if (res.writableEnded || res.destroyed) {\n return;\n }\n\n // setup SSE headers\n this.sseWriter.setupHeaders(res);\n\n // handle reconnection\n if (streamId && StreamValidator.validateStreamId(streamId)) {\n const existingStream = this.streamRegistry.get(streamId);\n // if stream exists, attach to it\n if (existingStream) {\n return this._attachToExistingStream(res, existingStream, options);\n }\n }\n\n // if stream does not exist, create a new one\n return this._createNewStream(res, handler, options);\n }\n\n // abort all active operations\n abortAll(): void {\n this.activeOperations.forEach((operation) => {\n if (operation.heartbeat) clearInterval(operation.heartbeat);\n operation.controller.abort(\"Server shutdown\");\n });\n this.activeOperations.clear();\n this.streamRegistry.clear();\n }\n\n // get the number of active operations\n getActiveCount(): number {\n return this.activeOperations.size;\n }\n\n // attach to existing stream\n private async _attachToExistingStream(\n res: IAppResponse,\n streamEntry: StreamEntry,\n options?: StreamConfig,\n ): Promise<void> {\n // handle reconnection - replay missed events\n const lastEventId = res.req?.headers[\"last-event-id\"];\n\n if (StreamValidator.validateEventId(lastEventId)) {\n // cast to string after validation\n const validEventId = lastEventId as string;\n if (streamEntry.eventBuffer.has(validEventId)) {\n const missedEvents =\n streamEntry.eventBuffer.getEventsSince(validEventId);\n // broadcast missed events to client\n for (const event of missedEvents) {\n if (options?.userSignal?.aborted) break;\n this.sseWriter.writeBufferedEvent(res, event);\n }\n } else {\n // buffer overflow - send warning\n this.sseWriter.writeBufferOverflowWarning(res, validEventId);\n }\n }\n\n // add client to stream entry\n streamEntry.clients.add(res);\n streamEntry.lastAccess = Date.now();\n\n // start heartbeat\n const combinedSignal = this._combineSignals(\n streamEntry.abortController.signal,\n options?.userSignal,\n );\n const heartbeat = this.sseWriter.startHeartbeat(res, combinedSignal);\n\n // track operation\n const streamOperation: StreamOperation = {\n controller: streamEntry.abortController,\n type: \"stream\",\n heartbeat,\n };\n this.activeOperations.add(streamOperation);\n\n // handle client disconnect\n res.on(\"close\", () => {\n clearInterval(heartbeat);\n streamEntry.clients.delete(res);\n this.activeOperations.delete(streamOperation);\n\n // cleanup if stream is completed and no clients are connected\n if (streamEntry.isCompleted && streamEntry.clients.size === 0) {\n setTimeout(() => {\n if (streamEntry.clients.size === 0) {\n this.streamRegistry.remove(streamEntry.streamId);\n }\n }, this.bufferTTL);\n }\n });\n\n // if stream is completed, close connection\n if (streamEntry.isCompleted) {\n res.end();\n // cleanup operation\n this.activeOperations.delete(streamOperation);\n clearInterval(heartbeat);\n }\n }\n private async _createNewStream(\n res: IAppResponse,\n handler: (signal: AbortSignal) => AsyncGenerator<any, void, unknown>,\n options?: StreamConfig,\n ): Promise<void> {\n const streamId = options?.streamId ?? randomUUID();\n\n // abort stream if response is closed\n if (res.writableEnded || res.destroyed) {\n return;\n }\n\n const abortController = new AbortController();\n\n // create event buffer\n const eventBuffer = new EventRingBuffer(\n options?.bufferSize ?? streamDefaults.bufferSize,\n );\n\n // setup signals and heartbeat\n const combinedSignal = this._combineSignals(\n abortController.signal,\n options?.userSignal,\n );\n const heartbeat = this.sseWriter.startHeartbeat(res, combinedSignal);\n\n // capture the current trace context at stream creation time\n const traceContext = context.active();\n\n // abort stream if response is closed\n if (res.writableEnded || res.destroyed) {\n clearInterval(heartbeat);\n return;\n }\n\n // create stream entry\n const streamEntry: StreamEntry = {\n streamId,\n generator: handler(combinedSignal),\n eventBuffer,\n clients: new Set([res]),\n isCompleted: false,\n lastAccess: Date.now(),\n abortController,\n traceContext,\n };\n this.streamRegistry.add(streamEntry);\n\n // track operation\n const streamOperation: StreamOperation = {\n controller: abortController,\n type: \"stream\",\n heartbeat,\n };\n this.activeOperations.add(streamOperation);\n\n res.on(\"close\", () => {\n clearInterval(heartbeat);\n this.activeOperations.delete(streamOperation);\n streamEntry.clients.delete(res);\n });\n\n await this._processGeneratorInBackground(streamEntry);\n\n // cleanup\n clearInterval(heartbeat);\n this.activeOperations.delete(streamOperation);\n }\n\n private async _processGeneratorInBackground(\n streamEntry: StreamEntry,\n ): Promise<void> {\n // run the entire generator processing within the captured trace context\n return context.with(streamEntry.traceContext, async () => {\n try {\n // retrieve all events from generator\n for await (const event of streamEntry.generator) {\n if (streamEntry.abortController.signal.aborted) break;\n const eventId = randomUUID();\n const eventData = JSON.stringify(event);\n\n // validate event size\n if (eventData.length > this.maxEventSize) {\n const errorMsg = `Event exceeds max size of ${this.maxEventSize} bytes`;\n const errorCode = SSEErrorCode.INVALID_REQUEST;\n // broadcast error to all connected clients\n this._broadcastErrorToClients(\n streamEntry,\n eventId,\n errorMsg,\n errorCode,\n );\n continue;\n }\n\n // buffer event for reconnection\n streamEntry.eventBuffer.add({\n id: eventId,\n type: event.type,\n data: eventData,\n timestamp: Date.now(),\n });\n\n // broadcast to all connected clients\n this._broadcastEventsToClients(streamEntry, eventId, event);\n streamEntry.lastAccess = Date.now();\n }\n\n streamEntry.isCompleted = true;\n\n // close all clients\n this._closeAllClients(streamEntry);\n\n // cleanup if no clients are connected\n this._cleanupStream(streamEntry);\n } catch (error) {\n const errorMsg =\n error instanceof Error ? error.message : \"Internal server error\";\n const errorEventId = randomUUID();\n const errorCode = this._categorizeError(error);\n\n // client cancellation is a normal control-flow signal, not a failure\n if (errorCode === SSEErrorCode.STREAM_ABORTED) {\n logger.info(\"Stream aborted by client (code=%s)\", errorCode);\n } else {\n logger.error(\n \"Stream execution failed: %s (code=%s)\",\n errorMsg,\n errorCode,\n );\n }\n\n // buffer error event\n streamEntry.eventBuffer.add({\n id: errorEventId,\n type: \"error\",\n data: JSON.stringify({ error: errorMsg, code: errorCode }),\n timestamp: Date.now(),\n });\n\n // send error event to all connected clients\n this._broadcastErrorToClients(\n streamEntry,\n errorEventId,\n errorMsg,\n errorCode,\n true,\n );\n streamEntry.isCompleted = true;\n }\n });\n }\n\n private _combineSignals(\n internalSignal?: AbortSignal,\n userSignal?: AbortSignal,\n ): AbortSignal {\n if (!userSignal) return internalSignal || new AbortController().signal;\n\n const signals = [internalSignal, userSignal].filter(\n Boolean,\n ) as AbortSignal[];\n const controller = new AbortController();\n\n signals.forEach((signal) => {\n if (signal?.aborted) {\n controller.abort(signal.reason);\n return;\n }\n\n signal?.addEventListener(\n \"abort\",\n () => {\n controller.abort(signal.reason);\n },\n { once: true },\n );\n });\n return controller.signal;\n }\n\n // broadcast events to all connected clients\n private _broadcastEventsToClients(\n streamEntry: StreamEntry,\n eventId: string,\n event: any,\n ): void {\n for (const client of streamEntry.clients) {\n if (!client.writableEnded) {\n this.sseWriter.writeEvent(client, eventId, event);\n }\n }\n }\n\n // broadcast error to all connected clients\n private _broadcastErrorToClients(\n streamEntry: StreamEntry,\n eventId: string,\n errorMessage: string,\n errorCode: SSEErrorCode,\n closeClients: boolean = false,\n ): void {\n for (const client of streamEntry.clients) {\n if (!client.writableEnded) {\n this.sseWriter.writeError(client, eventId, errorMessage, errorCode);\n if (closeClients) {\n client.end();\n }\n }\n }\n }\n\n // close all connected clients\n private _closeAllClients(streamEntry: StreamEntry): void {\n for (const client of streamEntry.clients) {\n if (!client.writableEnded) {\n client.end();\n }\n }\n }\n\n // cleanup stream if no clients are connected\n private _cleanupStream(streamEntry: StreamEntry): void {\n if (streamEntry.clients.size === 0) {\n setTimeout(() => {\n if (streamEntry.clients.size === 0) {\n this.streamRegistry.remove(streamEntry.streamId);\n }\n }, this.bufferTTL);\n }\n }\n\n private _categorizeError(error: unknown): SSEErrorCode {\n if (error instanceof Error) {\n const message = error.message.toLowerCase();\n if (message.includes(\"timeout\") || message.includes(\"timed out\")) {\n return SSEErrorCode.TIMEOUT;\n }\n\n if (message.includes(\"unavailable\") || message.includes(\"econnrefused\")) {\n return SSEErrorCode.TEMPORARY_UNAVAILABLE;\n }\n\n if (error.name === \"AbortError\") {\n return SSEErrorCode.STREAM_ABORTED;\n }\n\n // Detect upstream API errors (e.g., from Databricks SDK ApiError)\n if (\n \"statusCode\" in error &&\n typeof (error as any).statusCode === \"number\"\n ) {\n return SSEErrorCode.UPSTREAM_ERROR;\n }\n }\n\n return SSEErrorCode.INTERNAL_ERROR;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stream-manager.js","names":[],"sources":["../../src/stream/stream-manager.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { context } from \"@opentelemetry/api\";\nimport type { IAppResponse, StreamConfig } from \"shared\";\nimport { createLogger } from \"../logging/logger\";\nimport { EventRingBuffer } from \"./buffers\";\nimport { streamDefaults } from \"./defaults\";\nimport { SSEWriter } from \"./sse-writer\";\nimport { StreamRegistry } from \"./stream-registry\";\nimport { SSEErrorCode, type StreamEntry, type StreamOperation } from \"./types\";\nimport { StreamValidator } from \"./validator\";\n\nconst logger = createLogger(\"stream\");\n\n// main entry point for Server-Sent events streaming\nexport class StreamManager {\n private activeOperations: Set<StreamOperation>;\n private streamRegistry: StreamRegistry;\n private sseWriter: SSEWriter;\n private maxEventSize: number;\n private bufferTTL: number;\n\n constructor(options?: StreamConfig) {\n this.streamRegistry = new StreamRegistry(\n options?.maxActiveStreams ?? streamDefaults.maxActiveStreams,\n );\n this.sseWriter = new SSEWriter();\n this.maxEventSize = options?.maxEventSize ?? streamDefaults.maxEventSize;\n this.bufferTTL = options?.bufferTTL ?? streamDefaults.bufferTTL;\n this.activeOperations = new Set();\n }\n\n // main streaming method - handles new connection and reconnection\n async stream(\n res: IAppResponse,\n handler: (signal: AbortSignal) => AsyncGenerator<any, void, unknown>,\n options?: StreamConfig,\n ): Promise<void> {\n const { streamId } = options || {};\n\n // check if response is already closed\n if (res.writableEnded || res.destroyed) {\n return;\n }\n\n // setup SSE headers\n this.sseWriter.setupHeaders(res);\n\n // handle reconnection\n if (streamId && StreamValidator.validateStreamId(streamId)) {\n const existingStream = this.streamRegistry.get(streamId);\n // if stream exists, attach to it\n if (existingStream) {\n return this._attachToExistingStream(res, existingStream, options);\n }\n }\n\n // if stream does not exist, create a new one\n return this._createNewStream(res, handler, options);\n }\n\n // abort all active operations\n abortAll(): void {\n this.activeOperations.forEach((operation) => {\n if (operation.heartbeat) clearInterval(operation.heartbeat);\n operation.controller.abort(\"Server shutdown\");\n });\n this.activeOperations.clear();\n this.streamRegistry.clear();\n }\n\n // get the number of active operations\n getActiveCount(): number {\n return this.activeOperations.size;\n }\n\n // attach to existing stream\n private async _attachToExistingStream(\n res: IAppResponse,\n streamEntry: StreamEntry,\n options?: StreamConfig,\n ): Promise<void> {\n // handle reconnection - replay missed events\n const lastEventId = res.req?.headers[\"last-event-id\"];\n\n if (StreamValidator.validateEventId(lastEventId)) {\n // cast to string after validation\n const validEventId = lastEventId as string;\n if (streamEntry.eventBuffer.has(validEventId)) {\n const missedEvents =\n streamEntry.eventBuffer.getEventsSince(validEventId);\n // broadcast missed events to client\n for (const event of missedEvents) {\n if (options?.userSignal?.aborted) break;\n this.sseWriter.writeBufferedEvent(res, event);\n }\n } else {\n // buffer overflow - send warning\n this.sseWriter.writeBufferOverflowWarning(res, validEventId);\n }\n }\n\n // add client to stream entry\n streamEntry.clients.add(res);\n streamEntry.lastAccess = Date.now();\n\n // start heartbeat\n const combinedSignal = this._combineSignals(\n streamEntry.abortController.signal,\n options?.userSignal,\n );\n const heartbeat = this.sseWriter.startHeartbeat(res, combinedSignal);\n\n // track operation\n const streamOperation: StreamOperation = {\n controller: streamEntry.abortController,\n type: \"stream\",\n heartbeat,\n };\n this.activeOperations.add(streamOperation);\n\n // handle client disconnect\n res.on(\"close\", () => {\n clearInterval(heartbeat);\n streamEntry.clients.delete(res);\n this.activeOperations.delete(streamOperation);\n\n // Stop the generator when no clients remain\n if (streamEntry.clients.size === 0 && !streamEntry.isCompleted) {\n streamEntry.abortController.abort(\"All clients disconnected\");\n }\n\n // cleanup if stream is completed and no clients are connected\n if (streamEntry.isCompleted && streamEntry.clients.size === 0) {\n setTimeout(() => {\n if (streamEntry.clients.size === 0) {\n this.streamRegistry.remove(streamEntry.streamId);\n }\n }, this.bufferTTL);\n }\n });\n\n // if stream is completed, close connection\n if (streamEntry.isCompleted) {\n res.end();\n // cleanup operation\n this.activeOperations.delete(streamOperation);\n clearInterval(heartbeat);\n }\n }\n private async _createNewStream(\n res: IAppResponse,\n handler: (signal: AbortSignal) => AsyncGenerator<any, void, unknown>,\n options?: StreamConfig,\n ): Promise<void> {\n const streamId = options?.streamId ?? randomUUID();\n\n // abort stream if response is closed\n if (res.writableEnded || res.destroyed) {\n return;\n }\n\n const abortController = new AbortController();\n\n // create event buffer\n const eventBuffer = new EventRingBuffer(\n options?.bufferSize ?? streamDefaults.bufferSize,\n );\n\n // setup signals and heartbeat\n const combinedSignal = this._combineSignals(\n abortController.signal,\n options?.userSignal,\n );\n const heartbeat = this.sseWriter.startHeartbeat(res, combinedSignal);\n\n // capture the current trace context at stream creation time\n const traceContext = context.active();\n\n // abort stream if response is closed\n if (res.writableEnded || res.destroyed) {\n clearInterval(heartbeat);\n return;\n }\n\n // create stream entry\n const streamEntry: StreamEntry = {\n streamId,\n generator: handler(combinedSignal),\n eventBuffer,\n clients: new Set([res]),\n isCompleted: false,\n lastAccess: Date.now(),\n abortController,\n traceContext,\n };\n this.streamRegistry.add(streamEntry);\n\n // track operation\n const streamOperation: StreamOperation = {\n controller: abortController,\n type: \"stream\",\n heartbeat,\n };\n this.activeOperations.add(streamOperation);\n\n res.on(\"close\", () => {\n clearInterval(heartbeat);\n this.activeOperations.delete(streamOperation);\n streamEntry.clients.delete(res);\n\n // Stop the generator when no clients remain so polling loops\n // (e.g. jobs runAndWait) don't keep running in the background.\n if (streamEntry.clients.size === 0 && !streamEntry.isCompleted) {\n abortController.abort(\"Client disconnected\");\n }\n });\n\n await this._processGeneratorInBackground(streamEntry);\n\n // cleanup\n clearInterval(heartbeat);\n this.activeOperations.delete(streamOperation);\n }\n\n private async _processGeneratorInBackground(\n streamEntry: StreamEntry,\n ): Promise<void> {\n // run the entire generator processing within the captured trace context\n return context.with(streamEntry.traceContext, async () => {\n try {\n // retrieve all events from generator\n for await (const event of streamEntry.generator) {\n if (streamEntry.abortController.signal.aborted) break;\n const eventId = randomUUID();\n const eventData = JSON.stringify(event);\n\n // validate event size\n if (eventData.length > this.maxEventSize) {\n const errorMsg = `Event exceeds max size of ${this.maxEventSize} bytes`;\n const errorCode = SSEErrorCode.INVALID_REQUEST;\n // broadcast error to all connected clients\n this._broadcastErrorToClients(\n streamEntry,\n eventId,\n errorMsg,\n errorCode,\n );\n continue;\n }\n\n // buffer event for reconnection\n streamEntry.eventBuffer.add({\n id: eventId,\n type: event.type,\n data: eventData,\n timestamp: Date.now(),\n });\n\n // broadcast to all connected clients\n this._broadcastEventsToClients(streamEntry, eventId, event);\n streamEntry.lastAccess = Date.now();\n }\n\n streamEntry.isCompleted = true;\n\n // close all clients\n this._closeAllClients(streamEntry);\n\n // cleanup if no clients are connected\n this._cleanupStream(streamEntry);\n } catch (error) {\n const errorMsg =\n error instanceof Error ? error.message : \"Internal server error\";\n const errorEventId = randomUUID();\n const errorCode = this._categorizeError(error);\n\n // client cancellation is a normal control-flow signal, not a failure\n if (errorCode === SSEErrorCode.STREAM_ABORTED) {\n logger.info(\"Stream aborted by client (code=%s)\", errorCode);\n } else {\n logger.error(\n \"Stream execution failed: %s (code=%s)\",\n errorMsg,\n errorCode,\n );\n }\n\n // buffer error event\n streamEntry.eventBuffer.add({\n id: errorEventId,\n type: \"error\",\n data: JSON.stringify({ error: errorMsg, code: errorCode }),\n timestamp: Date.now(),\n });\n\n // send error event to all connected clients\n this._broadcastErrorToClients(\n streamEntry,\n errorEventId,\n errorMsg,\n errorCode,\n true,\n );\n streamEntry.isCompleted = true;\n }\n });\n }\n\n private _combineSignals(\n internalSignal?: AbortSignal,\n userSignal?: AbortSignal,\n ): AbortSignal {\n if (!userSignal) return internalSignal || new AbortController().signal;\n\n const signals = [internalSignal, userSignal].filter(\n Boolean,\n ) as AbortSignal[];\n const controller = new AbortController();\n\n signals.forEach((signal) => {\n if (signal?.aborted) {\n controller.abort(signal.reason);\n return;\n }\n\n signal?.addEventListener(\n \"abort\",\n () => {\n controller.abort(signal.reason);\n },\n { once: true },\n );\n });\n return controller.signal;\n }\n\n // broadcast events to all connected clients\n private _broadcastEventsToClients(\n streamEntry: StreamEntry,\n eventId: string,\n event: any,\n ): void {\n for (const client of streamEntry.clients) {\n if (!client.writableEnded) {\n this.sseWriter.writeEvent(client, eventId, event);\n }\n }\n }\n\n // broadcast error to all connected clients\n private _broadcastErrorToClients(\n streamEntry: StreamEntry,\n eventId: string,\n errorMessage: string,\n errorCode: SSEErrorCode,\n closeClients: boolean = false,\n ): void {\n for (const client of streamEntry.clients) {\n if (!client.writableEnded) {\n this.sseWriter.writeError(client, eventId, errorMessage, errorCode);\n if (closeClients) {\n client.end();\n }\n }\n }\n }\n\n // close all connected clients\n private _closeAllClients(streamEntry: StreamEntry): void {\n for (const client of streamEntry.clients) {\n if (!client.writableEnded) {\n client.end();\n }\n }\n }\n\n // cleanup stream if no clients are connected\n private _cleanupStream(streamEntry: StreamEntry): void {\n if (streamEntry.clients.size === 0) {\n setTimeout(() => {\n if (streamEntry.clients.size === 0) {\n this.streamRegistry.remove(streamEntry.streamId);\n }\n }, this.bufferTTL);\n }\n }\n\n private _categorizeError(error: unknown): SSEErrorCode {\n if (error instanceof Error) {\n const message = error.message.toLowerCase();\n if (message.includes(\"timeout\") || message.includes(\"timed out\")) {\n return SSEErrorCode.TIMEOUT;\n }\n\n if (message.includes(\"unavailable\") || message.includes(\"econnrefused\")) {\n return SSEErrorCode.TEMPORARY_UNAVAILABLE;\n }\n\n if (error.name === \"AbortError\") {\n return SSEErrorCode.STREAM_ABORTED;\n }\n\n // Detect upstream API errors (e.g., from Databricks SDK ApiError)\n if (\n \"statusCode\" in error &&\n typeof (error as any).statusCode === \"number\"\n ) {\n return SSEErrorCode.UPSTREAM_ERROR;\n }\n }\n\n return SSEErrorCode.INTERNAL_ERROR;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Interface: IJobsConfig
|
|
2
|
+
|
|
3
|
+
Configuration for the Jobs plugin.
|
|
4
|
+
|
|
5
|
+
## Extends[](#extends "Direct link to Extends")
|
|
6
|
+
|
|
7
|
+
* [`BasePluginConfig`](./docs/api/appkit/Interface.BasePluginConfig.md)
|
|
8
|
+
|
|
9
|
+
## Indexable[](#indexable "Direct link to Indexable")
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
[key: string]: unknown
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Properties[](#properties "Direct link to Properties")
|
|
17
|
+
|
|
18
|
+
### host?[](#host "Direct link to host?")
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
optional host: string;
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
#### Inherited from[](#inherited-from "Direct link to Inherited from")
|
|
26
|
+
|
|
27
|
+
[`BasePluginConfig`](./docs/api/appkit/Interface.BasePluginConfig.md).[`host`](./docs/api/appkit/Interface.BasePluginConfig.md#host)
|
|
28
|
+
|
|
29
|
+
***
|
|
30
|
+
|
|
31
|
+
### jobs?[](#jobs "Direct link to jobs?")
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
optional jobs: Record<string, JobConfig>;
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Named jobs to expose. Each key becomes a job accessor.
|
|
39
|
+
|
|
40
|
+
***
|
|
41
|
+
|
|
42
|
+
### name?[](#name "Direct link to name?")
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
optional name: string;
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### Inherited from[](#inherited-from-1 "Direct link to Inherited from")
|
|
50
|
+
|
|
51
|
+
[`BasePluginConfig`](./docs/api/appkit/Interface.BasePluginConfig.md).[`name`](./docs/api/appkit/Interface.BasePluginConfig.md#name)
|
|
52
|
+
|
|
53
|
+
***
|
|
54
|
+
|
|
55
|
+
### pollIntervalMs?[](#pollintervalms "Direct link to pollIntervalMs?")
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
optional pollIntervalMs: number;
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Poll interval for waitForRun in milliseconds. Defaults to 5000.
|
|
63
|
+
|
|
64
|
+
***
|
|
65
|
+
|
|
66
|
+
### telemetry?[](#telemetry "Direct link to telemetry?")
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
optional telemetry: TelemetryOptions;
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### Inherited from[](#inherited-from-2 "Direct link to Inherited from")
|
|
74
|
+
|
|
75
|
+
[`BasePluginConfig`](./docs/api/appkit/Interface.BasePluginConfig.md).[`telemetry`](./docs/api/appkit/Interface.BasePluginConfig.md#telemetry)
|
|
76
|
+
|
|
77
|
+
***
|
|
78
|
+
|
|
79
|
+
### timeout?[](#timeout "Direct link to timeout?")
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
optional timeout: number;
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Operation timeout in milliseconds. Defaults to 60000.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Interface: JobAPI
|
|
2
|
+
|
|
3
|
+
User-facing API for a single configured job.
|
|
4
|
+
|
|
5
|
+
## Methods[](#methods "Direct link to Methods")
|
|
6
|
+
|
|
7
|
+
### cancelRun()[](#cancelrun "Direct link to cancelRun()")
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
cancelRun(runId: number): Promise<ExecutionResult<void>>;
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Cancel a specific run.
|
|
15
|
+
|
|
16
|
+
#### Parameters[](#parameters "Direct link to Parameters")
|
|
17
|
+
|
|
18
|
+
| Parameter | Type |
|
|
19
|
+
| --------- | -------- |
|
|
20
|
+
| `runId` | `number` |
|
|
21
|
+
|
|
22
|
+
#### Returns[](#returns "Direct link to Returns")
|
|
23
|
+
|
|
24
|
+
`Promise`<[`ExecutionResult`](./docs/api/appkit/TypeAlias.ExecutionResult.md)<`void`>>
|
|
25
|
+
|
|
26
|
+
***
|
|
27
|
+
|
|
28
|
+
### getJob()[](#getjob "Direct link to getJob()")
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
getJob(): Promise<ExecutionResult<Job>>;
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Get the job definition.
|
|
36
|
+
|
|
37
|
+
#### Returns[](#returns-1 "Direct link to Returns")
|
|
38
|
+
|
|
39
|
+
`Promise`<[`ExecutionResult`](./docs/api/appkit/TypeAlias.ExecutionResult.md)<`Job`>>
|
|
40
|
+
|
|
41
|
+
***
|
|
42
|
+
|
|
43
|
+
### getRun()[](#getrun "Direct link to getRun()")
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
getRun(runId: number): Promise<ExecutionResult<Run>>;
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Get a specific run by ID.
|
|
51
|
+
|
|
52
|
+
#### Parameters[](#parameters-1 "Direct link to Parameters")
|
|
53
|
+
|
|
54
|
+
| Parameter | Type |
|
|
55
|
+
| --------- | -------- |
|
|
56
|
+
| `runId` | `number` |
|
|
57
|
+
|
|
58
|
+
#### Returns[](#returns-2 "Direct link to Returns")
|
|
59
|
+
|
|
60
|
+
`Promise`<[`ExecutionResult`](./docs/api/appkit/TypeAlias.ExecutionResult.md)<`Run`>>
|
|
61
|
+
|
|
62
|
+
***
|
|
63
|
+
|
|
64
|
+
### getRunOutput()[](#getrunoutput "Direct link to getRunOutput()")
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
getRunOutput(runId: number): Promise<ExecutionResult<RunOutput>>;
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Get output of a specific run.
|
|
72
|
+
|
|
73
|
+
#### Parameters[](#parameters-2 "Direct link to Parameters")
|
|
74
|
+
|
|
75
|
+
| Parameter | Type |
|
|
76
|
+
| --------- | -------- |
|
|
77
|
+
| `runId` | `number` |
|
|
78
|
+
|
|
79
|
+
#### Returns[](#returns-3 "Direct link to Returns")
|
|
80
|
+
|
|
81
|
+
`Promise`<[`ExecutionResult`](./docs/api/appkit/TypeAlias.ExecutionResult.md)<`RunOutput`>>
|
|
82
|
+
|
|
83
|
+
***
|
|
84
|
+
|
|
85
|
+
### lastRun()[](#lastrun "Direct link to lastRun()")
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
lastRun(): Promise<ExecutionResult<BaseRun | undefined>>;
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Get the most recent run for this job.
|
|
93
|
+
|
|
94
|
+
#### Returns[](#returns-4 "Direct link to Returns")
|
|
95
|
+
|
|
96
|
+
`Promise`<[`ExecutionResult`](./docs/api/appkit/TypeAlias.ExecutionResult.md)<`BaseRun` | `undefined`>>
|
|
97
|
+
|
|
98
|
+
***
|
|
99
|
+
|
|
100
|
+
### listRuns()[](#listruns "Direct link to listRuns()")
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
listRuns(options?: {
|
|
104
|
+
limit?: number;
|
|
105
|
+
}): Promise<ExecutionResult<BaseRun[]>>;
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
List runs for this job.
|
|
110
|
+
|
|
111
|
+
#### Parameters[](#parameters-3 "Direct link to Parameters")
|
|
112
|
+
|
|
113
|
+
| Parameter | Type |
|
|
114
|
+
| ---------------- | ----------------------- |
|
|
115
|
+
| `options?` | { `limit?`: `number`; } |
|
|
116
|
+
| `options.limit?` | `number` |
|
|
117
|
+
|
|
118
|
+
#### Returns[](#returns-5 "Direct link to Returns")
|
|
119
|
+
|
|
120
|
+
`Promise`<[`ExecutionResult`](./docs/api/appkit/TypeAlias.ExecutionResult.md)<`BaseRun`\[]>>
|
|
121
|
+
|
|
122
|
+
***
|
|
123
|
+
|
|
124
|
+
### runAndWait()[](#runandwait "Direct link to runAndWait()")
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
runAndWait(params?: Record<string, unknown>, signal?: AbortSignal): AsyncGenerator<JobRunStatus, void, unknown>;
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Trigger and poll until completion, yielding status updates.
|
|
132
|
+
|
|
133
|
+
#### Parameters[](#parameters-4 "Direct link to Parameters")
|
|
134
|
+
|
|
135
|
+
| Parameter | Type |
|
|
136
|
+
| --------- | ----------------------------- |
|
|
137
|
+
| `params?` | `Record`<`string`, `unknown`> |
|
|
138
|
+
| `signal?` | `AbortSignal` |
|
|
139
|
+
|
|
140
|
+
#### Returns[](#returns-6 "Direct link to Returns")
|
|
141
|
+
|
|
142
|
+
`AsyncGenerator`<`JobRunStatus`, `void`, `unknown`>
|
|
143
|
+
|
|
144
|
+
***
|
|
145
|
+
|
|
146
|
+
### runNow()[](#runnow "Direct link to runNow()")
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
runNow(params?: Record<string, unknown>): Promise<ExecutionResult<RunNowResponse>>;
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Trigger the configured job with validated params. Returns the run response.
|
|
154
|
+
|
|
155
|
+
#### Parameters[](#parameters-5 "Direct link to Parameters")
|
|
156
|
+
|
|
157
|
+
| Parameter | Type |
|
|
158
|
+
| --------- | ----------------------------- |
|
|
159
|
+
| `params?` | `Record`<`string`, `unknown`> |
|
|
160
|
+
|
|
161
|
+
#### Returns[](#returns-7 "Direct link to Returns")
|
|
162
|
+
|
|
163
|
+
`Promise`<[`ExecutionResult`](./docs/api/appkit/TypeAlias.ExecutionResult.md)<`RunNowResponse`>>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Interface: JobConfig
|
|
2
|
+
|
|
3
|
+
Per-job configuration options.
|
|
4
|
+
|
|
5
|
+
## Properties[](#properties "Direct link to Properties")
|
|
6
|
+
|
|
7
|
+
### params?[](#params "Direct link to params?")
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
optional params: ZodType<Record<string, unknown>, unknown, $ZodTypeInternals<Record<string, unknown>, unknown>>;
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Optional Zod schema for validating job parameters at runtime.
|
|
15
|
+
|
|
16
|
+
***
|
|
17
|
+
|
|
18
|
+
### taskType?[](#tasktype "Direct link to taskType?")
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
optional taskType: TaskType;
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The type of task this job runs. Determines how params are mapped to the SDK request.
|
|
26
|
+
|
|
27
|
+
***
|
|
28
|
+
|
|
29
|
+
### waitTimeout?[](#waittimeout "Direct link to waitTimeout?")
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
optional waitTimeout: number;
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Maximum time (ms) to poll in runAndWait before giving up. Defaults to 600 000 (10 min).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Type Alias: JobHandle
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
type JobHandle = JobAPI & {
|
|
5
|
+
asUser: (req: IAppRequest) => JobAPI;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Job handle returned by `appkit.jobs("etl")`. Supports OBO access via `.asUser(req)`.
|
|
11
|
+
|
|
12
|
+
## Type Declaration[](#type-declaration "Direct link to Type Declaration")
|
|
13
|
+
|
|
14
|
+
### asUser()[](#asuser "Direct link to asUser()")
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
asUser: (req: IAppRequest) => JobAPI;
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
#### Parameters[](#parameters "Direct link to Parameters")
|
|
22
|
+
|
|
23
|
+
| Parameter | Type |
|
|
24
|
+
| --------- | ------------- |
|
|
25
|
+
| `req` | `IAppRequest` |
|
|
26
|
+
|
|
27
|
+
#### Returns[](#returns "Direct link to Returns")
|
|
28
|
+
|
|
29
|
+
[`JobAPI`](./docs/api/appkit/Interface.JobAPI.md)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Type Alias: JobsExport()
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
type JobsExport = (jobKey: string) => JobHandle;
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
Public API shape of the jobs plugin. Callable to select a job by key.
|
|
9
|
+
|
|
10
|
+
## Parameters[](#parameters "Direct link to Parameters")
|
|
11
|
+
|
|
12
|
+
| Parameter | Type |
|
|
13
|
+
| --------- | -------- |
|
|
14
|
+
| `jobKey` | `string` |
|
|
15
|
+
|
|
16
|
+
## Returns[](#returns "Direct link to Returns")
|
|
17
|
+
|
|
18
|
+
[`JobHandle`](./docs/api/appkit/TypeAlias.JobHandle.md)
|
|
19
|
+
|
|
20
|
+
## Example[](#example "Direct link to Example")
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// Trigger a configured job
|
|
24
|
+
const { run_id } = await appkit.jobs("etl").runNow();
|
|
25
|
+
|
|
26
|
+
// Trigger and poll until completion
|
|
27
|
+
for await (const status of appkit.jobs("etl").runAndWait()) {
|
|
28
|
+
console.log(status.status, status.run);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// OBO access
|
|
32
|
+
await appkit.jobs("etl").asUser(req).runNow();
|
|
33
|
+
|
|
34
|
+
```
|
package/docs/api/appkit.md
CHANGED
|
@@ -37,7 +37,11 @@ Core library for building Databricks applications with type-safe SQL queries, pl
|
|
|
37
37
|
| [FilePolicyUser](./docs/api/appkit/Interface.FilePolicyUser.md) | Minimal user identity passed to the policy function. |
|
|
38
38
|
| [FileResource](./docs/api/appkit/Interface.FileResource.md) | Describes the file or directory being acted upon. |
|
|
39
39
|
| [GenerateDatabaseCredentialRequest](./docs/api/appkit/Interface.GenerateDatabaseCredentialRequest.md) | Request parameters for generating database OAuth credentials |
|
|
40
|
+
| [IJobsConfig](./docs/api/appkit/Interface.IJobsConfig.md) | Configuration for the Jobs plugin. |
|
|
40
41
|
| [ITelemetry](./docs/api/appkit/Interface.ITelemetry.md) | Plugin-facing interface for OpenTelemetry instrumentation. Provides a thin abstraction over OpenTelemetry APIs for plugins. |
|
|
42
|
+
| [JobAPI](./docs/api/appkit/Interface.JobAPI.md) | User-facing API for a single configured job. |
|
|
43
|
+
| [JobConfig](./docs/api/appkit/Interface.JobConfig.md) | Per-job configuration options. |
|
|
44
|
+
| [JobsConnectorConfig](./docs/api/appkit/Interface.JobsConnectorConfig.md) | - |
|
|
41
45
|
| [LakebasePoolConfig](./docs/api/appkit/Interface.LakebasePoolConfig.md) | Configuration for creating a Lakebase connection pool |
|
|
42
46
|
| [PluginManifest](./docs/api/appkit/Interface.PluginManifest.md) | Plugin manifest that declares metadata and resource requirements. Attached to plugin classes as a static property. Extends the shared PluginManifest with strict resource types. |
|
|
43
47
|
| [RequestedClaims](./docs/api/appkit/Interface.RequestedClaims.md) | Optional claims for fine-grained Unity Catalog table permissions When specified, the returned token will be scoped to only the requested tables |
|
|
@@ -60,6 +64,8 @@ Core library for building Databricks applications with type-safe SQL queries, pl
|
|
|
60
64
|
| [FileAction](./docs/api/appkit/TypeAlias.FileAction.md) | Every action the files plugin can perform. |
|
|
61
65
|
| [FilePolicy](./docs/api/appkit/TypeAlias.FilePolicy.md) | A policy function that decides whether `user` may perform `action` on `resource`. Return `true` to allow, `false` to deny. |
|
|
62
66
|
| [IAppRouter](./docs/api/appkit/TypeAlias.IAppRouter.md) | Express router type for plugin route registration |
|
|
67
|
+
| [JobHandle](./docs/api/appkit/TypeAlias.JobHandle.md) | Job handle returned by `appkit.jobs("etl")`. Supports OBO access via `.asUser(req)`. |
|
|
68
|
+
| [JobsExport](./docs/api/appkit/TypeAlias.JobsExport.md) | Public API shape of the jobs plugin. Callable to select a job by key. |
|
|
63
69
|
| [PluginData](./docs/api/appkit/TypeAlias.PluginData.md) | Tuple of plugin class, config, and name. Created by `toPlugin()` and passed to `createApp()`. |
|
|
64
70
|
| [ResourcePermission](./docs/api/appkit/TypeAlias.ResourcePermission.md) | Union of all possible permission levels across all resource types. |
|
|
65
71
|
| [ServingFactory](./docs/api/appkit/TypeAlias.ServingFactory.md) | Factory function returned by `AppKit.serving`. |
|