| /** | |
| * Low-level devtools client and tracer wiring over the current `Socket`. | |
| * | |
| * `DevToolsClient` speaks the devtools NDJSON protocol used by the unstable | |
| * devtools integration. It sends span starts, span events, span completions, | |
| * ping messages, and metric snapshots through a socket, then exposes tracer | |
| * layers that forward telemetry while preserving the current tracer's behavior. | |
| * | |
| * @since 4.0.0 | |
| */ | |
| import * as Cause from "../../Cause.ts" | |
| import * as Context from "../../Context.ts" | |
| import * as Deferred from "../../Deferred.ts" | |
| import * as Effect from "../../Effect.ts" | |
| import * as Fiber from "../../Fiber.ts" | |
| import * as Layer from "../../Layer.ts" | |
| import * as Metric from "../../Metric.ts" | |
| import * as Queue from "../../Queue.ts" | |
| import * as Schema from "../../Schema.ts" | |
| import type * as Scope from "../../Scope.ts" | |
| import * as Stream from "../../Stream.ts" | |
| import * as Tracer from "../../Tracer.ts" | |
| import * as Ndjson from "../encoding/Ndjson.ts" | |
| import * as Socket from "../socket/Socket.ts" | |
| import * as DevToolsSchema from "./DevToolsSchema.ts" | |
| const RequestSchema = Schema.toCodecJson(DevToolsSchema.Request) | |
| const ResponseSchema = Schema.toCodecJson(DevToolsSchema.Response) | |
| /** | |
| * Service for sending span and span-event telemetry to the Effect devtools | |
| * connection. | |
| * | |
| * @category services | |
| * @since 4.0.0 | |
| */ | |
| export class DevToolsClient extends Context.Service< | |
| DevToolsClient, | |
| { | |
| readonly sendUnsafe: ( | |
| _: DevToolsSchema.Span | DevToolsSchema.SpanEvent | |
| ) => void | |
| } | |
| >()("effect/devtools/DevToolsClient") {} | |
| const makeEffect = Effect.gen(function*() { | |
| const socket = yield* Socket.Socket | |
| const services = yield* Effect.context<never>() | |
| const requests = yield* Queue.unbounded<DevToolsSchema.Request>() | |
| const connected = yield* Deferred.make<void>() | |
| const offerMetricsSnapshot = Effect.sync(() => { | |
| Queue.offerUnsafe(requests, toMetricsSnapshot(services)) | |
| }) | |
| const handleResponse = ( | |
| response: DevToolsSchema.Response | |
| ): Effect.Effect<void> => { | |
| switch (response._tag) { | |
| case "MetricsRequest": { | |
| return offerMetricsSnapshot | |
| } | |
| case "Pong": { | |
| return Effect.void | |
| } | |
| } | |
| } | |
| const fiber = yield* Stream.fromQueue(requests).pipe( | |
| Stream.pipeThroughChannel( | |
| Ndjson.duplexSchemaString(Socket.toChannelString(socket), { | |
| inputSchema: RequestSchema, | |
| outputSchema: ResponseSchema | |
| }) | |
| ), | |
| Stream.onFirst(() => Deferred.completeWith(connected, Effect.void)), | |
| Stream.runForEach(handleResponse), | |
| Effect.forkDetach | |
| ) | |
| yield* Effect.addFinalizer(() => | |
| offerMetricsSnapshot.pipe( | |
| Effect.andThen( | |
| Effect.flatMap(Effect.fiberId, (id) => Queue.failCause(requests, Cause.interrupt(id))) | |
| ), | |
| Effect.andThen(Fiber.await(fiber)) | |
| ) | |
| ) | |
| yield* Effect.suspend(() => Queue.offer(requests, { _tag: "Ping" })).pipe( | |
| Effect.delay("3 seconds"), | |
| Effect.forever, | |
| Effect.forkScoped | |
| ) | |
| yield* Deferred.await(connected).pipe( | |
| Effect.timeoutOption("1 second"), | |
| Effect.asVoid | |
| ) | |
| return DevToolsClient.of({ | |
| sendUnsafe(request: DevToolsSchema.Span | DevToolsSchema.SpanEvent) { | |
| Queue.offerUnsafe(requests, request) | |
| } | |
| }) | |
| }) | |
| const toMetricsSnapshot = ( | |
| context: Context.Context<never> | |
| ): DevToolsSchema.MetricsSnapshot => ({ | |
| _tag: "MetricsSnapshot", | |
| metrics: Metric.snapshotUnsafe(context) | |
| }) | |
| /** | |
| * Creates a devtools client over the current `Socket`, speaking the devtools | |
| * NDJSON protocol, sending periodic pings, and responding to metrics snapshot | |
| * requests. | |
| * | |
| * **When to use** | |
| * | |
| * Use when you already have a `Socket` and need the low-level `DevToolsClient` | |
| * service to exchange devtools telemetry directly. | |
| * | |
| * **Details** | |
| * | |
| * The effect requires `Scope` because it starts background fibers for the socket | |
| * stream and heartbeat. | |
| * | |
| * **Gotchas** | |
| * | |
| * `make` creates only the client service; tracing is installed separately. | |
| * | |
| * @see {@link layer} for providing the client as a layer | |
| * @see {@link makeTracer} for creating a tracer after a `DevToolsClient` is available | |
| * @see {@link layerTracer} for creating the client from the current `Socket` and installing the tracer as a layer | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const make: Effect.Effect< | |
| DevToolsClient["Service"], | |
| never, | |
| Scope.Scope | Socket.Socket | |
| > = makeEffect.pipe( | |
| Effect.annotateLogs({ | |
| module: "DevTools", | |
| service: "Client" | |
| }) | |
| ) | |
| /** | |
| * Layer that provides `DevToolsClient` using the current `Socket`. | |
| * | |
| * **When to use** | |
| * | |
| * Use to provide the low-level `DevToolsClient` service over an existing | |
| * `Socket` for custom devtools integrations that send telemetry through the | |
| * client directly. | |
| * | |
| * **Details** | |
| * | |
| * It delegates to `make`, so it speaks the devtools NDJSON protocol over the | |
| * provided `Socket`, sends periodic pings, responds to metrics snapshot | |
| * requests, and finalizes its background fibers when the layer scope closes. | |
| * | |
| * **Gotchas** | |
| * | |
| * This layer only provides the client. It does not install the devtools tracer | |
| * by itself. | |
| * | |
| * @see {@link make} for constructing the client as a scoped effect instead of a layer | |
| * @see {@link layerTracer} for a higher-level layer that creates the client and installs the devtools tracer | |
| * | |
| * @category layers | |
| * @since 4.0.0 | |
| */ | |
| export const layer: Layer.Layer<DevToolsClient, never, Socket.Socket> = Layer.effect(DevToolsClient, make) | |
| const makeTracerEffect = Effect.gen(function*() { | |
| const client = yield* DevToolsClient | |
| const currentTracer = yield* Effect.tracer | |
| return Tracer.make({ | |
| span(options) { | |
| const span = currentTracer.span(options) | |
| client.sendUnsafe(span) | |
| const oldEvent = span.event | |
| span.event = function(this: Tracer.Span, name, startTime, attributes) { | |
| client.sendUnsafe({ | |
| _tag: "SpanEvent", | |
| traceId: span.traceId, | |
| spanId: span.spanId, | |
| name, | |
| startTime, | |
| attributes | |
| }) | |
| return oldEvent.call(this, name, startTime, attributes) | |
| } | |
| const oldEnd = span.end | |
| span.end = function(this: Tracer.Span, endTime, exit) { | |
| oldEnd.call(this, endTime, exit) | |
| client.sendUnsafe(span) | |
| } | |
| return span | |
| }, | |
| context: currentTracer.context | |
| }) | |
| }) | |
| /** | |
| * Creates a tracer that delegates to the current tracer while sending span | |
| * starts, span events, and span ends to `DevToolsClient`. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const makeTracer: Effect.Effect<Tracer.Tracer, never, DevToolsClient> = makeTracerEffect.pipe( | |
| Effect.annotateLogs({ | |
| module: "DevTools", | |
| service: "Tracer" | |
| }) | |
| ) | |
| /** | |
| * Layer that creates a `DevToolsClient` from the current `Socket` and installs | |
| * the devtools tracer. | |
| * | |
| * @category layers | |
| * @since 4.0.0 | |
| */ | |
| export const layerTracer: Layer.Layer<never, never, Socket.Socket> = Layer.effect(Tracer.Tracer, makeTracer).pipe( | |
| Layer.provide(layer) | |
| ) | |
Xet Storage Details
- Size:
- 7 kB
- Xet hash:
- 85bee33559e8729cee0d02a342e7b45ac1bbc65c1067b665a285f4c3c2624207
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.