@databricks/appkit 0.0.2
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 +3 -0
- package/DCO +25 -0
- package/LICENSE +203 -0
- package/NOTICE.md +73 -0
- package/README.md +35 -0
- package/bin/setup-claude.js +190 -0
- package/dist/_virtual/rolldown_runtime.js +39 -0
- package/dist/analytics/analytics.d.ts +31 -0
- package/dist/analytics/analytics.d.ts.map +1 -0
- package/dist/analytics/analytics.js +149 -0
- package/dist/analytics/analytics.js.map +1 -0
- package/dist/analytics/defaults.js +17 -0
- package/dist/analytics/defaults.js.map +1 -0
- package/dist/analytics/index.js +3 -0
- package/dist/analytics/query.js +50 -0
- package/dist/analytics/query.js.map +1 -0
- package/dist/analytics/types.d.ts +9 -0
- package/dist/analytics/types.d.ts.map +1 -0
- package/dist/app/index.d.ts +23 -0
- package/dist/app/index.d.ts.map +1 -0
- package/dist/app/index.js +49 -0
- package/dist/app/index.js.map +1 -0
- package/dist/appkit/package.js +7 -0
- package/dist/appkit/package.js.map +1 -0
- package/dist/cache/defaults.js +14 -0
- package/dist/cache/defaults.js.map +1 -0
- package/dist/cache/index.d.ts +119 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +307 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/storage/defaults.js +16 -0
- package/dist/cache/storage/defaults.js.map +1 -0
- package/dist/cache/storage/index.js +4 -0
- package/dist/cache/storage/memory.js +87 -0
- package/dist/cache/storage/memory.js.map +1 -0
- package/dist/cache/storage/persistent.js +211 -0
- package/dist/cache/storage/persistent.js.map +1 -0
- package/dist/connectors/index.js +6 -0
- package/dist/connectors/lakebase/client.js +348 -0
- package/dist/connectors/lakebase/client.js.map +1 -0
- package/dist/connectors/lakebase/defaults.js +13 -0
- package/dist/connectors/lakebase/defaults.js.map +1 -0
- package/dist/connectors/lakebase/index.js +3 -0
- package/dist/connectors/sql-warehouse/client.js +284 -0
- package/dist/connectors/sql-warehouse/client.js.map +1 -0
- package/dist/connectors/sql-warehouse/defaults.js +12 -0
- package/dist/connectors/sql-warehouse/defaults.js.map +1 -0
- package/dist/connectors/sql-warehouse/index.js +3 -0
- package/dist/core/appkit.d.ts +14 -0
- package/dist/core/appkit.d.ts.map +1 -0
- package/dist/core/appkit.js +66 -0
- package/dist/core/appkit.js.map +1 -0
- package/dist/core/index.js +3 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin/dev-reader.d.ts +20 -0
- package/dist/plugin/dev-reader.d.ts.map +1 -0
- package/dist/plugin/dev-reader.js +63 -0
- package/dist/plugin/dev-reader.js.map +1 -0
- package/dist/plugin/index.js +4 -0
- package/dist/plugin/interceptors/cache.js +15 -0
- package/dist/plugin/interceptors/cache.js.map +1 -0
- package/dist/plugin/interceptors/retry.js +32 -0
- package/dist/plugin/interceptors/retry.js.map +1 -0
- package/dist/plugin/interceptors/telemetry.js +33 -0
- package/dist/plugin/interceptors/telemetry.js.map +1 -0
- package/dist/plugin/interceptors/timeout.js +35 -0
- package/dist/plugin/interceptors/timeout.js.map +1 -0
- package/dist/plugin/plugin.d.ts +43 -0
- package/dist/plugin/plugin.d.ts.map +1 -0
- package/dist/plugin/plugin.js +119 -0
- package/dist/plugin/plugin.js.map +1 -0
- package/dist/plugin/to-plugin.d.ts +7 -0
- package/dist/plugin/to-plugin.d.ts.map +1 -0
- package/dist/plugin/to-plugin.js +12 -0
- package/dist/plugin/to-plugin.js.map +1 -0
- package/dist/server/base-server.js +24 -0
- package/dist/server/base-server.js.map +1 -0
- package/dist/server/index.d.ts +100 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +224 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/remote-tunnel/denied.html +68 -0
- package/dist/server/remote-tunnel/gate.js +51 -0
- package/dist/server/remote-tunnel/gate.js.map +1 -0
- package/dist/server/remote-tunnel/index.html +165 -0
- package/dist/server/remote-tunnel/remote-tunnel-controller.js +100 -0
- package/dist/server/remote-tunnel/remote-tunnel-controller.js.map +1 -0
- package/dist/server/remote-tunnel/remote-tunnel-manager.js +320 -0
- package/dist/server/remote-tunnel/remote-tunnel-manager.js.map +1 -0
- package/dist/server/remote-tunnel/wait.html +158 -0
- package/dist/server/static-server.js +47 -0
- package/dist/server/static-server.js.map +1 -0
- package/dist/server/types.d.ts +14 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/utils.js +70 -0
- package/dist/server/utils.js.map +1 -0
- package/dist/server/vite-dev-server.js +103 -0
- package/dist/server/vite-dev-server.js.map +1 -0
- package/dist/shared/src/cache.d.ts +62 -0
- package/dist/shared/src/cache.d.ts.map +1 -0
- package/dist/shared/src/execute.d.ts +46 -0
- package/dist/shared/src/execute.d.ts.map +1 -0
- package/dist/shared/src/plugin.d.ts +50 -0
- package/dist/shared/src/plugin.d.ts.map +1 -0
- package/dist/shared/src/sql/helpers.d.ts +160 -0
- package/dist/shared/src/sql/helpers.d.ts.map +1 -0
- package/dist/shared/src/sql/helpers.js +103 -0
- package/dist/shared/src/sql/helpers.js.map +1 -0
- package/dist/shared/src/sql/types.d.ts +34 -0
- package/dist/shared/src/sql/types.d.ts.map +1 -0
- package/dist/shared/src/tunnel.d.ts +30 -0
- package/dist/shared/src/tunnel.d.ts.map +1 -0
- package/dist/stream/arrow-stream-processor.js +154 -0
- package/dist/stream/arrow-stream-processor.js.map +1 -0
- package/dist/stream/buffers.js +88 -0
- package/dist/stream/buffers.js.map +1 -0
- package/dist/stream/defaults.js +14 -0
- package/dist/stream/defaults.js.map +1 -0
- package/dist/stream/index.js +6 -0
- package/dist/stream/sse-writer.js +61 -0
- package/dist/stream/sse-writer.js.map +1 -0
- package/dist/stream/stream-manager.d.ts +27 -0
- package/dist/stream/stream-manager.d.ts.map +1 -0
- package/dist/stream/stream-manager.js +191 -0
- package/dist/stream/stream-manager.js.map +1 -0
- package/dist/stream/stream-registry.js +54 -0
- package/dist/stream/stream-registry.js.map +1 -0
- package/dist/stream/types.js +14 -0
- package/dist/stream/types.js.map +1 -0
- package/dist/stream/validator.js +25 -0
- package/dist/stream/validator.js.map +1 -0
- package/dist/telemetry/config.js +20 -0
- package/dist/telemetry/config.js.map +1 -0
- package/dist/telemetry/index.d.ts +4 -0
- package/dist/telemetry/index.js +8 -0
- package/dist/telemetry/instrumentations.js +38 -0
- package/dist/telemetry/instrumentations.js.map +1 -0
- package/dist/telemetry/noop.js +54 -0
- package/dist/telemetry/noop.js.map +1 -0
- package/dist/telemetry/telemetry-manager.js +113 -0
- package/dist/telemetry/telemetry-manager.js.map +1 -0
- package/dist/telemetry/telemetry-provider.js +82 -0
- package/dist/telemetry/telemetry-provider.js.map +1 -0
- package/dist/telemetry/types.d.ts +74 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/type-generator/vite-plugin.d.ts +22 -0
- package/dist/type-generator/vite-plugin.d.ts.map +1 -0
- package/dist/type-generator/vite-plugin.js +49 -0
- package/dist/type-generator/vite-plugin.js.map +1 -0
- package/dist/utils/databricks-client-middleware.d.ts +17 -0
- package/dist/utils/databricks-client-middleware.d.ts.map +1 -0
- package/dist/utils/databricks-client-middleware.js +117 -0
- package/dist/utils/databricks-client-middleware.js.map +1 -0
- package/dist/utils/env-validator.js +14 -0
- package/dist/utils/env-validator.js.map +1 -0
- package/dist/utils/index.js +26 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/merge.js +25 -0
- package/dist/utils/merge.js.map +1 -0
- package/dist/utils/vite-config-merge.js +22 -0
- package/dist/utils/vite-config-merge.js.map +1 -0
- package/llms.txt +193 -0
- package/package.json +70 -0
- package/scripts/postinstall.js +6 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-manager.js","names":["streamOperation: StreamOperation","streamEntry: StreamEntry"],"sources":["../../src/stream/stream-manager.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { IAppResponse, StreamConfig } from \"shared\";\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\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 stream(\n res: IAppResponse,\n handler: (signal: AbortSignal) => AsyncGenerator<any, void, unknown>,\n options?: StreamConfig,\n ): Promise<void> {\n const { streamId } = options || {};\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 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 // 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 };\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 // handle client disconnect\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 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 // 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 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\n return SSEErrorCode.INTERNAL_ERROR;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { RingBuffer } from "./buffers.js";
|
|
2
|
+
import { SSEErrorCode } from "./types.js";
|
|
3
|
+
|
|
4
|
+
//#region src/stream/stream-registry.ts
|
|
5
|
+
var StreamRegistry = class {
|
|
6
|
+
constructor(maxActiveStreams) {
|
|
7
|
+
this.streams = new RingBuffer(maxActiveStreams, (entry) => entry.streamId);
|
|
8
|
+
}
|
|
9
|
+
add(entry) {
|
|
10
|
+
if (this.streams.getSize() >= this.streams.capacity) this._evictOldestStream(entry.streamId);
|
|
11
|
+
this.streams.add(entry);
|
|
12
|
+
}
|
|
13
|
+
get(streamId) {
|
|
14
|
+
return this.streams.get(streamId);
|
|
15
|
+
}
|
|
16
|
+
has(streamId) {
|
|
17
|
+
return this.streams.has(streamId);
|
|
18
|
+
}
|
|
19
|
+
remove(streamId) {
|
|
20
|
+
this.streams.remove(streamId);
|
|
21
|
+
}
|
|
22
|
+
size() {
|
|
23
|
+
return this.streams.getSize();
|
|
24
|
+
}
|
|
25
|
+
clear() {
|
|
26
|
+
const allStreams = this.streams.getAll();
|
|
27
|
+
for (const stream of allStreams) stream.abortController.abort("Server shutdown");
|
|
28
|
+
this.streams.clear();
|
|
29
|
+
}
|
|
30
|
+
_evictOldestStream(excludeStreamId) {
|
|
31
|
+
const allStreams = this.streams.getAll();
|
|
32
|
+
let oldestStream = null;
|
|
33
|
+
let oldestAccess = Infinity;
|
|
34
|
+
for (const stream of allStreams) if (stream.streamId !== excludeStreamId && stream.lastAccess < oldestAccess) {
|
|
35
|
+
oldestStream = stream;
|
|
36
|
+
oldestAccess = stream.lastAccess;
|
|
37
|
+
}
|
|
38
|
+
if (oldestStream) {
|
|
39
|
+
for (const client of oldestStream.clients) if (!client.writableEnded) try {
|
|
40
|
+
client.write(`event: error\n`);
|
|
41
|
+
client.write(`data: ${JSON.stringify({
|
|
42
|
+
error: "Stream evicted",
|
|
43
|
+
code: SSEErrorCode.STREAM_EVICTED
|
|
44
|
+
})}\n\n`);
|
|
45
|
+
} catch (_error) {}
|
|
46
|
+
oldestStream.abortController.abort("Stream evicted");
|
|
47
|
+
this.streams.remove(oldestStream.streamId);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
export { StreamRegistry };
|
|
54
|
+
//# sourceMappingURL=stream-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-registry.js","names":["oldestStream: StreamEntry | null"],"sources":["../../src/stream/stream-registry.ts"],"sourcesContent":["import { RingBuffer } from \"./buffers\";\nimport { SSEErrorCode, type StreamEntry } from \"./types\";\n\nexport class StreamRegistry {\n private streams: RingBuffer<StreamEntry>;\n\n constructor(maxActiveStreams: number) {\n this.streams = new RingBuffer<StreamEntry>(\n maxActiveStreams,\n (entry) => entry.streamId,\n );\n }\n\n // add a stream to the registry\n add(entry: StreamEntry): void {\n // enforce hard cap\n if (this.streams.getSize() >= this.streams.capacity) {\n this._evictOldestStream(entry.streamId);\n }\n\n this.streams.add(entry);\n }\n\n // get a stream from the registry\n get(streamId: string): StreamEntry | null {\n return this.streams.get(streamId);\n }\n\n // check if a stream exists in the registry\n has(streamId: string): boolean {\n return this.streams.has(streamId);\n }\n\n // remove a stream from the registry\n remove(streamId: string): void {\n this.streams.remove(streamId);\n }\n\n // get the number of streams in the registry\n size(): number {\n return this.streams.getSize();\n }\n\n clear(): void {\n const allStreams = this.streams.getAll();\n\n for (const stream of allStreams) {\n stream.abortController.abort(\"Server shutdown\");\n }\n\n this.streams.clear();\n }\n\n // evict the oldest stream from the registry\n private _evictOldestStream(excludeStreamId: string): void {\n const allStreams = this.streams.getAll();\n let oldestStream: StreamEntry | null = null;\n let oldestAccess = Infinity;\n\n // find the least recently accessed stream\n for (const stream of allStreams) {\n if (\n stream.streamId !== excludeStreamId &&\n stream.lastAccess < oldestAccess\n ) {\n oldestStream = stream;\n oldestAccess = stream.lastAccess;\n }\n }\n\n // abort the oldest stream\n if (oldestStream) {\n // broadcast stream eviction error to all clients\n for (const client of oldestStream.clients) {\n if (!client.writableEnded) {\n try {\n client.write(`event: error\\n`);\n client.write(\n `data: ${JSON.stringify({ error: \"Stream evicted\", code: SSEErrorCode.STREAM_EVICTED })}\\n\\n`,\n );\n } catch (_error) {\n // ignore\n }\n }\n }\n oldestStream.abortController.abort(\"Stream evicted\");\n this.streams.remove(oldestStream.streamId);\n }\n }\n}\n"],"mappings":";;;;AAGA,IAAa,iBAAb,MAA4B;CAG1B,YAAY,kBAA0B;AACpC,OAAK,UAAU,IAAI,WACjB,mBACC,UAAU,MAAM,SAClB;;CAIH,IAAI,OAA0B;AAE5B,MAAI,KAAK,QAAQ,SAAS,IAAI,KAAK,QAAQ,SACzC,MAAK,mBAAmB,MAAM,SAAS;AAGzC,OAAK,QAAQ,IAAI,MAAM;;CAIzB,IAAI,UAAsC;AACxC,SAAO,KAAK,QAAQ,IAAI,SAAS;;CAInC,IAAI,UAA2B;AAC7B,SAAO,KAAK,QAAQ,IAAI,SAAS;;CAInC,OAAO,UAAwB;AAC7B,OAAK,QAAQ,OAAO,SAAS;;CAI/B,OAAe;AACb,SAAO,KAAK,QAAQ,SAAS;;CAG/B,QAAc;EACZ,MAAM,aAAa,KAAK,QAAQ,QAAQ;AAExC,OAAK,MAAM,UAAU,WACnB,QAAO,gBAAgB,MAAM,kBAAkB;AAGjD,OAAK,QAAQ,OAAO;;CAItB,AAAQ,mBAAmB,iBAA+B;EACxD,MAAM,aAAa,KAAK,QAAQ,QAAQ;EACxC,IAAIA,eAAmC;EACvC,IAAI,eAAe;AAGnB,OAAK,MAAM,UAAU,WACnB,KACE,OAAO,aAAa,mBACpB,OAAO,aAAa,cACpB;AACA,kBAAe;AACf,kBAAe,OAAO;;AAK1B,MAAI,cAAc;AAEhB,QAAK,MAAM,UAAU,aAAa,QAChC,KAAI,CAAC,OAAO,cACV,KAAI;AACF,WAAO,MAAM,iBAAiB;AAC9B,WAAO,MACL,SAAS,KAAK,UAAU;KAAE,OAAO;KAAkB,MAAM,aAAa;KAAgB,CAAC,CAAC,MACzF;YACM,QAAQ;AAKrB,gBAAa,gBAAgB,MAAM,iBAAiB;AACpD,QAAK,QAAQ,OAAO,aAAa,SAAS"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/stream/types.ts
|
|
2
|
+
const SSEWarningCode = { BUFFER_OVERFLOW_RESTART: "BUFFER_OVERFLOW_RESTART" };
|
|
3
|
+
const SSEErrorCode = {
|
|
4
|
+
TEMPORARY_UNAVAILABLE: "TEMPORARY_UNAVAILABLE",
|
|
5
|
+
TIMEOUT: "TIMEOUT",
|
|
6
|
+
INTERNAL_ERROR: "INTERNAL_ERROR",
|
|
7
|
+
INVALID_REQUEST: "INVALID_REQUEST",
|
|
8
|
+
STREAM_ABORTED: "STREAM_ABORTED",
|
|
9
|
+
STREAM_EVICTED: "STREAM_EVICTED"
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
export { SSEErrorCode, SSEWarningCode };
|
|
14
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../src/stream/types.ts"],"sourcesContent":["import type { IAppResponse } from \"shared\";\nimport type { EventRingBuffer } from \"./buffers\";\n\nexport const SSEWarningCode = {\n BUFFER_OVERFLOW_RESTART: \"BUFFER_OVERFLOW_RESTART\",\n} as const satisfies Record<string, string>;\n\nexport type SSEWarningCode =\n (typeof SSEWarningCode)[keyof typeof SSEWarningCode];\n\nexport const SSEErrorCode = {\n TEMPORARY_UNAVAILABLE: \"TEMPORARY_UNAVAILABLE\",\n TIMEOUT: \"TIMEOUT\",\n INTERNAL_ERROR: \"INTERNAL_ERROR\",\n INVALID_REQUEST: \"INVALID_REQUEST\",\n STREAM_ABORTED: \"STREAM_ABORTED\",\n STREAM_EVICTED: \"STREAM_EVICTED\",\n} as const satisfies Record<string, string>;\n\nexport type SSEErrorCode = (typeof SSEErrorCode)[keyof typeof SSEErrorCode];\n\nexport interface SSEError {\n error: string;\n code: SSEErrorCode;\n}\n\nexport interface BufferedEvent {\n id: string;\n type: string;\n data: string;\n timestamp: number;\n}\n\nexport interface StreamEntry {\n streamId: string;\n generator: AsyncGenerator<any, void, unknown>;\n eventBuffer: EventRingBuffer;\n clients: Set<IAppResponse>;\n isCompleted: boolean;\n lastAccess: number;\n abortController: AbortController;\n}\n\nexport interface BufferEntry {\n buffer: EventRingBuffer;\n lastAccess: number;\n}\n\nexport interface StreamOperation {\n controller: AbortController;\n type: \"query\" | \"stream\";\n heartbeat?: NodeJS.Timeout;\n}\n"],"mappings":";AAGA,MAAa,iBAAiB,EAC5B,yBAAyB,2BAC1B;AAKD,MAAa,eAAe;CAC1B,uBAAuB;CACvB,SAAS;CACT,gBAAgB;CAChB,iBAAiB;CACjB,gBAAgB;CAChB,gBAAgB;CACjB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/stream/validator.ts
|
|
2
|
+
var StreamValidator = class StreamValidator {
|
|
3
|
+
static {
|
|
4
|
+
this.UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
5
|
+
}
|
|
6
|
+
static {
|
|
7
|
+
this.STREAM_ID_REGEX = /^[a-zA-Z0-9_-]+$/;
|
|
8
|
+
}
|
|
9
|
+
static validateEventId(eventId) {
|
|
10
|
+
if (!eventId || typeof eventId !== "string" || eventId.length !== 36) return false;
|
|
11
|
+
return StreamValidator.UUID_REGEX.test(eventId);
|
|
12
|
+
}
|
|
13
|
+
static validateStreamId(streamId) {
|
|
14
|
+
if (!streamId || streamId.length === 0 || streamId.length > 256) return false;
|
|
15
|
+
if (!StreamValidator.STREAM_ID_REGEX.test(streamId)) return false;
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
static sanitizeEventType(type) {
|
|
19
|
+
return (type || "message").replace(/[\r\n]/g, "").slice(0, 100);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
export { StreamValidator };
|
|
25
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.js","names":[],"sources":["../../src/stream/validator.ts"],"sourcesContent":["export class StreamValidator {\n private static readonly UUID_REGEX =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n private static readonly STREAM_ID_REGEX = /^[a-zA-Z0-9_-]+$/;\n\n // validates eventId format and throws on invalid input\n static validateEventId(eventId?: string | string[]): boolean {\n if (!eventId || typeof eventId !== \"string\" || eventId.length !== 36) {\n return false;\n }\n\n return StreamValidator.UUID_REGEX.test(eventId);\n }\n\n // validates streamId format and throws on invalid input\n static validateStreamId(streamId?: string): boolean {\n if (!streamId || streamId.length === 0 || streamId.length > 256) {\n return false;\n }\n\n if (!StreamValidator.STREAM_ID_REGEX.test(streamId)) {\n return false;\n }\n return true;\n }\n\n // sanitizes event type for SSE format\n static sanitizeEventType(type: string): string {\n return (type || \"message\").replace(/[\\r\\n]/g, \"\").slice(0, 100);\n }\n}\n"],"mappings":";AAAA,IAAa,kBAAb,MAAa,gBAAgB;;oBAEzB;;;yBAEwC;;CAG1C,OAAO,gBAAgB,SAAsC;AAC3D,MAAI,CAAC,WAAW,OAAO,YAAY,YAAY,QAAQ,WAAW,GAChE,QAAO;AAGT,SAAO,gBAAgB,WAAW,KAAK,QAAQ;;CAIjD,OAAO,iBAAiB,UAA4B;AAClD,MAAI,CAAC,YAAY,SAAS,WAAW,KAAK,SAAS,SAAS,IAC1D,QAAO;AAGT,MAAI,CAAC,gBAAgB,gBAAgB,KAAK,SAAS,CACjD,QAAO;AAET,SAAO;;CAIT,OAAO,kBAAkB,MAAsB;AAC7C,UAAQ,QAAQ,WAAW,QAAQ,WAAW,GAAG,CAAC,MAAM,GAAG,IAAI"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/telemetry/config.ts
|
|
2
|
+
function normalizeTelemetryOptions(config) {
|
|
3
|
+
if (typeof config === "undefined" || typeof config === "boolean") {
|
|
4
|
+
const value = config ?? true;
|
|
5
|
+
return {
|
|
6
|
+
traces: value,
|
|
7
|
+
metrics: value,
|
|
8
|
+
logs: value
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
traces: config?.traces ?? true,
|
|
13
|
+
metrics: config?.metrics ?? true,
|
|
14
|
+
logs: config?.logs ?? true
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { normalizeTelemetryOptions };
|
|
20
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","names":[],"sources":["../../src/telemetry/config.ts"],"sourcesContent":["import type { TelemetryOptions } from \"shared\";\n\nexport interface TelemetryProviderConfig {\n traces: boolean;\n metrics: boolean;\n logs: boolean;\n}\n\nexport function normalizeTelemetryOptions(\n config?: TelemetryOptions,\n): TelemetryProviderConfig {\n if (typeof config === \"undefined\" || typeof config === \"boolean\") {\n const value = config ?? true;\n return {\n traces: value,\n metrics: value,\n logs: value,\n };\n }\n\n return {\n traces: config?.traces ?? true,\n metrics: config?.metrics ?? true,\n logs: config?.logs ?? true,\n };\n}\n"],"mappings":";AAQA,SAAgB,0BACd,QACyB;AACzB,KAAI,OAAO,WAAW,eAAe,OAAO,WAAW,WAAW;EAChE,MAAM,QAAQ,UAAU;AACxB,SAAO;GACL,QAAQ;GACR,SAAS;GACT,MAAM;GACP;;AAGH,QAAO;EACL,QAAQ,QAAQ,UAAU;EAC1B,SAAS,QAAQ,WAAW;EAC5B,MAAM,QAAQ,QAAQ;EACvB"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ITelemetry, InstrumentConfig, TelemetryConfig } from "./types.js";
|
|
2
|
+
import { Counter, Histogram, Span as Span$1, SpanStatusCode } from "@opentelemetry/api";
|
|
3
|
+
import { SeverityNumber } from "@opentelemetry/api-logs";
|
|
4
|
+
export { type Counter, type Histogram, SeverityNumber, type Span$1 as Span, SpanStatusCode };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { normalizeTelemetryOptions } from "./config.js";
|
|
2
|
+
import { instrumentations } from "./instrumentations.js";
|
|
3
|
+
import { TelemetryProvider } from "./telemetry-provider.js";
|
|
4
|
+
import { TelemetryManager } from "./telemetry-manager.js";
|
|
5
|
+
import { SpanKind, SpanStatusCode } from "@opentelemetry/api";
|
|
6
|
+
import { SeverityNumber } from "@opentelemetry/api-logs";
|
|
7
|
+
|
|
8
|
+
export { SeverityNumber, SpanKind, SpanStatusCode };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
|
|
2
|
+
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
|
|
3
|
+
|
|
4
|
+
//#region src/telemetry/instrumentations.ts
|
|
5
|
+
/**
|
|
6
|
+
* Registry of pre-configured instrumentations for common use cases.
|
|
7
|
+
* These can be selectively registered by plugins that need them.
|
|
8
|
+
*
|
|
9
|
+
* While instrumentations are generally safe to re-register,
|
|
10
|
+
* the recommended approach is to register them once in a corresponding plugin constructor.
|
|
11
|
+
*/
|
|
12
|
+
const instrumentations = {
|
|
13
|
+
http: new HttpInstrumentation({ applyCustomAttributesOnSpan(span, request) {
|
|
14
|
+
let spanName = null;
|
|
15
|
+
if (request.route) {
|
|
16
|
+
const fullPath = (request.baseUrl || "") + (request.url?.split("?")[0] || "");
|
|
17
|
+
if (fullPath) spanName = `${request.method} ${fullPath}`;
|
|
18
|
+
} else if (request.url) {
|
|
19
|
+
const path = request.url.split("?")[0];
|
|
20
|
+
spanName = `${request.method} ${path}`;
|
|
21
|
+
}
|
|
22
|
+
if (spanName) span.updateName(spanName);
|
|
23
|
+
} }),
|
|
24
|
+
express: new ExpressInstrumentation({ requestHook: (span, info) => {
|
|
25
|
+
const req = info.request;
|
|
26
|
+
if (info.layerType === "request_handler" && req.route) {
|
|
27
|
+
const fullPath = (req.baseUrl || "") + (req.url?.split("?")[0] || "");
|
|
28
|
+
if (fullPath) {
|
|
29
|
+
const spanName = `${req.method} ${fullPath}`;
|
|
30
|
+
span.updateName(spanName);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} })
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
export { instrumentations };
|
|
38
|
+
//# sourceMappingURL=instrumentations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrumentations.js","names":["instrumentations: Record<string, Instrumentation>","spanName: string | null"],"sources":["../../src/telemetry/instrumentations.ts"],"sourcesContent":["import { ExpressInstrumentation } from \"@opentelemetry/instrumentation-express\";\nimport { HttpInstrumentation } from \"@opentelemetry/instrumentation-http\";\nimport type { Instrumentation } from \"@opentelemetry/instrumentation\";\n\n/**\n * Registry of pre-configured instrumentations for common use cases.\n * These can be selectively registered by plugins that need them.\n *\n * While instrumentations are generally safe to re-register,\n * the recommended approach is to register them once in a corresponding plugin constructor.\n */\nexport const instrumentations: Record<string, Instrumentation> = {\n http: new HttpInstrumentation({\n applyCustomAttributesOnSpan(span: any, request: any) {\n let spanName: string | null = null;\n\n if (request.route) {\n const baseUrl = request.baseUrl || \"\";\n const url = request.url?.split(\"?\")[0] || \"\";\n const fullPath = baseUrl + url;\n if (fullPath) {\n spanName = `${request.method} ${fullPath}`;\n }\n } else if (request.url) {\n // No Express route (e.g., static assets) - use the raw URL path\n // Remove query string for cleaner trace names\n const path = request.url.split(\"?\")[0];\n spanName = `${request.method} ${path}`;\n }\n\n if (spanName) {\n span.updateName(spanName);\n }\n },\n }),\n express: new ExpressInstrumentation({\n requestHook: (span: any, info: any) => {\n const req = info.request;\n\n // Only update span name for route handlers (layerType: request_handler)\n // This ensures we're not renaming middleware spans\n if (info.layerType === \"request_handler\" && req.route) {\n // Combine baseUrl with url to get full path with actual parameter values\n // e.g., baseUrl=\"/api/analytics\" + url=\"/query/spend_data\" = \"/api/analytics/query/spend_data\"\n const baseUrl = req.baseUrl || \"\";\n const url = req.url?.split(\"?\")[0] || \"\";\n const fullPath = baseUrl + url;\n if (fullPath) {\n const spanName = `${req.method} ${fullPath}`;\n span.updateName(spanName);\n }\n }\n },\n }),\n};\n"],"mappings":";;;;;;;;;;;AAWA,MAAaA,mBAAoD;CAC/D,MAAM,IAAI,oBAAoB,EAC5B,4BAA4B,MAAW,SAAc;EACnD,IAAIC,WAA0B;AAE9B,MAAI,QAAQ,OAAO;GAGjB,MAAM,YAFU,QAAQ,WAAW,OACvB,QAAQ,KAAK,MAAM,IAAI,CAAC,MAAM;AAE1C,OAAI,SACF,YAAW,GAAG,QAAQ,OAAO,GAAG;aAEzB,QAAQ,KAAK;GAGtB,MAAM,OAAO,QAAQ,IAAI,MAAM,IAAI,CAAC;AACpC,cAAW,GAAG,QAAQ,OAAO,GAAG;;AAGlC,MAAI,SACF,MAAK,WAAW,SAAS;IAG9B,CAAC;CACF,SAAS,IAAI,uBAAuB,EAClC,cAAc,MAAW,SAAc;EACrC,MAAM,MAAM,KAAK;AAIjB,MAAI,KAAK,cAAc,qBAAqB,IAAI,OAAO;GAKrD,MAAM,YAFU,IAAI,WAAW,OACnB,IAAI,KAAK,MAAM,IAAI,CAAC,MAAM;AAEtC,OAAI,UAAU;IACZ,MAAM,WAAW,GAAG,IAAI,OAAO,GAAG;AAClC,SAAK,WAAW,SAAS;;;IAIhC,CAAC;CACH"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { INVALID_SPAN_CONTEXT, createNoopMeter } from "@opentelemetry/api";
|
|
2
|
+
import { NOOP_LOGGER } from "@opentelemetry/api-logs";
|
|
3
|
+
|
|
4
|
+
//#region src/telemetry/noop.ts
|
|
5
|
+
var NonRecordingSpan = class {
|
|
6
|
+
constructor(spanContext = INVALID_SPAN_CONTEXT) {
|
|
7
|
+
this._spanContext = spanContext;
|
|
8
|
+
}
|
|
9
|
+
spanContext() {
|
|
10
|
+
return this._spanContext;
|
|
11
|
+
}
|
|
12
|
+
setAttribute(_key, _value) {
|
|
13
|
+
return this;
|
|
14
|
+
}
|
|
15
|
+
setAttributes(_attributes) {
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
addEvent(_name, _attributesOrStartTime, _startTime) {
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
addLink(_link) {
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
addLinks(_links) {
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
setStatus(_status) {
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
updateName(_name) {
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
end(_endTime) {}
|
|
34
|
+
isRecording() {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
recordException(_exception, _time) {}
|
|
38
|
+
};
|
|
39
|
+
var NoopTracer = class {
|
|
40
|
+
startSpan(_name, _options, _context) {
|
|
41
|
+
return new NonRecordingSpan(INVALID_SPAN_CONTEXT);
|
|
42
|
+
}
|
|
43
|
+
startActiveSpan(_name, ...args) {
|
|
44
|
+
const fn = args[args.length - 1];
|
|
45
|
+
if (typeof fn !== "function") return;
|
|
46
|
+
return fn(new NonRecordingSpan(INVALID_SPAN_CONTEXT));
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const NOOP_TRACER = new NoopTracer();
|
|
50
|
+
const NOOP_METER = createNoopMeter();
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
export { NOOP_LOGGER, NOOP_METER, NOOP_TRACER };
|
|
54
|
+
//# sourceMappingURL=noop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noop.js","names":[],"sources":["../../src/telemetry/noop.ts"],"sourcesContent":["// Our own noop tracer implementation.\n// Why?\n// Unfortunately, noop tracer is not exported from the api package, unlike noop meter and noop logger.\n// Read more: https://github.com/open-telemetry/opentelemetry-js/issues/3455\n// and https://github.com/open-telemetry/opentelemetry-js/issues/4518\n//\n// The original implementation is here: https://github.com/open-telemetry/opentelemetry-js/blob/a7acd9355cd0c1da63d285dfb960efeacc3cbc15/api/src/trace/NoopTracer.ts#L32\n// licensed under the Apache License 2.0.\n// Our own implementation is much simpler but will do the job for our needs.\n\nimport type {\n Context,\n Span,\n SpanContext,\n SpanOptions,\n Tracer,\n} from \"@opentelemetry/api\";\nimport {\n createNoopMeter,\n INVALID_SPAN_CONTEXT,\n type SpanStatusCode,\n} from \"@opentelemetry/api\";\n\nclass NonRecordingSpan implements Span {\n private readonly _spanContext: SpanContext;\n\n constructor(spanContext: SpanContext = INVALID_SPAN_CONTEXT) {\n this._spanContext = spanContext;\n }\n\n spanContext(): SpanContext {\n return this._spanContext;\n }\n\n setAttribute(_key: string, _value: any): this {\n return this;\n }\n\n setAttributes(_attributes: any): this {\n return this;\n }\n\n addEvent(\n _name: string,\n _attributesOrStartTime?: any,\n _startTime?: any,\n ): this {\n return this;\n }\n\n addLink(_link: any): this {\n return this;\n }\n\n addLinks(_links: any[]): this {\n return this;\n }\n\n setStatus(_status: { code: SpanStatusCode; message?: string }): this {\n return this;\n }\n\n updateName(_name: string): this {\n return this;\n }\n\n end(_endTime?: number): void {}\n\n isRecording(): boolean {\n return false;\n }\n\n recordException(_exception: any, _time?: number): void {}\n}\n\nexport class NoopTracer implements Tracer {\n startSpan(_name: string, _options?: SpanOptions, _context?: Context): Span {\n return new NonRecordingSpan(INVALID_SPAN_CONTEXT);\n }\n\n startActiveSpan<F extends (span: Span) => any>(\n _name: string,\n ...args: [F] | [SpanOptions, F] | [SpanOptions, Context, F]\n ): ReturnType<F> | undefined {\n const fn = args[args.length - 1] as F;\n\n if (typeof fn !== \"function\") {\n return undefined as ReturnType<F>;\n }\n\n return fn(new NonRecordingSpan(INVALID_SPAN_CONTEXT));\n }\n}\n\nexport const NOOP_TRACER = new NoopTracer();\nexport const NOOP_METER = createNoopMeter();\nexport { NOOP_LOGGER } from \"@opentelemetry/api-logs\";\n"],"mappings":";;;;AAuBA,IAAM,mBAAN,MAAuC;CAGrC,YAAY,cAA2B,sBAAsB;AAC3D,OAAK,eAAe;;CAGtB,cAA2B;AACzB,SAAO,KAAK;;CAGd,aAAa,MAAc,QAAmB;AAC5C,SAAO;;CAGT,cAAc,aAAwB;AACpC,SAAO;;CAGT,SACE,OACA,wBACA,YACM;AACN,SAAO;;CAGT,QAAQ,OAAkB;AACxB,SAAO;;CAGT,SAAS,QAAqB;AAC5B,SAAO;;CAGT,UAAU,SAA2D;AACnE,SAAO;;CAGT,WAAW,OAAqB;AAC9B,SAAO;;CAGT,IAAI,UAAyB;CAE7B,cAAuB;AACrB,SAAO;;CAGT,gBAAgB,YAAiB,OAAsB;;AAGzD,IAAa,aAAb,MAA0C;CACxC,UAAU,OAAe,UAAwB,UAA0B;AACzE,SAAO,IAAI,iBAAiB,qBAAqB;;CAGnD,gBACE,OACA,GAAG,MACwB;EAC3B,MAAM,KAAK,KAAK,KAAK,SAAS;AAE9B,MAAI,OAAO,OAAO,WAChB;AAGF,SAAO,GAAG,IAAI,iBAAiB,qBAAqB,CAAC;;;AAIzD,MAAa,cAAc,IAAI,YAAY;AAC3C,MAAa,aAAa,iBAAiB"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { TelemetryProvider } from "./telemetry-provider.js";
|
|
2
|
+
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
|
3
|
+
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto";
|
|
4
|
+
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto";
|
|
5
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
6
|
+
import { registerInstrumentations } from "@opentelemetry/instrumentation";
|
|
7
|
+
import { detectResources, envDetector, hostDetector, processDetector, resourceFromAttributes } from "@opentelemetry/resources";
|
|
8
|
+
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
|
|
9
|
+
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
10
|
+
import { AlwaysOnSampler } from "@opentelemetry/sdk-trace-base";
|
|
11
|
+
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
|
|
12
|
+
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
13
|
+
|
|
14
|
+
//#region src/telemetry/telemetry-manager.ts
|
|
15
|
+
var TelemetryManager = class TelemetryManager {
|
|
16
|
+
static {
|
|
17
|
+
this.DEFAULT_EXPORT_INTERVAL_MS = 1e4;
|
|
18
|
+
}
|
|
19
|
+
static {
|
|
20
|
+
this.DEFAULT_FALLBACK_APP_NAME = "databricks-app";
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Create a scoped telemetry provider for a specific plugin.
|
|
24
|
+
* The plugin's name will be used as the default tracer/meter name.
|
|
25
|
+
* @param pluginName - The name of the plugin to create scoped telemetry for
|
|
26
|
+
* @param telemetryConfig - The telemetry configuration for the plugin
|
|
27
|
+
* @returns A scoped telemetry instance for the plugin
|
|
28
|
+
*/
|
|
29
|
+
static getProvider(pluginName, telemetryConfig) {
|
|
30
|
+
return new TelemetryProvider(pluginName, TelemetryManager.getInstance(), telemetryConfig);
|
|
31
|
+
}
|
|
32
|
+
constructor() {}
|
|
33
|
+
static getInstance() {
|
|
34
|
+
if (!TelemetryManager.instance) TelemetryManager.instance = new TelemetryManager();
|
|
35
|
+
return TelemetryManager.instance;
|
|
36
|
+
}
|
|
37
|
+
static initialize(config = {}) {
|
|
38
|
+
TelemetryManager.getInstance()._initialize(config);
|
|
39
|
+
}
|
|
40
|
+
_initialize(config) {
|
|
41
|
+
if (this.sdk) return;
|
|
42
|
+
if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return;
|
|
43
|
+
try {
|
|
44
|
+
this.sdk = new NodeSDK({
|
|
45
|
+
resource: this.createResource(config),
|
|
46
|
+
autoDetectResources: false,
|
|
47
|
+
sampler: new AlwaysOnSampler(),
|
|
48
|
+
traceExporter: new OTLPTraceExporter({ headers: config.headers }),
|
|
49
|
+
metricReaders: [new PeriodicExportingMetricReader({
|
|
50
|
+
exporter: new OTLPMetricExporter({ headers: config.headers }),
|
|
51
|
+
exportIntervalMillis: config.exportIntervalMs || TelemetryManager.DEFAULT_EXPORT_INTERVAL_MS
|
|
52
|
+
})],
|
|
53
|
+
logRecordProcessors: [new BatchLogRecordProcessor(new OTLPLogExporter({ headers: config.headers }))],
|
|
54
|
+
instrumentations: this.getDefaultInstrumentations()
|
|
55
|
+
});
|
|
56
|
+
this.sdk.start();
|
|
57
|
+
this.registerShutdown();
|
|
58
|
+
console.log("[Telemetry] Initialized successfully");
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error("[Telemetry] Failed to initialize:", error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Register OpenTelemetry instrumentations.
|
|
65
|
+
* Can be called at any time, but recommended to call in plugin constructor.
|
|
66
|
+
* @param instrumentations - Array of OpenTelemetry instrumentations to register
|
|
67
|
+
*/
|
|
68
|
+
registerInstrumentations(instrumentations) {
|
|
69
|
+
registerInstrumentations({ instrumentations });
|
|
70
|
+
}
|
|
71
|
+
createResource(config) {
|
|
72
|
+
const serviceName = config.serviceName || process.env.OTEL_SERVICE_NAME || process.env.DATABRICKS_APP_NAME || TelemetryManager.DEFAULT_FALLBACK_APP_NAME;
|
|
73
|
+
const initialResource = resourceFromAttributes({
|
|
74
|
+
[ATTR_SERVICE_NAME]: serviceName,
|
|
75
|
+
[ATTR_SERVICE_VERSION]: config.serviceVersion ?? void 0
|
|
76
|
+
});
|
|
77
|
+
const detectedResource = detectResources({ detectors: [
|
|
78
|
+
envDetector,
|
|
79
|
+
hostDetector,
|
|
80
|
+
processDetector
|
|
81
|
+
] });
|
|
82
|
+
return initialResource.merge(detectedResource);
|
|
83
|
+
}
|
|
84
|
+
getDefaultInstrumentations() {
|
|
85
|
+
return [...getNodeAutoInstrumentations({
|
|
86
|
+
"@opentelemetry/instrumentation-http": { enabled: false },
|
|
87
|
+
"@opentelemetry/instrumentation-express": { enabled: false },
|
|
88
|
+
"@opentelemetry/instrumentation-fs": { enabled: false },
|
|
89
|
+
"@opentelemetry/instrumentation-dns": { enabled: false },
|
|
90
|
+
"@opentelemetry/instrumentation-net": { enabled: false }
|
|
91
|
+
})];
|
|
92
|
+
}
|
|
93
|
+
registerShutdown() {
|
|
94
|
+
const shutdownFn = async () => {
|
|
95
|
+
await TelemetryManager.getInstance().shutdown();
|
|
96
|
+
};
|
|
97
|
+
process.once("SIGTERM", shutdownFn);
|
|
98
|
+
process.once("SIGINT", shutdownFn);
|
|
99
|
+
}
|
|
100
|
+
async shutdown() {
|
|
101
|
+
if (!this.sdk) return;
|
|
102
|
+
try {
|
|
103
|
+
await this.sdk.shutdown();
|
|
104
|
+
this.sdk = void 0;
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error("[Telemetry] Error shutting down:", error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
export { TelemetryManager };
|
|
113
|
+
//# sourceMappingURL=telemetry-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry-manager.js","names":[],"sources":["../../src/telemetry/telemetry-manager.ts"],"sourcesContent":["import type { TelemetryOptions } from \"shared\";\nimport { getNodeAutoInstrumentations } from \"@opentelemetry/auto-instrumentations-node\";\nimport { OTLPLogExporter } from \"@opentelemetry/exporter-logs-otlp-proto\";\nimport { OTLPMetricExporter } from \"@opentelemetry/exporter-metrics-otlp-proto\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-proto\";\nimport {\n type Instrumentation,\n registerInstrumentations as otelRegisterInstrumentations,\n} from \"@opentelemetry/instrumentation\";\nimport {\n detectResources,\n envDetector,\n hostDetector,\n processDetector,\n type Resource,\n resourceFromAttributes,\n} from \"@opentelemetry/resources\";\nimport { BatchLogRecordProcessor } from \"@opentelemetry/sdk-logs\";\nimport { PeriodicExportingMetricReader } from \"@opentelemetry/sdk-metrics\";\nimport { AlwaysOnSampler } from \"@opentelemetry/sdk-trace-base\";\nimport {\n ATTR_SERVICE_NAME,\n ATTR_SERVICE_VERSION,\n} from \"@opentelemetry/semantic-conventions\";\nimport { TelemetryProvider } from \"./telemetry-provider\";\nimport type { TelemetryConfig } from \"./types\";\nimport { NodeSDK } from \"@opentelemetry/sdk-node\";\n\nexport class TelemetryManager {\n private static readonly DEFAULT_EXPORT_INTERVAL_MS = 10000;\n private static readonly DEFAULT_FALLBACK_APP_NAME = \"databricks-app\";\n\n private static instance?: TelemetryManager;\n private sdk?: NodeSDK;\n\n /**\n * Create a scoped telemetry provider for a specific plugin.\n * The plugin's name will be used as the default tracer/meter name.\n * @param pluginName - The name of the plugin to create scoped telemetry for\n * @param telemetryConfig - The telemetry configuration for the plugin\n * @returns A scoped telemetry instance for the plugin\n */\n static getProvider(\n pluginName: string,\n telemetryConfig?: TelemetryOptions,\n ): TelemetryProvider {\n const globalManager = TelemetryManager.getInstance();\n return new TelemetryProvider(pluginName, globalManager, telemetryConfig);\n }\n\n private constructor() {}\n\n static getInstance(): TelemetryManager {\n if (!TelemetryManager.instance) {\n TelemetryManager.instance = new TelemetryManager();\n }\n return TelemetryManager.instance;\n }\n\n static initialize(config: Partial<TelemetryConfig> = {}): void {\n const instance = TelemetryManager.getInstance();\n instance._initialize(config);\n }\n\n private _initialize(config: Partial<TelemetryConfig>): void {\n if (this.sdk) return;\n\n if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {\n return;\n }\n\n try {\n this.sdk = new NodeSDK({\n resource: this.createResource(config),\n autoDetectResources: false,\n sampler: new AlwaysOnSampler(),\n traceExporter: new OTLPTraceExporter({ headers: config.headers }),\n metricReaders: [\n new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({ headers: config.headers }),\n exportIntervalMillis:\n config.exportIntervalMs ||\n TelemetryManager.DEFAULT_EXPORT_INTERVAL_MS,\n }),\n ],\n logRecordProcessors: [\n new BatchLogRecordProcessor(\n new OTLPLogExporter({ headers: config.headers }),\n ),\n ],\n instrumentations: this.getDefaultInstrumentations(),\n });\n\n this.sdk.start();\n this.registerShutdown();\n console.log(\"[Telemetry] Initialized successfully\");\n } catch (error) {\n console.error(\"[Telemetry] Failed to initialize:\", error);\n }\n }\n\n /**\n * Register OpenTelemetry instrumentations.\n * Can be called at any time, but recommended to call in plugin constructor.\n * @param instrumentations - Array of OpenTelemetry instrumentations to register\n */\n registerInstrumentations(instrumentations: Instrumentation[]): void {\n otelRegisterInstrumentations({\n // global providers set by NodeSDK.start()\n instrumentations,\n });\n }\n\n private createResource(config: Partial<TelemetryConfig>): Resource {\n const serviceName =\n config.serviceName ||\n process.env.OTEL_SERVICE_NAME ||\n process.env.DATABRICKS_APP_NAME ||\n TelemetryManager.DEFAULT_FALLBACK_APP_NAME;\n const initialResource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: serviceName,\n [ATTR_SERVICE_VERSION]: config.serviceVersion ?? undefined,\n });\n const detectedResource = detectResources({\n detectors: [envDetector, hostDetector, processDetector],\n });\n return initialResource.merge(detectedResource);\n }\n\n private getDefaultInstrumentations(): Instrumentation[] {\n return [\n ...getNodeAutoInstrumentations({\n //\n // enabled as a part of the server plugin\n //\n \"@opentelemetry/instrumentation-http\": {\n enabled: false,\n },\n \"@opentelemetry/instrumentation-express\": {\n enabled: false,\n },\n //\n // reduce noise\n //\n \"@opentelemetry/instrumentation-fs\": {\n enabled: false,\n },\n \"@opentelemetry/instrumentation-dns\": {\n enabled: false,\n },\n \"@opentelemetry/instrumentation-net\": {\n enabled: false,\n },\n }),\n ];\n }\n\n private registerShutdown() {\n const shutdownFn = async () => {\n await TelemetryManager.getInstance().shutdown();\n };\n process.once(\"SIGTERM\", shutdownFn);\n process.once(\"SIGINT\", shutdownFn);\n }\n\n private async shutdown(): Promise<void> {\n if (!this.sdk) {\n return;\n }\n\n try {\n await this.sdk.shutdown();\n this.sdk = undefined;\n } catch (error) {\n console.error(\"[Telemetry] Error shutting down:\", error);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AA4BA,IAAa,mBAAb,MAAa,iBAAiB;;oCACyB;;;mCACD;;;;;;;;;CAYpD,OAAO,YACL,YACA,iBACmB;AAEnB,SAAO,IAAI,kBAAkB,YADP,iBAAiB,aAAa,EACI,gBAAgB;;CAG1E,AAAQ,cAAc;CAEtB,OAAO,cAAgC;AACrC,MAAI,CAAC,iBAAiB,SACpB,kBAAiB,WAAW,IAAI,kBAAkB;AAEpD,SAAO,iBAAiB;;CAG1B,OAAO,WAAW,SAAmC,EAAE,EAAQ;AAE7D,EADiB,iBAAiB,aAAa,CACtC,YAAY,OAAO;;CAG9B,AAAQ,YAAY,QAAwC;AAC1D,MAAI,KAAK,IAAK;AAEd,MAAI,CAAC,QAAQ,IAAI,4BACf;AAGF,MAAI;AACF,QAAK,MAAM,IAAI,QAAQ;IACrB,UAAU,KAAK,eAAe,OAAO;IACrC,qBAAqB;IACrB,SAAS,IAAI,iBAAiB;IAC9B,eAAe,IAAI,kBAAkB,EAAE,SAAS,OAAO,SAAS,CAAC;IACjE,eAAe,CACb,IAAI,8BAA8B;KAChC,UAAU,IAAI,mBAAmB,EAAE,SAAS,OAAO,SAAS,CAAC;KAC7D,sBACE,OAAO,oBACP,iBAAiB;KACpB,CAAC,CACH;IACD,qBAAqB,CACnB,IAAI,wBACF,IAAI,gBAAgB,EAAE,SAAS,OAAO,SAAS,CAAC,CACjD,CACF;IACD,kBAAkB,KAAK,4BAA4B;IACpD,CAAC;AAEF,QAAK,IAAI,OAAO;AAChB,QAAK,kBAAkB;AACvB,WAAQ,IAAI,uCAAuC;WAC5C,OAAO;AACd,WAAQ,MAAM,qCAAqC,MAAM;;;;;;;;CAS7D,yBAAyB,kBAA2C;AAClE,2BAA6B,EAE3B,kBACD,CAAC;;CAGJ,AAAQ,eAAe,QAA4C;EACjE,MAAM,cACJ,OAAO,eACP,QAAQ,IAAI,qBACZ,QAAQ,IAAI,uBACZ,iBAAiB;EACnB,MAAM,kBAAkB,uBAAuB;IAC5C,oBAAoB;IACpB,uBAAuB,OAAO,kBAAkB;GAClD,CAAC;EACF,MAAM,mBAAmB,gBAAgB,EACvC,WAAW;GAAC;GAAa;GAAc;GAAgB,EACxD,CAAC;AACF,SAAO,gBAAgB,MAAM,iBAAiB;;CAGhD,AAAQ,6BAAgD;AACtD,SAAO,CACL,GAAG,4BAA4B;GAI7B,uCAAuC,EACrC,SAAS,OACV;GACD,0CAA0C,EACxC,SAAS,OACV;GAID,qCAAqC,EACnC,SAAS,OACV;GACD,sCAAsC,EACpC,SAAS,OACV;GACD,sCAAsC,EACpC,SAAS,OACV;GACF,CAAC,CACH;;CAGH,AAAQ,mBAAmB;EACzB,MAAM,aAAa,YAAY;AAC7B,SAAM,iBAAiB,aAAa,CAAC,UAAU;;AAEjD,UAAQ,KAAK,WAAW,WAAW;AACnC,UAAQ,KAAK,UAAU,WAAW;;CAGpC,MAAc,WAA0B;AACtC,MAAI,CAAC,KAAK,IACR;AAGF,MAAI;AACF,SAAM,KAAK,IAAI,UAAU;AACzB,QAAK,MAAM;WACJ,OAAO;AACd,WAAQ,MAAM,oCAAoC,MAAM"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { normalizeTelemetryOptions } from "./config.js";
|
|
2
|
+
import { NOOP_LOGGER, NOOP_METER, NOOP_TRACER } from "./noop.js";
|
|
3
|
+
import { metrics, trace } from "@opentelemetry/api";
|
|
4
|
+
import { logs } from "@opentelemetry/api-logs";
|
|
5
|
+
|
|
6
|
+
//#region src/telemetry/telemetry-provider.ts
|
|
7
|
+
/**
|
|
8
|
+
* Scoped telemetry instance for specific plugins and other classes.
|
|
9
|
+
* Automatically uses the plugin name as the default tracer/meter name.
|
|
10
|
+
*/
|
|
11
|
+
var TelemetryProvider = class {
|
|
12
|
+
constructor(pluginName, globalManager, telemetryConfig) {
|
|
13
|
+
this.pluginName = pluginName;
|
|
14
|
+
this.globalManager = globalManager;
|
|
15
|
+
this.config = normalizeTelemetryOptions(telemetryConfig);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Gets a tracer for creating spans.
|
|
19
|
+
* @param options - Optional tracer configuration.
|
|
20
|
+
*/
|
|
21
|
+
getTracer(options) {
|
|
22
|
+
if (!this.config.traces) return NOOP_TRACER;
|
|
23
|
+
const tracerName = this.getInstrumentName(options);
|
|
24
|
+
return trace.getTracer(tracerName);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Gets a meter for recording metrics.
|
|
28
|
+
* @param config - Optional meter configuration.
|
|
29
|
+
*/
|
|
30
|
+
getMeter(config) {
|
|
31
|
+
if (!this.config.metrics) return NOOP_METER;
|
|
32
|
+
const meterName = this.getInstrumentName(config);
|
|
33
|
+
return metrics.getMeter(meterName);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Gets a logger for emitting log records.
|
|
37
|
+
* @param config - Optional logger configuration.
|
|
38
|
+
*/
|
|
39
|
+
getLogger(config) {
|
|
40
|
+
if (!this.config.logs) return NOOP_LOGGER;
|
|
41
|
+
const loggerName = this.getInstrumentName(config);
|
|
42
|
+
return logs.getLogger(loggerName);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Emits a log record using the default logger.
|
|
46
|
+
* Convenience method for logging without explicitly getting a logger.
|
|
47
|
+
* @param logRecord - The log record to emit
|
|
48
|
+
*/
|
|
49
|
+
emit(logRecord) {
|
|
50
|
+
this.getLogger().emit(logRecord);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Register OpenTelemetry instrumentations.
|
|
54
|
+
* Can be called at any time, but recommended to call in plugin constructor.
|
|
55
|
+
* @param instrumentations - Array of OpenTelemetry instrumentations to register
|
|
56
|
+
*/
|
|
57
|
+
registerInstrumentations(instrumentations) {
|
|
58
|
+
if (!this.config.traces) return;
|
|
59
|
+
this.globalManager.registerInstrumentations(instrumentations);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Starts an active span and executes a callback function within its context.
|
|
63
|
+
* Uses the plugin's default tracer unless custom tracer options are provided.
|
|
64
|
+
* @param name - The name of the span
|
|
65
|
+
* @param options - Span options including attributes, kind, etc.
|
|
66
|
+
* @param fn - Callback function to execute within the span context
|
|
67
|
+
* @param tracerOptions - Optional tracer configuration
|
|
68
|
+
* @returns Promise resolving to the callback's return value
|
|
69
|
+
*/
|
|
70
|
+
startActiveSpan(name, options, fn, tracerOptions) {
|
|
71
|
+
return this.getTracer(tracerOptions).startActiveSpan(name, options, fn);
|
|
72
|
+
}
|
|
73
|
+
getInstrumentName(options) {
|
|
74
|
+
const prefix = this.pluginName;
|
|
75
|
+
if (!options || !options.name) return this.pluginName;
|
|
76
|
+
return options.includePrefix ? `${prefix}-${options.name}` : options.name;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
export { TelemetryProvider };
|
|
82
|
+
//# sourceMappingURL=telemetry-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry-provider.js","names":[],"sources":["../../src/telemetry/telemetry-provider.ts"],"sourcesContent":["import type { TelemetryOptions } from \"shared\";\nimport type { Meter, Span, SpanOptions, Tracer } from \"@opentelemetry/api\";\nimport { metrics, trace } from \"@opentelemetry/api\";\nimport { type Logger, type LogRecord, logs } from \"@opentelemetry/api-logs\";\nimport type { Instrumentation } from \"@opentelemetry/instrumentation\";\nimport {\n normalizeTelemetryOptions,\n type TelemetryProviderConfig,\n} from \"./config\";\nimport { NOOP_LOGGER, NOOP_METER, NOOP_TRACER } from \"./noop\";\nimport type { TelemetryManager } from \"./telemetry-manager\";\nimport type { InstrumentConfig, ITelemetry } from \"./types\";\n\n/**\n * Scoped telemetry instance for specific plugins and other classes.\n * Automatically uses the plugin name as the default tracer/meter name.\n */\nexport class TelemetryProvider implements ITelemetry {\n private readonly pluginName: string;\n private readonly globalManager: TelemetryManager;\n private readonly config: TelemetryProviderConfig;\n\n constructor(\n pluginName: string,\n globalManager: TelemetryManager,\n telemetryConfig?: TelemetryOptions,\n ) {\n this.pluginName = pluginName;\n this.globalManager = globalManager;\n this.config = normalizeTelemetryOptions(telemetryConfig);\n }\n\n /**\n * Gets a tracer for creating spans.\n * @param options - Optional tracer configuration.\n */\n getTracer(options?: InstrumentConfig): Tracer {\n if (!this.config.traces) {\n return NOOP_TRACER;\n }\n\n const tracerName = this.getInstrumentName(options);\n return trace.getTracer(tracerName);\n }\n\n /**\n * Gets a meter for recording metrics.\n * @param config - Optional meter configuration.\n */\n getMeter(config?: InstrumentConfig): Meter {\n if (!this.config.metrics) {\n return NOOP_METER;\n }\n\n const meterName = this.getInstrumentName(config);\n return metrics.getMeter(meterName);\n }\n\n /**\n * Gets a logger for emitting log records.\n * @param config - Optional logger configuration.\n */\n getLogger(config?: InstrumentConfig): Logger {\n if (!this.config.logs) {\n return NOOP_LOGGER;\n }\n\n const loggerName = this.getInstrumentName(config);\n return logs.getLogger(loggerName);\n }\n\n /**\n * Emits a log record using the default logger.\n * Convenience method for logging without explicitly getting a logger.\n * @param logRecord - The log record to emit\n */\n emit(logRecord: LogRecord): void {\n const logger = this.getLogger();\n logger.emit(logRecord);\n }\n\n /**\n * Register OpenTelemetry instrumentations.\n * Can be called at any time, but recommended to call in plugin constructor.\n * @param instrumentations - Array of OpenTelemetry instrumentations to register\n */\n registerInstrumentations(instrumentations: Instrumentation[]): void {\n if (!this.config.traces) {\n return;\n }\n\n this.globalManager.registerInstrumentations(instrumentations);\n }\n\n /**\n * Starts an active span and executes a callback function within its context.\n * Uses the plugin's default tracer unless custom tracer options are provided.\n * @param name - The name of the span\n * @param options - Span options including attributes, kind, etc.\n * @param fn - Callback function to execute within the span context\n * @param tracerOptions - Optional tracer configuration\n * @returns Promise resolving to the callback's return value\n */\n startActiveSpan<T>(\n name: string,\n options: SpanOptions,\n fn: (span: Span) => Promise<T>,\n tracerOptions?: InstrumentConfig,\n ): Promise<T> {\n const tracer = this.getTracer(tracerOptions);\n return tracer.startActiveSpan(name, options, fn);\n }\n\n private getInstrumentName(options?: InstrumentConfig): string {\n const prefix = this.pluginName;\n if (!options || !options.name) {\n return this.pluginName;\n }\n\n return options.includePrefix ? `${prefix}-${options.name}` : options.name;\n }\n}\n"],"mappings":";;;;;;;;;;AAiBA,IAAa,oBAAb,MAAqD;CAKnD,YACE,YACA,eACA,iBACA;AACA,OAAK,aAAa;AAClB,OAAK,gBAAgB;AACrB,OAAK,SAAS,0BAA0B,gBAAgB;;;;;;CAO1D,UAAU,SAAoC;AAC5C,MAAI,CAAC,KAAK,OAAO,OACf,QAAO;EAGT,MAAM,aAAa,KAAK,kBAAkB,QAAQ;AAClD,SAAO,MAAM,UAAU,WAAW;;;;;;CAOpC,SAAS,QAAkC;AACzC,MAAI,CAAC,KAAK,OAAO,QACf,QAAO;EAGT,MAAM,YAAY,KAAK,kBAAkB,OAAO;AAChD,SAAO,QAAQ,SAAS,UAAU;;;;;;CAOpC,UAAU,QAAmC;AAC3C,MAAI,CAAC,KAAK,OAAO,KACf,QAAO;EAGT,MAAM,aAAa,KAAK,kBAAkB,OAAO;AACjD,SAAO,KAAK,UAAU,WAAW;;;;;;;CAQnC,KAAK,WAA4B;AAE/B,EADe,KAAK,WAAW,CACxB,KAAK,UAAU;;;;;;;CAQxB,yBAAyB,kBAA2C;AAClE,MAAI,CAAC,KAAK,OAAO,OACf;AAGF,OAAK,cAAc,yBAAyB,iBAAiB;;;;;;;;;;;CAY/D,gBACE,MACA,SACA,IACA,eACY;AAEZ,SADe,KAAK,UAAU,cAAc,CAC9B,gBAAgB,MAAM,SAAS,GAAG;;CAGlD,AAAQ,kBAAkB,SAAoC;EAC5D,MAAM,SAAS,KAAK;AACpB,MAAI,CAAC,WAAW,CAAC,QAAQ,KACvB,QAAO,KAAK;AAGd,SAAO,QAAQ,gBAAgB,GAAG,OAAO,GAAG,QAAQ,SAAS,QAAQ"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Meter, Span, SpanOptions, Tracer } from "@opentelemetry/api";
|
|
2
|
+
import { LogRecord, Logger } from "@opentelemetry/api-logs";
|
|
3
|
+
import { Instrumentation } from "@opentelemetry/instrumentation";
|
|
4
|
+
|
|
5
|
+
//#region src/telemetry/types.d.ts
|
|
6
|
+
interface TelemetryConfig {
|
|
7
|
+
serviceName?: string;
|
|
8
|
+
serviceVersion?: string;
|
|
9
|
+
instrumentations?: Instrumentation[];
|
|
10
|
+
exportIntervalMs?: number;
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Instrument customization options.
|
|
15
|
+
*/
|
|
16
|
+
interface InstrumentConfig {
|
|
17
|
+
/**
|
|
18
|
+
* The name of the instrument.
|
|
19
|
+
*/
|
|
20
|
+
name?: string;
|
|
21
|
+
/**
|
|
22
|
+
* If true, the prefix from the context (e.g. plugin name) will be kept when constructing the final instrument name.
|
|
23
|
+
* For example, if the plugin name is "my-plugin" and the instrument name is "my-instrument", the final instrument name will be "my-plugin-my-instrument".
|
|
24
|
+
* If false, the final instrument name will be "my-instrument".
|
|
25
|
+
*/
|
|
26
|
+
includePrefix?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Plugin-facing interface for OpenTelemetry instrumentation.
|
|
30
|
+
* Provides a thin abstraction over OpenTelemetry APIs for plugins.
|
|
31
|
+
*/
|
|
32
|
+
interface ITelemetry {
|
|
33
|
+
/**
|
|
34
|
+
* Gets a tracer for creating spans.
|
|
35
|
+
* @param options - Instrument customization options.
|
|
36
|
+
*/
|
|
37
|
+
getTracer(options?: InstrumentConfig): Tracer;
|
|
38
|
+
/**
|
|
39
|
+
* Gets a meter for recording metrics.
|
|
40
|
+
* @param options - Instrument customization options.
|
|
41
|
+
*/
|
|
42
|
+
getMeter(options?: InstrumentConfig): Meter;
|
|
43
|
+
/**
|
|
44
|
+
* Gets a logger for emitting log records.
|
|
45
|
+
* @param options - Instrument customization options.
|
|
46
|
+
*/
|
|
47
|
+
getLogger(options?: InstrumentConfig): Logger;
|
|
48
|
+
/**
|
|
49
|
+
* Emits a log record using the default logger.
|
|
50
|
+
* Respects the logs enabled/disabled config.
|
|
51
|
+
* @param logRecord - The log record to emit
|
|
52
|
+
*/
|
|
53
|
+
emit(logRecord: LogRecord): void;
|
|
54
|
+
/**
|
|
55
|
+
* Starts an active span and executes a callback function within its context.
|
|
56
|
+
* Respects the traces enabled/disabled config.
|
|
57
|
+
* When traces are disabled, executes the callback with a no-op span.
|
|
58
|
+
* @param name - The name of the span
|
|
59
|
+
* @param options - Span options including attributes, kind, etc.
|
|
60
|
+
* @param fn - Callback function to execute within the span context
|
|
61
|
+
* @param tracerOptions - Optional tracer configuration (custom name, prefix inclusion)
|
|
62
|
+
* @returns Promise resolving to the callback's return value
|
|
63
|
+
*/
|
|
64
|
+
startActiveSpan<T>(name: string, options: SpanOptions, fn: (span: Span) => Promise<T>, tracerOptions?: InstrumentConfig): Promise<T>;
|
|
65
|
+
/**
|
|
66
|
+
* Register OpenTelemetry instrumentations.
|
|
67
|
+
* Can be called at any time, but recommended to call in plugin constructor.
|
|
68
|
+
* @param instrumentations - Array of OpenTelemetry instrumentations to register
|
|
69
|
+
*/
|
|
70
|
+
registerInstrumentations(instrumentations: Instrumentation[]): void;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { ITelemetry, InstrumentConfig, TelemetryConfig };
|
|
74
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/telemetry/types.ts"],"sourcesContent":[],"mappings":";;;;;UAIiB,eAAA;;EAAA,cAAA,CAAA,EAAA,MAAe;EAAA,gBAAA,CAAA,EAGX,eAHW,EAAA;kBAGX,CAAA,EAAA,MAAA;SAET,CAAA,EAAA,MAAA,CAAA,MAAA,EAAA,MAAA,CAAA;;AAMZ;AAiBA;;AAKsB,UAtBL,gBAAA,CAsBK;;;;MAYA,CAAA,EAAA,MAAA;;;;;;eAsBE,CAAA,EAAA,OAAA;;;;;;UAvCP,UAAA;;;;;sBAKK,mBAAmB;;;;;qBAMpB,mBAAmB;;;;;sBAMlB,mBAAmB;;;;;;kBAOvB;;;;;;;;;;;4CAcL,wBACE,SAAS,QAAQ,oBACZ,mBACf,QAAQ;;;;;;6CAOgC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Plugin } from "vite";
|
|
2
|
+
|
|
3
|
+
//#region src/type-generator/vite-plugin.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for the AppKit types plugin.
|
|
7
|
+
*/
|
|
8
|
+
interface AppKitTypesPluginOptions {
|
|
9
|
+
outFile?: string;
|
|
10
|
+
/** Folders to watch for changes. */
|
|
11
|
+
watchFolders?: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Vite plugin to generate types for AppKit queries.
|
|
15
|
+
* Calls `npx appkit-generate-types` under the hood.
|
|
16
|
+
* @param options - Options to override default values.
|
|
17
|
+
* @returns Vite plugin to generate types for AppKit queries.
|
|
18
|
+
*/
|
|
19
|
+
declare function appKitTypesPlugin(options?: AppKitTypesPluginOptions): Plugin;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { appKitTypesPlugin };
|
|
22
|
+
//# sourceMappingURL=vite-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-plugin.d.ts","names":[],"sources":["../../src/type-generator/vite-plugin.ts"],"sourcesContent":[],"mappings":";;;;;;AAEmC;AAkBnC,UAbU,wBAAA,CAauB;EAAA,OAAA,CAAA,EAAA,MAAA;;cAAsC,CAAA,EAAA,MAAA,EAAA;;;;;;;;iBAAvD,iBAAA,WAA4B,2BAA2B"}
|