import { Effect, Layer, Option, Tracer } from "effect-app" import { NonEmptyString255 } from "effect-app/Schema" import { SqlClient } from "effect/unstable/sql" import { LocaleRef, RequestContext, spanAttributes } from "../RequestContext.js" import { ContextMapContainer } from "../Store/ContextMapContainer.js" import { storeId } from "../Store/Memory.js" const withSqlTransaction = (self: Effect.Effect): Effect.Effect => Effect.serviceOption(SqlClient.SqlClient).pipe( Effect.flatMap(Option.match({ onNone: () => self, onSome: (sql) => sql.withTransaction(self).pipe(Effect.orDie) })) ) export const getRequestContext = Effect .all({ span: Effect.currentSpan.pipe(Effect.orDie), locale: LocaleRef.asEffect(), namespace: storeId.asEffect() }) .pipe( Effect.map(({ locale, namespace, span }) => RequestContext.make({ span: Tracer.externalSpan(span), locale, namespace, name: NonEmptyString255(span.name) }) ) ) export const getRC = Effect.all({ locale: LocaleRef.asEffect(), namespace: storeId.asEffect() }) const withRequestSpan = (name = "request", options?: Tracer.SpanOptions) => (f: Effect.Effect) => Effect.andThen( getRC, (ctx) => f.pipe( Effect.withSpan(name, { ...options, attributes: { ...spanAttributes({ ...ctx, name: NonEmptyString255(name) }), ...options?.attributes } }, { captureStackTrace: options?.captureStackTrace ?? false }), // TODO: false // request context info is picked up directly in the logger for annotations. Effect.withLogSpan(name) ) ) export interface SetupRequestOptions { readonly withTransaction?: boolean } export const setupRequestContextFromCurrent = (name = "request", options?: Tracer.SpanOptions & SetupRequestOptions) => (self: Effect.Effect) => self .pipe( options?.withTransaction === true ? withSqlTransaction : (_) => _, withRequestSpan(name, options), Effect.provide(ContextMapContainer.layer, { local: true }) ) // TODO: consider integrating Effect.withParentSpan export function setupRequestContext( self: Effect.Effect, requestContext: RequestContext, options?: SetupRequestOptions ) { const layer = Layer.mergeAll( ContextMapContainer.layer, Layer.succeed(LocaleRef, requestContext.locale), Layer.succeed(storeId, requestContext.namespace) ) return self .pipe( options?.withTransaction === true ? withSqlTransaction : (_) => _, withRequestSpan(requestContext.name), Effect.provide(layer, { local: true }) ) } export function setupRequestContextWithCustomSpan( self: Effect.Effect, requestContext: RequestContext, name: string, options?: Tracer.SpanOptions & SetupRequestOptions ) { const layer = Layer.mergeAll( ContextMapContainer.layer, Layer.succeed(LocaleRef, requestContext.locale), Layer.succeed(storeId, requestContext.namespace) ) return self .pipe( options?.withTransaction === true ? withSqlTransaction : (_) => _, withRequestSpan(name, options), Effect.provide(layer, { local: true }) ) }