@lunora/runtime 1.0.0-alpha.29 → 1.0.0-alpha.30

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/dist/index.d.mts CHANGED
@@ -661,6 +661,123 @@ interface LogEvent {
661
661
  /** Acting userId, or absent when anonymous. */
662
662
  userId?: string;
663
663
  }
664
+ /**
665
+ * What kind of instrument produced a measurement, which decides how a collector
666
+ * aggregates it:
667
+ *
668
+ * - `counter` — a monotonic delta to add up (requests, retries, bytes sent).
669
+ * - `gauge` — a point-in-time reading that replaces the last one (queue depth,
670
+ * cache size).
671
+ * - `histogram` — a value whose *distribution* matters (latency, payload size),
672
+ * giving percentiles rather than just a mean.
673
+ */
674
+ type MetricKind = "counter" | "gauge" | "histogram";
675
+ /**
676
+ * One measurement recorded from a function handler.
677
+ *
678
+ * Each `ctx.metrics.*` call produces exactly one of these — the runtime does no
679
+ * pre-aggregation, so counters carry **delta** temporality and a collector sums
680
+ * them. That keeps the sink model identical to logs and spans (one event, one
681
+ * export) at the cost of chattiness in a hot loop, where the handler should sum
682
+ * locally and record once.
683
+ */
684
+ interface MetricEvent {
685
+ /**
686
+ * Structured attributes the caller attached, normalized to a fresh bag of
687
+ * JSON-safe primitives (see `shared/log-fields.ts`). These are the metric's
688
+ * dimensions — keep them low-cardinality; an id-valued attribute creates a
689
+ * distinct time series per id.
690
+ *
691
+ * Caller-controlled, so they MAY contain user input and they DO egress to
692
+ * whatever destination the sink ships to — the same caveat as a log line's
693
+ * `fields` and a span's `error.message`. Scrub upstream if that matters.
694
+ */
695
+ attributes?: LogFields;
696
+ /** Function path that recorded the measurement, e.g. `"orders:checkout"`. */
697
+ functionPath: string;
698
+ /** Instrument kind; see {@link MetricKind}. */
699
+ kind: MetricKind;
700
+ /** Instrument name, e.g. `"orders.placed"`. */
701
+ name: string;
702
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
703
+ shardKey?: string;
704
+ /** Wall-clock millis when the measurement was recorded. */
705
+ ts: number;
706
+ /**
707
+ * The measured value: the increment for a `counter`, the current reading for
708
+ * a `gauge`, the observed sample for a `histogram`.
709
+ */
710
+ value: number;
711
+ }
712
+ /**
713
+ * One span produced by a `ctx.trace(name, fn)` call, or the synthetic root span
714
+ * the shard records for the dispatch itself so a waterfall has a bar to hang
715
+ * its children under.
716
+ *
717
+ * Ids are the same lowercase-hex form the OTLP encoders and `traceparent` use
718
+ * (32-hex trace, 16-hex span), so a `SpanEvent` composes into an OTLP span with
719
+ * no reformatting.
720
+ */
721
+ interface SpanEvent {
722
+ /**
723
+ * Structured attributes the caller attached, already normalized to a fresh
724
+ * bag of JSON-safe primitives (see `shared/log-fields.ts`) exactly like a log
725
+ * line's `fields`. Absent when the caller passed none.
726
+ */
727
+ attributes?: LogFields;
728
+ /** Wall-clock duration of the span body, in milliseconds. */
729
+ durationMs: number;
730
+ /**
731
+ * Populated when the span body threw. `type` is the error's constructor name
732
+ * (or its `LunoraError` code); `message` is the human-readable string and may
733
+ * include user input, so sinks shipping to third parties should scrub it.
734
+ */
735
+ error?: {
736
+ message: string;
737
+ type: string;
738
+ };
739
+ /**
740
+ * Function path the span was created under, e.g. `"messages:list"`. A span
741
+ * created inside a function invoked via `ctx.runQuery`/`runMutation`/
742
+ * `runAction` carries the OUTER entrypoint's path, since the composed call
743
+ * reuses its context — the same attribution rule `ctx.log` follows.
744
+ */
745
+ functionPath: string;
746
+ /** Caller-supplied span name, e.g. `"stripe.charge"`. */
747
+ name: string;
748
+ /** True when the span body returned without throwing. */
749
+ ok: boolean;
750
+ /**
751
+ * Span id of the enclosing span — the parent `ctx.trace` when nested, else
752
+ * the dispatch's own RPC span (from the inbound `traceparent`). A span with
753
+ * no inbound trace context is parented to a locally-minted root, so this is
754
+ * always set for a `ctx.trace` span; only the synthetic `dispatch` span below
755
+ * carries `""`, meaning "nothing above me in this trace".
756
+ */
757
+ parentSpanId: string;
758
+ /**
759
+ * True for the synthetic span representing the **dispatch itself**, which the
760
+ * shard records so a waterfall has a bar for the request to hang its
761
+ * `ctx.trace` spans under.
762
+ *
763
+ * Named for what it is rather than "root": it is not the root of the
764
+ * collector-side trace — the worker's own RPC span sits above it — and it is
765
+ * never exported to a sink, because the runtime already emits that dispatch
766
+ * via `onRpc` and a collector would otherwise show it twice. Locally it *is*
767
+ * the outermost span, which is why the fold prefers it as a trace's anchor.
768
+ */
769
+ dispatch?: boolean;
770
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
771
+ shardKey?: string;
772
+ /** This span's own id (16-hex). */
773
+ spanId: string;
774
+ /** Wall-clock millis when the span started. */
775
+ startTs: number;
776
+ /** Trace this span belongs to (32-hex) — shared with the dispatch's logs. */
777
+ traceId: string;
778
+ /** Acting userId, or absent when anonymous. */
779
+ userId?: string;
780
+ }
664
781
  /**
665
782
  * Per-RPC dispatch event. Single-shard calls set `shardKey`; cross-shard
666
783
  * fan-outs set `fanOut` with the table being aggregated, shard count, and
@@ -726,8 +843,19 @@ type ObservabilitySinkContext = LogSinkContext;
726
843
  interface ObservabilitySink {
727
844
  /** Invoked once per `ctx.log.*` call from a function handler. */
728
845
  onLog?: (event: LogEvent, context?: ObservabilitySinkContext) => void;
846
+ /**
847
+ * Invoked once per `ctx.metrics.*` measurement. No pre-aggregation happens
848
+ * upstream, so counter values are deltas for the destination to sum.
849
+ */
850
+ onMetric?: (event: MetricEvent, context?: ObservabilitySinkContext) => void;
729
851
  /** Invoked once per dispatched RPC (single-shard or fan-out). */
730
852
  onRpc?: (event: ObservabilityEvent, context?: ObservabilitySinkContext) => void;
853
+ /**
854
+ * Invoked once per `ctx.trace(name, fn)` span, when the span body settles.
855
+ * Distinct from `onRpc`: that is the one SERVER span per dispatch, this is the
856
+ * INTERNAL spans a handler creates beneath it.
857
+ */
858
+ onSpan?: (event: SpanEvent, context?: ObservabilitySinkContext) => void;
731
859
  }
732
860
  /**
733
861
  * Invoke `sink.onRpc` with the given event, swallowing any error the sink
@@ -2678,7 +2806,7 @@ interface WebhookSinkOptions extends OnlyErrorsOption {
2678
2806
  */
2679
2807
  transform?: (event: ObservabilityEvent) => null | ObservabilityEvent | undefined;
2680
2808
  /**
2681
- * Optional redaction hook for `ctx.log` events (the {@link transform}
2809
+ * Optional redaction hook for `ctx.log` events (the `transform`
2682
2810
  * counterpart for log lines). Same fail-closed contract: return the event to
2683
2811
  * ship it, `null`/`undefined` to drop it, and a throw drops it. When unset,
2684
2812
  * log events are shipped as-is (message + structured fields — which may carry
@@ -2855,9 +2983,11 @@ interface OtlpSinkOptions extends OnlyErrorsOption {
2855
2983
  * side lands in the same collector. Each RPC dispatch becomes one OTLP **span**
2856
2984
  * (`${endpoint}/v1/traces`) named after its `functionPath`, with start/end
2857
2985
  * derived from `durationMs` and status OK/ERROR; each `ctx.log.*` line becomes
2858
- * one OTLP **log record** (`${endpoint}/v1/logs`). Trace/span ids are random
2859
- * per span real trace correlation (worker→container `traceparent`) is a later
2860
- * phase.
2986
+ * one OTLP **log record** (`${endpoint}/v1/logs`). Spans and log records reuse
2987
+ * the dispatch's `traceId`/`spanId` (minted at dispatch entry and propagated to
2988
+ * the shard and any container as a `traceparent`), so a handler's logs, its RPC
2989
+ * span, and the container spans beneath it all stitch into one trace; ids are
2990
+ * only randomised on paths that carry no trace context.
2861
2991
  *
2862
2992
  * Like {@link webhookSink}, each export is its own `fetch`, registered with the
2863
2993
  * request's `context.waitUntil` when present so it survives isolate teardown,
@@ -2881,4 +3011,4 @@ declare const otlpSink: (options: OtlpSinkOptions) => ObservabilitySink;
2881
3011
  */
2882
3012
  declare const combineSinks: (...sinks: ObservabilitySink[]) => ObservabilitySink;
2883
3013
  declare const VERSION: string;
2884
- export { type AdminTableResolver, type AirbyteMessage, type AnalyticsEngineDataPointLike, type AnalyticsEngineDatasetLike, type AnalyticsEngineSinkOptions, type AuthAdmin, type AuthCapabilities, type AuthConfigInfo, type AuthImpersonation, type AuthPage, type AuthSession, type AuthUser, type AuthUserFieldSpec, type BackupManifest, type BackupStore, type ComposeIdentityResolversErrorMode, type ComposeIdentityResolversOptions, type ConnectorChange, type ConnectorSyncPage, type CorsOptions, type CronHandler, type CronJobDispatch, type CronJobInfo, type CrossShardCounter, type CrossShardReader, type CrossShardRelationCapabilities, type CrossShardRelationOptions, type CsrfOptions, DEFAULT_REGISTRY_CACHE_TTL_MS, type DurableObjectJurisdiction, type DynamicShardRegistry, type DynamicShardRegistryOptions, type ExecutionContextLike, type ExportFanOutRequest, type ExportFanOutResult, type FanOutRequest, type FanOutResult, type FanOutSpec, type FivetranResponse, type FrameworkHostHandler, type FrameworkWorkerOptions, type FrameworkWorkerOptionsInput, type FunctionDescriptor, type FunctionRegistryEntry, type FunctionRegistryLike, type GlobalExportFunction as GlobalExportFn, type GlobalImportFunction as GlobalImportFn, type GlobalIntrospector, type GlobalTableInfo as GlobalTableInfoMeta, type GlobalTablePage as GlobalTablePageMeta, type HttpActionContext, type HttpActionLike, type HttpRouterLike, type IdentityContractLike, type IdentityResolver, type IdentityValidation, type ImportFanOutRequest, type ImportFanOutResult, type KvIntrospector, type KvKeyEntry, type KvKeyListResult, type KvNamespaceSummary, type KvValueResult, type ListAuthUsersOptions, type LogEvent, type LogFields, type LogLevel, LunoraError, type LunoraErrorBody, type LunoraHandlerOptions, type LunoraWorker, type MergeStrategy, type MigrationFanOutRequest, type MigrationFanOutResult, NOOP_EXECUTION_CONTEXT, type ObservabilityEvent, type ObservabilitySink, type ObservabilitySinkContext, type OtlpSinkOptions, type PipelineLike, type PipelineLogSinkOptions, type QueryCoordinator, type QueryCoordinatorOptions, type RankFanOutRequest, type RankFanOutResult, type RankPageFanOutRequest, type RankPageFanOutResult, type ResolvedSecurity, type ResolvedShard, type Route, type RpcContext, type RpcEnvelope, SHARD_REGISTRY_DO_NAME, type ScheduledControllerLike, type SecurityHeadersOptions, type SecurityOptions, type SentrySinkOptions, type ShardError, type ShardExportOutcome, type ShardImportOutcome, type ShardMigrationOutcome, type ShardNamespaceLike, type ShardRankOutcome, type ShardRankPageOutcome, type ShardRegistry, type ShardTrafficEntry, type ShardTrafficFanOutRequest, type ShardTrafficFanOutResult, type ShardingInfo, type StorageListFunction as StorageListFn, type StorageObject, VERSION, type VectorIndexSummary, type VectorIntrospector, type VectorQueryMatch, type WebhookSinkOptions, type WorkerOptions, analyticsEngineSink, applyJurisdiction, combineSinks, composeIdentityResolvers, composeWorker, consoleSink, createCrossShardRelationCapabilities, createDynamicShardRegistry, createLunoraHandler, createQueryCoordinator, createStaticShardRegistry, createWorker, decorateResponse, defineRpcEnvelope, emitLogEvent, emitRpcEvent, enforceOrigin, handleCorsPreflight, mergeStrategyForAggregate, otlpSink, pipelineLogSink, resolveLunoraOptions, resolveSecurity, resolveShard, routeIdentityResolvers, sentrySink, toAirbyteMessages, toErrorResponse, toFivetranResponse, webhookSink, withFrameworkWorker };
3014
+ export { type AdminTableResolver, type AirbyteMessage, type AnalyticsEngineDataPointLike, type AnalyticsEngineDatasetLike, type AnalyticsEngineSinkOptions, type AuthAdmin, type AuthCapabilities, type AuthConfigInfo, type AuthImpersonation, type AuthPage, type AuthSession, type AuthUser, type AuthUserFieldSpec, type BackupManifest, type BackupStore, type ComposeIdentityResolversErrorMode, type ComposeIdentityResolversOptions, type ConnectorChange, type ConnectorSyncPage, type CorsOptions, type CronHandler, type CronJobDispatch, type CronJobInfo, type CrossShardCounter, type CrossShardReader, type CrossShardRelationCapabilities, type CrossShardRelationOptions, type CsrfOptions, DEFAULT_REGISTRY_CACHE_TTL_MS, type DurableObjectJurisdiction, type DynamicShardRegistry, type DynamicShardRegistryOptions, type ExecutionContextLike, type ExportFanOutRequest, type ExportFanOutResult, type FanOutRequest, type FanOutResult, type FanOutSpec, type FivetranResponse, type FrameworkHostHandler, type FrameworkWorkerOptions, type FrameworkWorkerOptionsInput, type FunctionDescriptor, type FunctionRegistryEntry, type FunctionRegistryLike, type GlobalExportFunction as GlobalExportFn, type GlobalImportFunction as GlobalImportFn, type GlobalIntrospector, type GlobalTableInfo as GlobalTableInfoMeta, type GlobalTablePage as GlobalTablePageMeta, type HttpActionContext, type HttpActionLike, type HttpRouterLike, type IdentityContractLike, type IdentityResolver, type IdentityValidation, type ImportFanOutRequest, type ImportFanOutResult, type KvIntrospector, type KvKeyEntry, type KvKeyListResult, type KvNamespaceSummary, type KvValueResult, type ListAuthUsersOptions, type LogEvent, type LogFields, type LogLevel, LunoraError, type LunoraErrorBody, type LunoraHandlerOptions, type LunoraWorker, type MergeStrategy, type MetricEvent, type MetricKind, type MigrationFanOutRequest, type MigrationFanOutResult, NOOP_EXECUTION_CONTEXT, type ObservabilityEvent, type ObservabilitySink, type ObservabilitySinkContext, type OtlpSinkOptions, type PipelineLike, type PipelineLogSinkOptions, type QueryCoordinator, type QueryCoordinatorOptions, type RankFanOutRequest, type RankFanOutResult, type RankPageFanOutRequest, type RankPageFanOutResult, type ResolvedSecurity, type ResolvedShard, type Route, type RpcContext, type RpcEnvelope, SHARD_REGISTRY_DO_NAME, type ScheduledControllerLike, type SecurityHeadersOptions, type SecurityOptions, type SentrySinkOptions, type ShardError, type ShardExportOutcome, type ShardImportOutcome, type ShardMigrationOutcome, type ShardNamespaceLike, type ShardRankOutcome, type ShardRankPageOutcome, type ShardRegistry, type ShardTrafficEntry, type ShardTrafficFanOutRequest, type ShardTrafficFanOutResult, type ShardingInfo, type SpanEvent, type StorageListFunction as StorageListFn, type StorageObject, VERSION, type VectorIndexSummary, type VectorIntrospector, type VectorQueryMatch, type WebhookSinkOptions, type WorkerOptions, analyticsEngineSink, applyJurisdiction, combineSinks, composeIdentityResolvers, composeWorker, consoleSink, createCrossShardRelationCapabilities, createDynamicShardRegistry, createLunoraHandler, createQueryCoordinator, createStaticShardRegistry, createWorker, decorateResponse, defineRpcEnvelope, emitLogEvent, emitRpcEvent, enforceOrigin, handleCorsPreflight, mergeStrategyForAggregate, otlpSink, pipelineLogSink, resolveLunoraOptions, resolveSecurity, resolveShard, routeIdentityResolvers, sentrySink, toAirbyteMessages, toErrorResponse, toFivetranResponse, webhookSink, withFrameworkWorker };
package/dist/index.d.ts CHANGED
@@ -661,6 +661,123 @@ interface LogEvent {
661
661
  /** Acting userId, or absent when anonymous. */
662
662
  userId?: string;
663
663
  }
664
+ /**
665
+ * What kind of instrument produced a measurement, which decides how a collector
666
+ * aggregates it:
667
+ *
668
+ * - `counter` — a monotonic delta to add up (requests, retries, bytes sent).
669
+ * - `gauge` — a point-in-time reading that replaces the last one (queue depth,
670
+ * cache size).
671
+ * - `histogram` — a value whose *distribution* matters (latency, payload size),
672
+ * giving percentiles rather than just a mean.
673
+ */
674
+ type MetricKind = "counter" | "gauge" | "histogram";
675
+ /**
676
+ * One measurement recorded from a function handler.
677
+ *
678
+ * Each `ctx.metrics.*` call produces exactly one of these — the runtime does no
679
+ * pre-aggregation, so counters carry **delta** temporality and a collector sums
680
+ * them. That keeps the sink model identical to logs and spans (one event, one
681
+ * export) at the cost of chattiness in a hot loop, where the handler should sum
682
+ * locally and record once.
683
+ */
684
+ interface MetricEvent {
685
+ /**
686
+ * Structured attributes the caller attached, normalized to a fresh bag of
687
+ * JSON-safe primitives (see `shared/log-fields.ts`). These are the metric's
688
+ * dimensions — keep them low-cardinality; an id-valued attribute creates a
689
+ * distinct time series per id.
690
+ *
691
+ * Caller-controlled, so they MAY contain user input and they DO egress to
692
+ * whatever destination the sink ships to — the same caveat as a log line's
693
+ * `fields` and a span's `error.message`. Scrub upstream if that matters.
694
+ */
695
+ attributes?: LogFields;
696
+ /** Function path that recorded the measurement, e.g. `"orders:checkout"`. */
697
+ functionPath: string;
698
+ /** Instrument kind; see {@link MetricKind}. */
699
+ kind: MetricKind;
700
+ /** Instrument name, e.g. `"orders.placed"`. */
701
+ name: string;
702
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
703
+ shardKey?: string;
704
+ /** Wall-clock millis when the measurement was recorded. */
705
+ ts: number;
706
+ /**
707
+ * The measured value: the increment for a `counter`, the current reading for
708
+ * a `gauge`, the observed sample for a `histogram`.
709
+ */
710
+ value: number;
711
+ }
712
+ /**
713
+ * One span produced by a `ctx.trace(name, fn)` call, or the synthetic root span
714
+ * the shard records for the dispatch itself so a waterfall has a bar to hang
715
+ * its children under.
716
+ *
717
+ * Ids are the same lowercase-hex form the OTLP encoders and `traceparent` use
718
+ * (32-hex trace, 16-hex span), so a `SpanEvent` composes into an OTLP span with
719
+ * no reformatting.
720
+ */
721
+ interface SpanEvent {
722
+ /**
723
+ * Structured attributes the caller attached, already normalized to a fresh
724
+ * bag of JSON-safe primitives (see `shared/log-fields.ts`) exactly like a log
725
+ * line's `fields`. Absent when the caller passed none.
726
+ */
727
+ attributes?: LogFields;
728
+ /** Wall-clock duration of the span body, in milliseconds. */
729
+ durationMs: number;
730
+ /**
731
+ * Populated when the span body threw. `type` is the error's constructor name
732
+ * (or its `LunoraError` code); `message` is the human-readable string and may
733
+ * include user input, so sinks shipping to third parties should scrub it.
734
+ */
735
+ error?: {
736
+ message: string;
737
+ type: string;
738
+ };
739
+ /**
740
+ * Function path the span was created under, e.g. `"messages:list"`. A span
741
+ * created inside a function invoked via `ctx.runQuery`/`runMutation`/
742
+ * `runAction` carries the OUTER entrypoint's path, since the composed call
743
+ * reuses its context — the same attribution rule `ctx.log` follows.
744
+ */
745
+ functionPath: string;
746
+ /** Caller-supplied span name, e.g. `"stripe.charge"`. */
747
+ name: string;
748
+ /** True when the span body returned without throwing. */
749
+ ok: boolean;
750
+ /**
751
+ * Span id of the enclosing span — the parent `ctx.trace` when nested, else
752
+ * the dispatch's own RPC span (from the inbound `traceparent`). A span with
753
+ * no inbound trace context is parented to a locally-minted root, so this is
754
+ * always set for a `ctx.trace` span; only the synthetic `dispatch` span below
755
+ * carries `""`, meaning "nothing above me in this trace".
756
+ */
757
+ parentSpanId: string;
758
+ /**
759
+ * True for the synthetic span representing the **dispatch itself**, which the
760
+ * shard records so a waterfall has a bar for the request to hang its
761
+ * `ctx.trace` spans under.
762
+ *
763
+ * Named for what it is rather than "root": it is not the root of the
764
+ * collector-side trace — the worker's own RPC span sits above it — and it is
765
+ * never exported to a sink, because the runtime already emits that dispatch
766
+ * via `onRpc` and a collector would otherwise show it twice. Locally it *is*
767
+ * the outermost span, which is why the fold prefers it as a trace's anchor.
768
+ */
769
+ dispatch?: boolean;
770
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
771
+ shardKey?: string;
772
+ /** This span's own id (16-hex). */
773
+ spanId: string;
774
+ /** Wall-clock millis when the span started. */
775
+ startTs: number;
776
+ /** Trace this span belongs to (32-hex) — shared with the dispatch's logs. */
777
+ traceId: string;
778
+ /** Acting userId, or absent when anonymous. */
779
+ userId?: string;
780
+ }
664
781
  /**
665
782
  * Per-RPC dispatch event. Single-shard calls set `shardKey`; cross-shard
666
783
  * fan-outs set `fanOut` with the table being aggregated, shard count, and
@@ -726,8 +843,19 @@ type ObservabilitySinkContext = LogSinkContext;
726
843
  interface ObservabilitySink {
727
844
  /** Invoked once per `ctx.log.*` call from a function handler. */
728
845
  onLog?: (event: LogEvent, context?: ObservabilitySinkContext) => void;
846
+ /**
847
+ * Invoked once per `ctx.metrics.*` measurement. No pre-aggregation happens
848
+ * upstream, so counter values are deltas for the destination to sum.
849
+ */
850
+ onMetric?: (event: MetricEvent, context?: ObservabilitySinkContext) => void;
729
851
  /** Invoked once per dispatched RPC (single-shard or fan-out). */
730
852
  onRpc?: (event: ObservabilityEvent, context?: ObservabilitySinkContext) => void;
853
+ /**
854
+ * Invoked once per `ctx.trace(name, fn)` span, when the span body settles.
855
+ * Distinct from `onRpc`: that is the one SERVER span per dispatch, this is the
856
+ * INTERNAL spans a handler creates beneath it.
857
+ */
858
+ onSpan?: (event: SpanEvent, context?: ObservabilitySinkContext) => void;
731
859
  }
732
860
  /**
733
861
  * Invoke `sink.onRpc` with the given event, swallowing any error the sink
@@ -2678,7 +2806,7 @@ interface WebhookSinkOptions extends OnlyErrorsOption {
2678
2806
  */
2679
2807
  transform?: (event: ObservabilityEvent) => null | ObservabilityEvent | undefined;
2680
2808
  /**
2681
- * Optional redaction hook for `ctx.log` events (the {@link transform}
2809
+ * Optional redaction hook for `ctx.log` events (the `transform`
2682
2810
  * counterpart for log lines). Same fail-closed contract: return the event to
2683
2811
  * ship it, `null`/`undefined` to drop it, and a throw drops it. When unset,
2684
2812
  * log events are shipped as-is (message + structured fields — which may carry
@@ -2855,9 +2983,11 @@ interface OtlpSinkOptions extends OnlyErrorsOption {
2855
2983
  * side lands in the same collector. Each RPC dispatch becomes one OTLP **span**
2856
2984
  * (`${endpoint}/v1/traces`) named after its `functionPath`, with start/end
2857
2985
  * derived from `durationMs` and status OK/ERROR; each `ctx.log.*` line becomes
2858
- * one OTLP **log record** (`${endpoint}/v1/logs`). Trace/span ids are random
2859
- * per span real trace correlation (worker→container `traceparent`) is a later
2860
- * phase.
2986
+ * one OTLP **log record** (`${endpoint}/v1/logs`). Spans and log records reuse
2987
+ * the dispatch's `traceId`/`spanId` (minted at dispatch entry and propagated to
2988
+ * the shard and any container as a `traceparent`), so a handler's logs, its RPC
2989
+ * span, and the container spans beneath it all stitch into one trace; ids are
2990
+ * only randomised on paths that carry no trace context.
2861
2991
  *
2862
2992
  * Like {@link webhookSink}, each export is its own `fetch`, registered with the
2863
2993
  * request's `context.waitUntil` when present so it survives isolate teardown,
@@ -2881,4 +3011,4 @@ declare const otlpSink: (options: OtlpSinkOptions) => ObservabilitySink;
2881
3011
  */
2882
3012
  declare const combineSinks: (...sinks: ObservabilitySink[]) => ObservabilitySink;
2883
3013
  declare const VERSION: string;
2884
- export { type AdminTableResolver, type AirbyteMessage, type AnalyticsEngineDataPointLike, type AnalyticsEngineDatasetLike, type AnalyticsEngineSinkOptions, type AuthAdmin, type AuthCapabilities, type AuthConfigInfo, type AuthImpersonation, type AuthPage, type AuthSession, type AuthUser, type AuthUserFieldSpec, type BackupManifest, type BackupStore, type ComposeIdentityResolversErrorMode, type ComposeIdentityResolversOptions, type ConnectorChange, type ConnectorSyncPage, type CorsOptions, type CronHandler, type CronJobDispatch, type CronJobInfo, type CrossShardCounter, type CrossShardReader, type CrossShardRelationCapabilities, type CrossShardRelationOptions, type CsrfOptions, DEFAULT_REGISTRY_CACHE_TTL_MS, type DurableObjectJurisdiction, type DynamicShardRegistry, type DynamicShardRegistryOptions, type ExecutionContextLike, type ExportFanOutRequest, type ExportFanOutResult, type FanOutRequest, type FanOutResult, type FanOutSpec, type FivetranResponse, type FrameworkHostHandler, type FrameworkWorkerOptions, type FrameworkWorkerOptionsInput, type FunctionDescriptor, type FunctionRegistryEntry, type FunctionRegistryLike, type GlobalExportFunction as GlobalExportFn, type GlobalImportFunction as GlobalImportFn, type GlobalIntrospector, type GlobalTableInfo as GlobalTableInfoMeta, type GlobalTablePage as GlobalTablePageMeta, type HttpActionContext, type HttpActionLike, type HttpRouterLike, type IdentityContractLike, type IdentityResolver, type IdentityValidation, type ImportFanOutRequest, type ImportFanOutResult, type KvIntrospector, type KvKeyEntry, type KvKeyListResult, type KvNamespaceSummary, type KvValueResult, type ListAuthUsersOptions, type LogEvent, type LogFields, type LogLevel, LunoraError, type LunoraErrorBody, type LunoraHandlerOptions, type LunoraWorker, type MergeStrategy, type MigrationFanOutRequest, type MigrationFanOutResult, NOOP_EXECUTION_CONTEXT, type ObservabilityEvent, type ObservabilitySink, type ObservabilitySinkContext, type OtlpSinkOptions, type PipelineLike, type PipelineLogSinkOptions, type QueryCoordinator, type QueryCoordinatorOptions, type RankFanOutRequest, type RankFanOutResult, type RankPageFanOutRequest, type RankPageFanOutResult, type ResolvedSecurity, type ResolvedShard, type Route, type RpcContext, type RpcEnvelope, SHARD_REGISTRY_DO_NAME, type ScheduledControllerLike, type SecurityHeadersOptions, type SecurityOptions, type SentrySinkOptions, type ShardError, type ShardExportOutcome, type ShardImportOutcome, type ShardMigrationOutcome, type ShardNamespaceLike, type ShardRankOutcome, type ShardRankPageOutcome, type ShardRegistry, type ShardTrafficEntry, type ShardTrafficFanOutRequest, type ShardTrafficFanOutResult, type ShardingInfo, type StorageListFunction as StorageListFn, type StorageObject, VERSION, type VectorIndexSummary, type VectorIntrospector, type VectorQueryMatch, type WebhookSinkOptions, type WorkerOptions, analyticsEngineSink, applyJurisdiction, combineSinks, composeIdentityResolvers, composeWorker, consoleSink, createCrossShardRelationCapabilities, createDynamicShardRegistry, createLunoraHandler, createQueryCoordinator, createStaticShardRegistry, createWorker, decorateResponse, defineRpcEnvelope, emitLogEvent, emitRpcEvent, enforceOrigin, handleCorsPreflight, mergeStrategyForAggregate, otlpSink, pipelineLogSink, resolveLunoraOptions, resolveSecurity, resolveShard, routeIdentityResolvers, sentrySink, toAirbyteMessages, toErrorResponse, toFivetranResponse, webhookSink, withFrameworkWorker };
3014
+ export { type AdminTableResolver, type AirbyteMessage, type AnalyticsEngineDataPointLike, type AnalyticsEngineDatasetLike, type AnalyticsEngineSinkOptions, type AuthAdmin, type AuthCapabilities, type AuthConfigInfo, type AuthImpersonation, type AuthPage, type AuthSession, type AuthUser, type AuthUserFieldSpec, type BackupManifest, type BackupStore, type ComposeIdentityResolversErrorMode, type ComposeIdentityResolversOptions, type ConnectorChange, type ConnectorSyncPage, type CorsOptions, type CronHandler, type CronJobDispatch, type CronJobInfo, type CrossShardCounter, type CrossShardReader, type CrossShardRelationCapabilities, type CrossShardRelationOptions, type CsrfOptions, DEFAULT_REGISTRY_CACHE_TTL_MS, type DurableObjectJurisdiction, type DynamicShardRegistry, type DynamicShardRegistryOptions, type ExecutionContextLike, type ExportFanOutRequest, type ExportFanOutResult, type FanOutRequest, type FanOutResult, type FanOutSpec, type FivetranResponse, type FrameworkHostHandler, type FrameworkWorkerOptions, type FrameworkWorkerOptionsInput, type FunctionDescriptor, type FunctionRegistryEntry, type FunctionRegistryLike, type GlobalExportFunction as GlobalExportFn, type GlobalImportFunction as GlobalImportFn, type GlobalIntrospector, type GlobalTableInfo as GlobalTableInfoMeta, type GlobalTablePage as GlobalTablePageMeta, type HttpActionContext, type HttpActionLike, type HttpRouterLike, type IdentityContractLike, type IdentityResolver, type IdentityValidation, type ImportFanOutRequest, type ImportFanOutResult, type KvIntrospector, type KvKeyEntry, type KvKeyListResult, type KvNamespaceSummary, type KvValueResult, type ListAuthUsersOptions, type LogEvent, type LogFields, type LogLevel, LunoraError, type LunoraErrorBody, type LunoraHandlerOptions, type LunoraWorker, type MergeStrategy, type MetricEvent, type MetricKind, type MigrationFanOutRequest, type MigrationFanOutResult, NOOP_EXECUTION_CONTEXT, type ObservabilityEvent, type ObservabilitySink, type ObservabilitySinkContext, type OtlpSinkOptions, type PipelineLike, type PipelineLogSinkOptions, type QueryCoordinator, type QueryCoordinatorOptions, type RankFanOutRequest, type RankFanOutResult, type RankPageFanOutRequest, type RankPageFanOutResult, type ResolvedSecurity, type ResolvedShard, type Route, type RpcContext, type RpcEnvelope, SHARD_REGISTRY_DO_NAME, type ScheduledControllerLike, type SecurityHeadersOptions, type SecurityOptions, type SentrySinkOptions, type ShardError, type ShardExportOutcome, type ShardImportOutcome, type ShardMigrationOutcome, type ShardNamespaceLike, type ShardRankOutcome, type ShardRankPageOutcome, type ShardRegistry, type ShardTrafficEntry, type ShardTrafficFanOutRequest, type ShardTrafficFanOutResult, type ShardingInfo, type SpanEvent, type StorageListFunction as StorageListFn, type StorageObject, VERSION, type VectorIndexSummary, type VectorIntrospector, type VectorQueryMatch, type WebhookSinkOptions, type WorkerOptions, analyticsEngineSink, applyJurisdiction, combineSinks, composeIdentityResolvers, composeWorker, consoleSink, createCrossShardRelationCapabilities, createDynamicShardRegistry, createLunoraHandler, createQueryCoordinator, createStaticShardRegistry, createWorker, decorateResponse, defineRpcEnvelope, emitLogEvent, emitRpcEvent, enforceOrigin, handleCorsPreflight, mergeStrategyForAggregate, otlpSink, pipelineLogSink, resolveLunoraOptions, resolveSecurity, resolveShard, routeIdentityResolvers, sentrySink, toAirbyteMessages, toErrorResponse, toFivetranResponse, webhookSink, withFrameworkWorker };
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  export { toAirbyteMessages, toFivetranResponse } from './packem_shared/toAirbyteMessages-DrHdplb4.mjs';
2
- export { composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, resolveLunoraOptions, withFrameworkWorker } from './packem_shared/composeWorker-Br4_GgSW.mjs';
2
+ export { composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, resolveLunoraOptions, withFrameworkWorker } from './packem_shared/composeWorker-CqUlK17X.mjs';
3
3
  export { createCrossShardRelationCapabilities } from './packem_shared/createCrossShardRelationCapabilities-CbcWjkAn.mjs';
4
4
  export { DEFAULT_REGISTRY_CACHE_TTL_MS, SHARD_REGISTRY_DO_NAME, createDynamicShardRegistry } from './packem_shared/DEFAULT_REGISTRY_CACHE_TTL_MS-B3pA7aXp.mjs';
5
5
  export { LunoraError, toErrorResponse } from './packem_shared/LunoraError-Bpb9EFJ3.mjs';
6
6
  export { emitLogEvent, emitRpcEvent } from './packem_shared/emitLogEvent-pEdtqAK8.mjs';
7
- export { analyticsEngineSink, combineSinks, consoleSink, otlpSink, pipelineLogSink, sentrySink, webhookSink } from './packem_shared/analyticsEngineSink-DKZvbDFG.mjs';
7
+ export { analyticsEngineSink, combineSinks, consoleSink, otlpSink, pipelineLogSink, sentrySink, webhookSink } from './packem_shared/analyticsEngineSink-DWUhiYHC.mjs';
8
8
  export { createQueryCoordinator, createStaticShardRegistry, mergeStrategyForAggregate } from './packem_shared/createQueryCoordinator-DNCJzOZE.mjs';
9
9
  export { applyJurisdiction, resolveShard } from './packem_shared/applyJurisdiction-BkZtTkct.mjs';
10
10
  export { decorateResponse, enforceOrigin, handleCorsPreflight, resolveSecurity } from './packem_shared/decorateResponse-DRWQFNhF.mjs';
@@ -1,4 +1,4 @@
1
- import { m as mergeHeaders, o as otlpRandomHex, w as wrapResourceSpans, e as encodeAttribute, a as wrapResourceLogs, O as OTLP_SEVERITY, c as otlpUnixNano } from './otlp-0FQT3AI8.mjs';
1
+ import { m as mergeHeaders, w as wrapResourceSpans, o as otlpRandomHex, a as wrapResourceMetrics, c as wrapResourceLogs, e as encodeAttribute, O as OTLP_SEVERITY, d as otlpUnixNano } from './otlp-DOLuy1Aj.mjs';
2
2
 
3
3
  const stringifyFieldValue = (value) => {
4
4
  if (typeof value === "string") {
@@ -44,22 +44,88 @@ const otlpTraceBody = (event, serviceName, endMs) => {
44
44
  };
45
45
  return wrapResourceSpans(span, "@lunora/runtime", serviceName);
46
46
  };
47
- const otlpLogBody = (event, serviceName) => {
48
- const attributeByKey = /* @__PURE__ */ new Map();
49
- attributeByKey.set("lunora.function_path", encodeAttribute("lunora.function_path", event.functionPath));
50
- if (event.shardKey !== void 0) {
51
- attributeByKey.set("lunora.shard_key", encodeAttribute("lunora.shard_key", event.shardKey));
47
+ const encodeSignalAttributes = (reserved, caller) => {
48
+ const byKey = /* @__PURE__ */ new Map([["lunora.function_path", encodeAttribute("lunora.function_path", reserved.functionPath)]]);
49
+ if (reserved.shardKey !== void 0) {
50
+ byKey.set("lunora.shard_key", encodeAttribute("lunora.shard_key", reserved.shardKey));
52
51
  }
53
- if (event.userId !== void 0) {
54
- attributeByKey.set("lunora.user_id", encodeAttribute("lunora.user_id", event.userId));
52
+ if (reserved.userId !== void 0) {
53
+ byKey.set("lunora.user_id", encodeAttribute("lunora.user_id", reserved.userId));
55
54
  }
56
- if (event.fields) {
57
- for (const [key, value] of Object.entries(event.fields)) {
58
- attributeByKey.set(key, encodeAttribute(key, coerceFieldValue(value)));
59
- }
55
+ if (reserved.errorType !== void 0) {
56
+ byKey.set("error.type", encodeAttribute("error.type", reserved.errorType));
60
57
  }
58
+ for (const [key, value] of Object.entries(caller ?? {})) {
59
+ byKey.set(key, encodeAttribute(key, coerceFieldValue(value)));
60
+ }
61
+ return [...byKey.values()];
62
+ };
63
+ const otlpSpanBody = (event, serviceName) => {
64
+ const span = {
65
+ attributes: encodeSignalAttributes(
66
+ { errorType: event.error?.type, functionPath: event.functionPath, shardKey: event.shardKey, userId: event.userId },
67
+ event.attributes
68
+ ),
69
+ endTimeUnixNano: otlpUnixNano(event.startTs + event.durationMs),
70
+ // Always SPAN_KIND_INTERNAL: only `ctx.trace` spans reach a sink. The
71
+ // synthetic dispatch span is buffered for the Studio waterfall and never
72
+ // exported, because the runtime already emits that dispatch to `onRpc` as
73
+ // a SERVER span — encoding it here too would duplicate it in every trace.
74
+ kind: 1,
75
+ name: event.name,
76
+ parentSpanId: event.parentSpanId,
77
+ spanId: event.spanId,
78
+ startTimeUnixNano: otlpUnixNano(event.startTs),
79
+ // STATUS_CODE_OK (1) / STATUS_CODE_ERROR (2).
80
+ status: event.ok ? { code: 1 } : { code: 2, message: event.error?.message ?? "" },
81
+ traceId: event.traceId
82
+ };
83
+ return wrapResourceSpans(span, "@lunora/runtime", serviceName);
84
+ };
85
+ const otlpMetricBody = (event, serviceName) => {
86
+ const timeUnixNano = otlpUnixNano(event.ts);
87
+ const attributes = encodeSignalAttributes({ functionPath: event.functionPath, shardKey: event.shardKey }, event.attributes);
88
+ const dataPoint = { asDouble: event.value, attributes, timeUnixNano };
89
+ if (event.kind === "gauge") {
90
+ return wrapResourceMetrics({ gauge: { dataPoints: [dataPoint] }, name: event.name }, "@lunora/runtime", serviceName);
91
+ }
92
+ if (event.kind === "histogram") {
93
+ return wrapResourceMetrics(
94
+ {
95
+ histogram: {
96
+ aggregationTemporality: 1,
97
+ dataPoints: [
98
+ {
99
+ attributes,
100
+ bucketCounts: ["1"],
101
+ count: "1",
102
+ explicitBounds: [],
103
+ max: event.value,
104
+ min: event.value,
105
+ // `startTimeUnixNano` omitted for the same reason as
106
+ // the Sum data point above — see the comment there.
107
+ sum: event.value,
108
+ timeUnixNano
109
+ }
110
+ ]
111
+ },
112
+ name: event.name
113
+ },
114
+ "@lunora/runtime",
115
+ serviceName
116
+ );
117
+ }
118
+ return wrapResourceMetrics(
119
+ { name: event.name, sum: { aggregationTemporality: 1, dataPoints: [dataPoint], isMonotonic: true } },
120
+ "@lunora/runtime",
121
+ serviceName
122
+ );
123
+ };
124
+ const otlpLogBody = (event, serviceName) => {
61
125
  const logRecord = {
62
- attributes: [...attributeByKey.values()],
126
+ // Caller-supplied structured fields become log-record attributes so a
127
+ // pipeline can filter/index on them; precedence per `encodeSignalAttributes`.
128
+ attributes: encodeSignalAttributes({ functionPath: event.functionPath, shardKey: event.shardKey, userId: event.userId }, event.fields),
63
129
  body: { stringValue: event.message },
64
130
  severityNumber: OTLP_SEVERITY[event.level],
65
131
  severityText: event.level.toUpperCase(),
@@ -93,6 +159,9 @@ const consoleSink = (options = {}) => {
93
159
  console.log("[lunora:log]", event.functionPath, event.message);
94
160
  }
95
161
  },
162
+ onMetric: (event) => {
163
+ console.log("[lunora:metric]", `${event.name}=${String(event.value)}`, event.kind, event.functionPath);
164
+ },
96
165
  onRpc: (event) => {
97
166
  if (shouldSkip(event, onlyErrors)) {
98
167
  return;
@@ -102,6 +171,10 @@ const consoleSink = (options = {}) => {
102
171
  } else {
103
172
  console.error("[lunora:rpc]", event);
104
173
  }
174
+ },
175
+ onSpan: (event) => {
176
+ const status = event.ok ? "ok" : `error ${event.error?.type ?? ""}`.trim();
177
+ console.log("[lunora:span]", event.name, `${String(event.durationMs)}ms`, status, event.functionPath);
105
178
  }
106
179
  };
107
180
  };
@@ -241,42 +314,51 @@ const otlpSink = (options) => {
241
314
  }
242
315
  const tracesUrl = `${base}/v1/traces`;
243
316
  const logsUrl = `${base}/v1/logs`;
317
+ const metricsUrl = `${base}/v1/metrics`;
244
318
  const mergedHeaders = mergeHeaders({ "content-type": "application/json" }, headers, token);
245
319
  return {
246
320
  onLog: (event, context) => {
247
321
  otlpPost(logsUrl, otlpLogBody(event, serviceName), mergedHeaders, context);
248
322
  },
323
+ onMetric: (event, context) => {
324
+ otlpPost(metricsUrl, otlpMetricBody(event, serviceName), mergedHeaders, context);
325
+ },
249
326
  onRpc: (event, context) => {
250
327
  if (shouldSkip(event, onlyErrors)) {
251
328
  return;
252
329
  }
253
330
  otlpPost(tracesUrl, otlpTraceBody(event, serviceName, Date.now()), mergedHeaders, context);
331
+ },
332
+ onSpan: (event, context) => {
333
+ otlpPost(tracesUrl, otlpSpanBody(event, serviceName), mergedHeaders, context);
254
334
  }
255
335
  };
256
336
  };
257
337
  const combineSinks = (...sinks) => {
338
+ const fanOut = (method, event, context) => {
339
+ for (const sink of sinks) {
340
+ const handler = sink[method];
341
+ if (!handler) {
342
+ continue;
343
+ }
344
+ try {
345
+ handler.call(sink, event, context);
346
+ } catch {
347
+ }
348
+ }
349
+ };
258
350
  return {
259
351
  onLog: (event, context) => {
260
- for (const sink of sinks) {
261
- if (!sink.onLog) {
262
- continue;
263
- }
264
- try {
265
- sink.onLog(event, context);
266
- } catch {
267
- }
268
- }
352
+ fanOut("onLog", event, context);
353
+ },
354
+ onMetric: (event, context) => {
355
+ fanOut("onMetric", event, context);
269
356
  },
270
357
  onRpc: (event, context) => {
271
- for (const sink of sinks) {
272
- if (!sink.onRpc) {
273
- continue;
274
- }
275
- try {
276
- sink.onRpc(event, context);
277
- } catch {
278
- }
279
- }
358
+ fanOut("onRpc", event, context);
359
+ },
360
+ onSpan: (event, context) => {
361
+ fanOut("onSpan", event, context);
280
362
  }
281
363
  };
282
364
  };
@@ -1,6 +1,6 @@
1
1
  import { isLunoraError, toErrorBody } from '@lunora/errors';
2
2
  import { NOOP_EXECUTION_CONTEXT } from './NOOP_EXECUTION_CONTEXT-CCTu0Bf1.mjs';
3
- import { o as otlpRandomHex, b as buildTraceparent } from './otlp-0FQT3AI8.mjs';
3
+ import { o as otlpRandomHex, b as buildTraceparent } from './otlp-DOLuy1Aj.mjs';
4
4
  import { LunoraError, toErrorResponse } from './LunoraError-Bpb9EFJ3.mjs';
5
5
  import { wrapResolverWithContract } from './composeIdentityResolvers-XGjO7V1J.mjs';
6
6
  export { composeIdentityResolvers, routeIdentityResolvers } from './composeIdentityResolvers-XGjO7V1J.mjs';
@@ -81,5 +81,15 @@ const wrapResourceLogs = (logRecord, scopeName, serviceName) => {
81
81
  ]
82
82
  };
83
83
  };
84
+ const wrapResourceMetrics = (metric, scopeName, serviceName) => {
85
+ return {
86
+ resourceMetrics: [
87
+ {
88
+ resource: { attributes: [encodeAttribute("service.name", serviceName)] },
89
+ scopeMetrics: [{ metrics: [metric], scope: { name: scopeName } }]
90
+ }
91
+ ]
92
+ };
93
+ };
84
94
 
85
- export { OTLP_SEVERITY as O, wrapResourceLogs as a, buildTraceparent as b, otlpUnixNano as c, encodeAttribute as e, mergeHeaders as m, otlpRandomHex as o, wrapResourceSpans as w };
95
+ export { OTLP_SEVERITY as O, wrapResourceMetrics as a, buildTraceparent as b, wrapResourceLogs as c, otlpUnixNano as d, encodeAttribute as e, mergeHeaders as m, otlpRandomHex as o, wrapResourceSpans as w };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/runtime",
3
- "version": "1.0.0-alpha.29",
3
+ "version": "1.0.0-alpha.30",
4
4
  "description": "Lunora Worker runtime: the RPC router, shard resolver, and query coordinator",
5
5
  "keywords": [
6
6
  "cloudflare",