@lunora/do 1.0.0-alpha.45 → 1.0.0-alpha.47

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
@@ -2323,6 +2323,124 @@ interface MetricEvent {
2323
2323
  */
2324
2324
  value: number;
2325
2325
  }
2326
+ /**
2327
+ * Severity of a `ctx.log.*` call. The five console method names (`log` is the
2328
+ * default level, distinct from `info`) plus `trace`/`fatal`, so the logger spans
2329
+ * the full OpenTelemetry severity ramp (`trace`→`fatal`).
2330
+ */
2331
+ type ContextLogLevel = "debug" | "error" | "fatal" | "info" | "log" | "trace" | "warn";
2332
+ /**
2333
+ * Per-event context handed to a sink alongside the event: lets a sink register
2334
+ * background work (a telemetry POST, a durable pipeline send) with the request's
2335
+ * `waitUntil` so it survives isolate teardown after the response returns. Absent
2336
+ * `waitUntil` (no request context) means the sink falls back to fire-and-forget.
2337
+ */
2338
+ interface LogSinkContext {
2339
+ /**
2340
+ * Resolves this request's detected OTLP resource attributes (`service.version`,
2341
+ * `cloud.region`, …) on demand, or absent when the host does not detect any.
2342
+ *
2343
+ * Deliberately a resolved, allowlisted bag behind a thunk rather than the raw
2344
+ * `env` and `Request` the host detected them from: this context is fanned out
2345
+ * to **every** registered sink, including user-authored ones, so anything
2346
+ * reachable here should be assumed to end up in someone's debug log — and raw
2347
+ * `env` is every secret binding, while a raw `Request` carries the caller's
2348
+ * `Authorization` and `Cookie`. The thunk keeps detection lazy (a sink that
2349
+ * does not want resource attributes pays nothing) and hosts are expected to
2350
+ * memoize it per request.
2351
+ */
2352
+ resourceAttributes?: () => Record<string, boolean | number | string>;
2353
+ /** Keep a background promise alive past the response (the request's `waitUntil`). */
2354
+ waitUntil?: (promise: Promise<unknown>) => void;
2355
+ }
2356
+ /**
2357
+ * One application log line emitted from a function handler via `ctx.log`.
2358
+ * Produced per `ctx.log.*` call (unlike a per-dispatch RPC summary).
2359
+ */
2360
+ interface LogEvent {
2361
+ /** Raw arguments passed to the `ctx.log.*` call, in order. */
2362
+ args: unknown[];
2363
+ /**
2364
+ * OTel `LogRecord.eventName` — set when the line was emitted as a **structured
2365
+ * event** via `ctx.log.event(name, fields)` rather than as a human-readable
2366
+ * log line.
2367
+ *
2368
+ * The distinction is the whole point of the Events API: a log line's payload
2369
+ * is its `message` (prose, for a human, unstable), while an event's payload is
2370
+ * its `fields` (a named schema, for a query, stable). A collector that knows
2371
+ * `eventName` can index and aggregate the latter; without it, "how many
2372
+ * checkouts failed" degrades into a substring search over prose.
2373
+ *
2374
+ * Absent for ordinary `ctx.log.*` calls.
2375
+ */
2376
+ eventName?: string;
2377
+ /**
2378
+ * Structured fields the caller attached (`ctx.log.info(message, fields)` or a
2379
+ * bound `ctx.log.with(fields)` child), already normalized to a fresh bag of
2380
+ * JSON-safe primitives (see `shared/log-fields.ts`). Absent for a plain
2381
+ * console-style call.
2382
+ */
2383
+ fields?: LogFields;
2384
+ /** Function path that emitted the line, e.g. `"messages:list"`. */
2385
+ functionPath: string;
2386
+ /** Severity the line was logged at. */
2387
+ level: ContextLogLevel;
2388
+ /** Display string — the message, or the console-style args rendered and space-joined. */
2389
+ message: string;
2390
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
2391
+ shardKey?: string;
2392
+ /** Span id of the RPC this line was emitted under (trace correlation), or absent. */
2393
+ spanId?: string;
2394
+ /** Trace id this line belongs to (from the inbound `traceparent`), or absent. */
2395
+ traceId?: string;
2396
+ /** Wall-clock millis when the line was emitted. */
2397
+ ts: number;
2398
+ /** Acting userId, or absent when anonymous. */
2399
+ userId?: string;
2400
+ }
2401
+ /**
2402
+ * The OTel `SpanKind` union, in the spec's own words rather than its wire
2403
+ * numbers, so a call site reads `{ kind: "client" }` instead of `{ kind: 3 }`.
2404
+ *
2405
+ * Kind is not cosmetic: a service map is built from it. A CLIENT span with no
2406
+ * matching SERVER span on the other side is a dropped hop; PRODUCER/CONSUMER is
2407
+ * what makes a queue render as an async edge rather than a synchronous call.
2408
+ * Getting it wrong is why "everything is INTERNAL" traces produce no topology.
2409
+ */
2410
+ type OtlpSpanKind = "client" | "consumer" | "internal" | "producer" | "server";
2411
+ /**
2412
+ * One timestamped occurrence inside a span — OTel's `Span.events`.
2413
+ *
2414
+ * The right shape for something that has a moment but no duration: a retry, a
2415
+ * cache miss, a validation failure, a thrown exception. Modelling those as
2416
+ * near-zero-width child spans clutters the waterfall, and modelling them as
2417
+ * separate log lines loses the "which span was I in" correlation that makes them
2418
+ * useful in the first place.
2419
+ */
2420
+ interface SpanEventPoint {
2421
+ /** Structured attributes, normalized like a span's own. */
2422
+ attributes?: LogFields;
2423
+ /** Event name, e.g. `"exception"` or `"cache.miss"`. */
2424
+ name: string;
2425
+ /** Wall-clock millis when it happened. */
2426
+ ts: number;
2427
+ }
2428
+ /**
2429
+ * A causal reference to a span in ANOTHER trace — OTel's `Span.links`.
2430
+ *
2431
+ * The standard answer to fan-in: a queue consumer processing a batch of 100
2432
+ * messages links to the 100 producing spans rather than parenting to one of them
2433
+ * (arbitrary) or all of them (impossible). The traces stay separately navigable
2434
+ * and the causal edge survives.
2435
+ */
2436
+ interface SpanLink {
2437
+ /** Attributes describing the relationship, e.g. `{ "link.kind": "enqueued_by" }`. */
2438
+ attributes?: LogFields;
2439
+ /** Linked span id (16-hex). */
2440
+ spanId: string;
2441
+ /** Linked trace id (32-hex). */
2442
+ traceId: string;
2443
+ }
2326
2444
  /**
2327
2445
  * One span produced by a `ctx.trace(name, fn)` call, or the synthetic root span
2328
2446
  * the shard records for the dispatch itself so a waterfall has a bar to hang
@@ -2342,10 +2460,61 @@ interface MetricEvent {
2342
2460
  * value winning on a key clash.
2343
2461
  */
2344
2462
  interface SpanHandle {
2463
+ /**
2464
+ * Record a timestamped {@link SpanEventPoint} on the enclosing span — a retry,
2465
+ * a cache miss, a state transition. Prefer this over an extra `ctx.log` line
2466
+ * for anything that only makes sense *relative to this span*: it rides the
2467
+ * span's own export, so it costs no additional log record and can never be
2468
+ * separated from its context.
2469
+ */
2470
+ addEvent: (name: string, attributes?: LogFields) => void;
2471
+ /**
2472
+ * Link this span to one in another trace (see {@link SpanLink}) — how a batch
2473
+ * consumer points back at the requests that enqueued its items without
2474
+ * collapsing every producer into one giant trace.
2475
+ */
2476
+ addLink: (link: SpanLink) => void;
2477
+ /**
2478
+ * Record a caught exception as the OTel-conventional `exception` span event
2479
+ * (`exception.type` / `exception.message` / `exception.stacktrace`).
2480
+ *
2481
+ * Distinct from letting the error propagate: this is for an error you
2482
+ * **handled** — a retried request, a fallback that worked — which should be
2483
+ * visible in the trace without marking the span failed. An error that escapes
2484
+ * the span body is recorded automatically and *does* set the error status.
2485
+ */
2486
+ recordException: (error: unknown) => void;
2345
2487
  /** Set one attribute on the enclosing span (merged at record time; post-hoc wins on key clash). */
2346
2488
  setAttribute: (key: string, value: LogFields[string]) => void;
2347
2489
  /** Merge attributes onto the enclosing span (post-hoc wins on key clash). */
2348
2490
  setAttributes: (fields: LogFields) => void;
2491
+ /**
2492
+ * The W3C ids of the span this handle refers to.
2493
+ *
2494
+ * A handle that cannot say WHICH span it is forces every consumer that needs
2495
+ * the identity — a `traceparent` for a hand-rolled outbound call, a trace id
2496
+ * echoed in an error response so a user can quote it in a bug report, an
2497
+ * `@opentelemetry/api` bridge parenting a third-party library's spans — to
2498
+ * reach around the API for it.
2499
+ */
2500
+ spanContext: () => {
2501
+ spanId: string;
2502
+ traceId: string;
2503
+ };
2504
+ }
2505
+ /** Options accepted by `ctx.trace(name, fn, options)` beyond the plain attribute bag. */
2506
+ interface SpanOptions {
2507
+ /** Start attributes, snapshotted before the body runs. */
2508
+ attributes?: LogFields;
2509
+ /**
2510
+ * OTel `SpanKind`, default `"internal"`. Set `"client"` for a call OUT to
2511
+ * another service, `"producer"`/`"consumer"` for queue hops — this is what a
2512
+ * collector builds its service map from, so leaving everything `"internal"`
2513
+ * yields a trace with no topology.
2514
+ */
2515
+ kind?: OtlpSpanKind;
2516
+ /** Links to spans in other traces, known at start (see {@link SpanLink}). */
2517
+ links?: SpanLink[];
2349
2518
  }
2350
2519
  interface SpanEvent {
2351
2520
  /**
@@ -2356,6 +2525,12 @@ interface SpanEvent {
2356
2525
  attributes?: LogFields;
2357
2526
  /** Wall-clock duration of the span body, in milliseconds. */
2358
2527
  durationMs: number;
2528
+ /**
2529
+ * Timestamped occurrences inside the span (see {@link SpanEventPoint}) —
2530
+ * `ctx.trace`'s `span.addEvent(...)` / `span.recordException(...)`. Absent
2531
+ * when the body recorded none.
2532
+ */
2533
+ events?: SpanEventPoint[];
2359
2534
  /**
2360
2535
  * Populated when the span body threw. `type` is the error's constructor name
2361
2536
  * (or its `LunoraError` code); `message` is the human-readable string and may
@@ -2372,6 +2547,14 @@ interface SpanEvent {
2372
2547
  * reuses its context — the same attribution rule `ctx.log` follows.
2373
2548
  */
2374
2549
  functionPath: string;
2550
+ /**
2551
+ * OTel `SpanKind`. Absent means `"internal"` — the overwhelming majority of
2552
+ * `ctx.trace` spans — so the common case costs no bytes on the wire and every
2553
+ * pre-existing recorded span stays valid.
2554
+ */
2555
+ kind?: OtlpSpanKind;
2556
+ /** Causal references to spans in other traces (see {@link SpanLink}). Absent when none. */
2557
+ links?: SpanLink[];
2375
2558
  /** Caller-supplied span name, e.g. `"stripe.charge"`. */
2376
2559
  name: string;
2377
2560
  /** True when the span body returned without throwing. */
@@ -2417,7 +2600,7 @@ interface SpanEvent {
2417
2600
  * which it can attach attributes only known *after* it resolves (post-hoc). It is
2418
2601
  * a trailing parameter, so a `(trace) => …` body that ignores it still conforms.
2419
2602
  */
2420
- type ContextTracer = <T>(name: string, function_: (trace: ContextTracer, span: SpanHandle) => Promise<T> | T, attributes?: LogFields) => Promise<T>;
2603
+ type ContextTracer = <T>(name: string, function_: (trace: ContextTracer, span: SpanHandle) => Promise<T> | T, options?: LogFields | SpanOptions) => Promise<T>;
2421
2604
  /** Structural shape of the `ctx.metrics` recorder (see the server `LunoraMetrics`). */
2422
2605
  interface ContextMetrics {
2423
2606
  count: (name: string, value?: number, attributes?: LogFields) => void;
@@ -2427,6 +2610,13 @@ interface ContextMetrics {
2427
2610
  /** The trace a ctx's spans hang off: the shared id, and the span they parent to. */
2428
2611
  interface TraceAnchor {
2429
2612
  rootSpanId: string;
2613
+ /**
2614
+ * The W3C `sampled` verdict for this trace, inherited from the inbound
2615
+ * `traceparent` when there was one. Carried on the anchor rather than
2616
+ * re-derived per outbound call so every `ctx.fetch` of one dispatch
2617
+ * propagates the same answer.
2618
+ */
2619
+ sampled?: boolean;
2430
2620
  traceId: string;
2431
2621
  }
2432
2622
  /**
@@ -2503,6 +2693,12 @@ interface MetricsDeps {
2503
2693
  record: (event: MetricEvent) => void;
2504
2694
  shardKey: string | undefined;
2505
2695
  }
2696
+ /** Everything a {@link SpanHandle}'s body attached, ready to merge into the recorded span. */
2697
+ interface SpanCollection {
2698
+ attributes: Record<string, LogFields[string]>;
2699
+ events: SpanEventPoint[];
2700
+ links: SpanLink[];
2701
+ }
2506
2702
  /**
2507
2703
  * Build the `ctx.trace` span factory for one dispatched function.
2508
2704
  *
@@ -2560,6 +2756,8 @@ interface MetricsDeps {
2560
2756
  * waterfall is unaffected.
2561
2757
  */
2562
2758
  declare const createTracer: (deps: TracerDeps) => ContextTracer;
2759
+ /** The `fetch` shape `ctx.fetch` exposes — the platform global's, narrowed to what we wrap. */
2760
+ type ContextFetch = (input: Request | string | URL, init?: RequestInit) => Promise<Response>;
2563
2761
  /**
2564
2762
  * Build the `ctx.metrics` recorder for one dispatched function.
2565
2763
  *
@@ -2582,6 +2780,14 @@ declare const createMetrics: (deps: MetricsDeps) => ContextMetrics;
2582
2780
  */
2583
2781
  declare const dispatchRootSpan: (input: {
2584
2782
  anchor: TraceAnchor;
2783
+ /**
2784
+ * What the handler attached to the dispatch through `ctx.span` — the **wide
2785
+ * event**. These are the attributes that would otherwise have been scattered
2786
+ * across a dozen `ctx.log` lines; carrying them on the one span that already
2787
+ * exists per request is the OTel-native way to get a wide event without
2788
+ * multiplying log records.
2789
+ */
2790
+ collected?: SpanCollection;
2585
2791
  durationMs: number;
2586
2792
  failure: {
2587
2793
  thrown: unknown;
@@ -3787,67 +3993,6 @@ declare const pointInBoundingBox: (point: GeoPoint, box: GeoBoundingBox) => bool
3787
3993
  * in the box is scanned before the exact `pointInBoundingBox` refine.
3788
3994
  */
3789
3995
  declare const boundingBoxGeohashes: (box: GeoBoundingBox) => string[];
3790
- /**
3791
- * Severity of a `ctx.log.*` call. The five console method names (`log` is the
3792
- * default level, distinct from `info`) plus `trace`/`fatal`, so the logger spans
3793
- * the full OpenTelemetry severity ramp (`trace`→`fatal`).
3794
- */
3795
- type ContextLogLevel = "debug" | "error" | "fatal" | "info" | "log" | "trace" | "warn";
3796
- /**
3797
- * Per-event context handed to a sink alongside the event: lets a sink register
3798
- * background work (a telemetry POST, a durable pipeline send) with the request's
3799
- * `waitUntil` so it survives isolate teardown after the response returns. Absent
3800
- * `waitUntil` (no request context) means the sink falls back to fire-and-forget.
3801
- */
3802
- interface LogSinkContext {
3803
- /**
3804
- * Resolves this request's detected OTLP resource attributes (`service.version`,
3805
- * `cloud.region`, …) on demand, or absent when the host does not detect any.
3806
- *
3807
- * Deliberately a resolved, allowlisted bag behind a thunk rather than the raw
3808
- * `env` and `Request` the host detected them from: this context is fanned out
3809
- * to **every** registered sink, including user-authored ones, so anything
3810
- * reachable here should be assumed to end up in someone's debug log — and raw
3811
- * `env` is every secret binding, while a raw `Request` carries the caller's
3812
- * `Authorization` and `Cookie`. The thunk keeps detection lazy (a sink that
3813
- * does not want resource attributes pays nothing) and hosts are expected to
3814
- * memoize it per request.
3815
- */
3816
- resourceAttributes?: () => Record<string, boolean | number | string>;
3817
- /** Keep a background promise alive past the response (the request's `waitUntil`). */
3818
- waitUntil?: (promise: Promise<unknown>) => void;
3819
- }
3820
- /**
3821
- * One application log line emitted from a function handler via `ctx.log`.
3822
- * Produced per `ctx.log.*` call (unlike a per-dispatch RPC summary).
3823
- */
3824
- interface LogEvent {
3825
- /** Raw arguments passed to the `ctx.log.*` call, in order. */
3826
- args: unknown[];
3827
- /**
3828
- * Structured fields the caller attached (`ctx.log.info(message, fields)` or a
3829
- * bound `ctx.log.with(fields)` child), already normalized to a fresh bag of
3830
- * JSON-safe primitives (see `shared/log-fields.ts`). Absent for a plain
3831
- * console-style call.
3832
- */
3833
- fields?: LogFields;
3834
- /** Function path that emitted the line, e.g. `"messages:list"`. */
3835
- functionPath: string;
3836
- /** Severity the line was logged at. */
3837
- level: ContextLogLevel;
3838
- /** Display string — the message, or the console-style args rendered and space-joined. */
3839
- message: string;
3840
- /** Shard key for single-shard calls; absent for the unnamed root DO. */
3841
- shardKey?: string;
3842
- /** Span id of the RPC this line was emitted under (trace correlation), or absent. */
3843
- spanId?: string;
3844
- /** Trace id this line belongs to (from the inbound `traceparent`), or absent. */
3845
- traceId?: string;
3846
- /** Wall-clock millis when the line was emitted. */
3847
- ts: number;
3848
- /** Acting userId, or absent when anonymous. */
3849
- userId?: string;
3850
- }
3851
3996
  /**
3852
3997
  * Severity of a buffered log entry — the full seven-tier `ctx.log` ramp
3853
3998
  * (`trace`→`fatal`), not a console-shaped subset. The buffer used to fold the
@@ -4413,6 +4558,18 @@ declare class SessionDO {
4413
4558
  private handleGet;
4414
4559
  private handleRevoke;
4415
4560
  }
4561
+ /**
4562
+ * How much detail `ctx.db` auto-instrumentation produces.
4563
+ *
4564
+ * `"summary"` (default) — aggregate counters on the dispatch's wide event: no
4565
+ * extra spans, no extra log records, and a cost that does not grow with call count.
4566
+ *
4567
+ * `"spans"` — one span per database call. The full waterfall, at the price of a
4568
+ * span per call; right when diagnosing, noisy as a permanent default.
4569
+ *
4570
+ * `"off"` — no database telemetry at all.
4571
+ */
4572
+ type DatabaseInstrumentation = "off" | "spans" | "summary";
4416
4573
  /** One table's resolved TTL policy, as surfaced to the DO alarm by the generated shard subclass. */
4417
4574
  interface TtlSweepSpec {
4418
4575
  /** Millisecond offset added to `field` to derive the expiry (`field + after`); absent ⇒ `field` is the absolute expiry. */
@@ -4481,6 +4638,15 @@ declare const subscriptionListDeltas: (previousJson: string, nextResult: unknown
4481
4638
  * `@lunora/runtime`'s `ObservabilitySink` without taking a dependency on it.
4482
4639
  */
4483
4640
  interface TelemetrySink {
4641
+ /**
4642
+ * Ship anything the sink has buffered, now. Called at the end of every
4643
+ * dispatch (and of an alarm / socket message), so a batching sink — which
4644
+ * exports one request per invocation instead of one per event — is never left
4645
+ * holding telemetry a quiet shard would sit on indefinitely. Optional: a
4646
+ * non-buffering sink simply omits it. Mirror of `@lunora/runtime`'s
4647
+ * `ObservabilitySink.flush`.
4648
+ */
4649
+ flush?: (context?: LogSinkContext) => void;
4484
4650
  /**
4485
4651
  * **Opt-in, EXPERIMENTAL, default off.** When `true`, each `ctx.trace` span is
4486
4652
  * ALSO emitted as a Cloudflare **custom span** (`tracing.enterSpan` from
@@ -4495,9 +4661,25 @@ interface TelemetrySink {
4495
4661
  * `fuseCloudflareTraces`; see {@link createTracer} for the double-export caveat.
4496
4662
  */
4497
4663
  fuseCloudflareTraces?: boolean;
4664
+ /**
4665
+ * Detail level for automatic `ctx.db` instrumentation. Default `"summary"` —
4666
+ * aggregate counters folded onto the dispatch's root span when one is recorded,
4667
+ * so cost does not grow with call count and an uninstrumented handler still
4668
+ * emits nothing extra. Mirror of `@lunora/runtime`'s `ObservabilitySink`.
4669
+ */
4670
+ instrumentDatabase?: DatabaseInstrumentation;
4498
4671
  onLog?: (event: LogEventInput, context?: LogSinkContext) => void;
4499
4672
  onMetric?: (event: MetricEvent, context?: LogSinkContext) => void;
4500
4673
  onSpan?: (event: SpanEvent, context?: LogSinkContext) => void;
4674
+ /**
4675
+ * Whether `ctx.fetch` is instrumented — a CLIENT span per outbound call plus
4676
+ * W3C `traceparent` propagation to the callee. Default on; set `false` to get
4677
+ * the bare platform `fetch`, or an object to control which destinations
4678
+ * receive trace context. Mirror of `@lunora/runtime`'s `ObservabilitySink`.
4679
+ */
4680
+ traceFetch?: boolean | {
4681
+ propagate?: ((url: URL) => boolean) | boolean;
4682
+ };
4501
4683
  }
4502
4684
  /**
4503
4685
  * Structural shape of the `ctx.log` logger the DO builds (see the server
@@ -4507,6 +4689,20 @@ interface TelemetrySink {
4507
4689
  interface ContextLogger {
4508
4690
  debug: (...args: unknown[]) => void;
4509
4691
  error: (...args: unknown[]) => void;
4692
+ /**
4693
+ * Emit a **structured event** — OTel's Events API — rather than a log line.
4694
+ *
4695
+ * The difference is what carries the meaning. A log line's payload is its
4696
+ * message: prose, written for a human, free to be reworded next week. An
4697
+ * event's payload is its `fields` under a stable `name`, written for a query.
4698
+ * Only the second can answer "how many checkouts failed, by plan, this hour"
4699
+ * without a substring search over English.
4700
+ *
4701
+ * On the wire this sets OTel's `LogRecord.eventName` (plus the `event.name`
4702
+ * attribute for collectors predating that field), so any OTLP backend
4703
+ * recognises it without Lunora-specific configuration.
4704
+ */
4705
+ event: (name: string, fields?: LogFields) => void;
4510
4706
  fatal: (...args: unknown[]) => void;
4511
4707
  info: (...args: unknown[]) => void;
4512
4708
  log: (...args: unknown[]) => void;
@@ -4947,6 +5143,34 @@ declare abstract class ShardDO {
4947
5143
  * by `span.traceId`, and deleted in the `finally`.
4948
5144
  */
4949
5145
  private traceSampling;
5146
+ /**
5147
+ * The per-dispatch **wide event** — everything a handler attached through
5148
+ * `ctx.span` — keyed by `traceId` for exactly the reason `traceSampling`
5149
+ * is: a DO interleaves dispatches across `await` points, and a flat field
5150
+ * would let a sibling dispatch's attributes land on this one's span.
5151
+ *
5152
+ * A wide event is the answer to "monitor everything without drowning in
5153
+ * logs": rather than a dozen `ctx.log.info` lines whose only readers are
5154
+ * humans grepping, a handler accumulates its facts onto the ONE span the
5155
+ * dispatch already emits, and the collector gets a single richly-attributed
5156
+ * record it can group and aggregate. Cost is flat — one span per request,
5157
+ * however much you attach.
5158
+ *
5159
+ * Keyed by {@link dispatchSpanKey} (trace id AND root span id) rather than
5160
+ * `traceId` alone — see there for the concurrent-dispatch collision that
5161
+ * distinction prevents.
5162
+ *
5163
+ * Bounded by {@link MAX_TRACKED_DISPATCH_SPANS}: the dispatch `finally`
5164
+ * deletes its own entry, but a ctx built outside a dispatch (an alarm, a
5165
+ * subscription re-run) mints its own anchor and has no such boundary, so the
5166
+ * map is FIFO-capped rather than trusted to drain.
5167
+ */
5168
+ private dispatchSpans;
5169
+ /**
5170
+ * The most recent telemetry sink seen while building a ctx — the flush handle
5171
+ * for paths that have no ctx of their own (see `flushTelemetry`).
5172
+ */
5173
+ private lastTelemetrySink;
4950
5174
  /**
4951
5175
  * Client-issued idempotency key for the in-flight mutation, forwarded via the
4952
5176
  * `x-lunora-mutation-id` header. When set, the dispatch path dedups the call
@@ -5223,6 +5447,19 @@ declare abstract class ShardDO {
5223
5447
  * Hibernation API: invoked by the runtime when a message arrives on a
5224
5448
  * hibernated socket. Subclasses can override this to intercept; the
5225
5449
  * default decodes a {@link SubscriptionEnvelope} and updates the registry.
5450
+ *
5451
+ * Deliberately NOT wrapped in {@link withTriggerTrace}, unlike `alarm`. Frame
5452
+ * rate here is unbounded — a whisper fan-out or presence stream drives many
5453
+ * per second — and minting a trace anchor per frame costs two `crypto`
5454
+ * draws plus hex encoding, which measurably regressed the fan-out benchmark
5455
+ * (~30% on `broadcastWhisper` to 128 members). The trade is also worse than it
5456
+ * looks: a frame's work is a subscription re-evaluation whose data flow is
5457
+ * already attributable to the RPC that wrote the data, so the root span buys
5458
+ * little. An alarm is the opposite — low frequency, and genuinely
5459
+ * un-attributable background work — which is why that one keeps the wrapper.
5460
+ *
5461
+ * `ctx.trace`/`ctx.span` inside a frame still record; they just anchor to the
5462
+ * ctx's own trace rather than a per-frame root.
5226
5463
  */
5227
5464
  webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void>;
5228
5465
  /**
@@ -6071,7 +6308,7 @@ declare abstract class ShardDO {
6071
6308
  * Unlike request-log args, `ctx.log` args are NOT redacted: the developer
6072
6309
  * chose to log them, exactly like a raw `console.log`.
6073
6310
  */
6074
- protected recordUserLog(functionPath: string, level: ContextLogLevel, args: unknown[], message: string, fields: Record<string, unknown> | undefined, sink?: TelemetrySink): void;
6311
+ protected recordUserLog(functionPath: string, level: ContextLogLevel, args: unknown[], message: string, fields: Record<string, unknown> | undefined, sink?: TelemetrySink, eventName?: string, anchor?: TraceAnchor): void;
6075
6312
  /**
6076
6313
  * Build the `ctx.log` logger for one dispatched function. Each severity method
6077
6314
  * accepts either the structured form (`(message, fields)`) or console-style
@@ -6097,6 +6334,44 @@ declare abstract class ShardDO {
6097
6334
  * resolved sink sets `fuseCloudflareTraces` (see {@link resolveCloudflareTracing}).
6098
6335
  */
6099
6336
  protected makeTracer(functionPath: string, sink?: TelemetrySink, anchor?: TraceAnchor): ContextTracer;
6337
+ /**
6338
+ * The trace anchor a ctx's `trace` and `span` both hang off.
6339
+ *
6340
+ * Resolved once per `buildCtx` and shared, so `ctx.trace` spans and the
6341
+ * `ctx.span` wide event land in the SAME trace. Previously each consumer
6342
+ * minted its own fallback when there was no current trace, which was fine
6343
+ * while `ctx.trace` was the only consumer and silently splits the two now
6344
+ * that there are two.
6345
+ *
6346
+ * `identityScoped` marks a deferred/interleaved caller (a subscription seed
6347
+ * or refresh): those must NOT inherit the shared per-request trace, which a
6348
+ * concurrent RPC may have re-set, so they mint their own self-contained one.
6349
+ */
6350
+ protected resolveDispatchAnchor(identityScoped: boolean): TraceAnchor;
6351
+ /**
6352
+ * Wrap `ctx.db` in automatic instrumentation — see {@link instrumentDatabase}
6353
+ * for why the default is aggregate counters rather than a span per call.
6354
+ *
6355
+ * A no-op (returning the database untouched) with no sink configured or with
6356
+ * `instrumentDatabase: "off"`, so a deployment that collects nothing pays
6357
+ * nothing.
6358
+ */
6359
+ protected instrumentDb<T extends object>(database: T, functionPath: string, anchor: TraceAnchor, sink?: TelemetrySink): T;
6360
+ /**
6361
+ * Build `ctx.fetch` — the platform `fetch`, instrumented.
6362
+ *
6363
+ * Every outbound call becomes a CLIENT span and carries a `traceparent` to
6364
+ * the callee, so time spent waiting on someone else's service stops being an
6365
+ * unexplained gap in the waterfall and the callee's spans join this trace
6366
+ * instead of starting an unrelated one.
6367
+ *
6368
+ * Falls back to the bare global `fetch` when no sink is configured or the
6369
+ * sink opted out via `traceFetch: false` — there is no point paying for spans
6370
+ * nobody collects, and an app calling a third party it would rather not send
6371
+ * trace ids to needs a way to say so.
6372
+ */
6373
+ protected makeFetch(functionPath: string, anchor: TraceAnchor, sink?: TelemetrySink): ContextFetch;
6374
+ protected makeDispatchSpan(anchor: TraceAnchor, sink?: TelemetrySink): SpanHandle;
6100
6375
  /**
6101
6376
  * Build the `ctx.metrics` recorder for one dispatched function. Thin wiring
6102
6377
  * over {@link createMetrics}, which owns the instrument semantics.
@@ -6116,6 +6391,70 @@ declare abstract class ShardDO {
6116
6391
  * cross-instance aggregation is still the sink's job.
6117
6392
  */
6118
6393
  protected recordMetric(event: MetricEvent, sink?: TelemetrySink): void;
6394
+ /** The decode + route body of {@link webSocketMessage}, split out so the trace wrapper stays a one-liner. */
6395
+ protected handleWebSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void>;
6396
+ /**
6397
+ * The alarm's actual work, split out so {@link alarm} is a one-line trace
6398
+ * wrapper. An alarm drives `.global()` shape refreshes and external-source
6399
+ * ingest with no client waiting on a response, which is exactly where a
6400
+ * silent failure hides longest — so it gets a root span like any dispatch.
6401
+ */
6402
+ private handleAlarmBody;
6403
+ /**
6404
+ * Build `ctx.span` — the handle onto the **dispatch's own span**, and with it
6405
+ * the wide-event surface.
6406
+ *
6407
+ * `ctx.trace(...)` creates a *new* child span for a sub-operation; this
6408
+ * attaches to the one that already exists for the request. That is the
6409
+ * distinction between "time this thing" and "record a fact about this
6410
+ * request", and conflating them is why instrumentation usually degrades into
6411
+ * log spam: with nowhere to put a fact, people reach for `ctx.log.info`.
6412
+ *
6413
+ * Attributes accumulate across the whole dispatch and are folded into the
6414
+ * root span in `recordDispatchRootSpan` — the OTel-native form of a
6415
+ * wide event, needing no non-standard "canonical log line" convention on the
6416
+ * collector side.
6417
+ *
6418
+ * Keyed by `dispatchSpanKey` — trace id AND root span id — so two concurrent
6419
+ * dispatches forwarded under the same client trace accumulate separately.
6420
+ */
6421
+ /**
6422
+ * The dispatch entry's db tally, created on first use. Shares the entry with
6423
+ * `ctx.span`'s collector but is deliberately a separate slot — see
6424
+ * `instrumentDb`.
6425
+ */
6426
+ private dispatchTally;
6427
+ /**
6428
+ * Give a NON-`fetch` Durable Object trigger — an alarm, an inbound socket
6429
+ * frame — the same telemetry an RPC dispatch gets: its own trace anchor, a
6430
+ * dispatch root span, and a flush of the batching sink when it finishes.
6431
+ *
6432
+ * These paths were previously invisible. An alarm can drive `.global()` shape
6433
+ * refreshes and external-source ingest, and a socket frame can run a whole
6434
+ * subscription re-evaluation, but neither produced a root span — so any
6435
+ * `ctx.trace` span they created hung off a freshly-minted anchor with nothing
6436
+ * above it, and a collector showed orphans with no bar explaining what caused
6437
+ * them. Alarms are also precisely where a silent failure hides longest,
6438
+ * because no client is waiting on a response to notice.
6439
+ *
6440
+ * The anchor is published on `currentRequestTrace` ONLY when nothing else has
6441
+ * claimed it, and restored afterwards, so a concurrently-interleaved RPC
6442
+ * dispatch (which captured its own anchor in a local at entry) keeps its
6443
+ * attribution. Worst case under interleaving is a mis-attributed inner span —
6444
+ * the same trade the surrounding code already makes with this field — never a
6445
+ * corrupted or lost one.
6446
+ */
6447
+ private withTriggerTrace;
6448
+ /**
6449
+ * Ask the last-seen telemetry sink to ship what it has buffered.
6450
+ *
6451
+ * Used by the trigger paths ({@link withTriggerTrace}), which have no `ctx`
6452
+ * and therefore no direct handle on `config.observability`. The sink is a
6453
+ * per-worker singleton in every real configuration, so remembering the most
6454
+ * recent one is exact in practice and harmless otherwise: a flush is
6455
+ * idempotent and a sink with an empty buffer is a no-op.
6456
+ */
6457
+ private flushTelemetry;
6119
6458
  /**
6120
6459
  * Buffer the synthetic root span for a finished dispatch. The caller gates
6121
6460
  * this on the dispatch having actually produced spans (the `hasTrace` check at
@@ -6126,8 +6465,31 @@ declare abstract class ShardDO {
6126
6465
  * `anchor` carries the dispatch's trace ids, captured at entry rather than
6127
6466
  * read from `this` here — this runs after the handler's awaits, where the
6128
6467
  * shared field may already belong to an interleaved dispatch.
6468
+ *
6469
+ * When the handler attached a **wide event** through `ctx.span`, this also
6470
+ * exports it — see {@link exportWideEvent} for why it goes out as an OTel
6471
+ * Event record rather than on the span itself.
6129
6472
  */
6130
6473
  private recordDispatchRootSpan;
6474
+ /**
6475
+ * Export a dispatch's wide event as a standard OTel **Event** log record
6476
+ * (`lunora.dispatch`), correlated to the dispatch's trace and span.
6477
+ *
6478
+ * **Why a log record rather than the span's attributes.** The local dispatch
6479
+ * root span shares its `spanId` with the SERVER span `@lunora/runtime` emits
6480
+ * for the same dispatch — they are the same logical span, seen from the two
6481
+ * sides of the shard hop. Exporting our copy too would put two partial spans
6482
+ * with identical `trace_id`/`span_id` on the wire, which collectors resolve
6483
+ * inconsistently (merge, last-write, or duplicate). An Event record carrying
6484
+ * `traceId`/`spanId` is unambiguous, is the OTel-sanctioned shape for exactly
6485
+ * this ("a named, structured occurrence"), and every OTLP backend can group
6486
+ * and aggregate it with no Lunora-specific configuration.
6487
+ *
6488
+ * The span still carries the attributes LOCALLY, which is what the Studio
6489
+ * waterfall renders — so the wide event is visible in both places, exported
6490
+ * exactly once.
6491
+ */
6492
+ private exportWideEvent;
6131
6493
  /**
6132
6494
  * Buffer one span for the studio Traces panel and hand it to the optional
6133
6495
  * `sink.onSpan`. Best-effort throughout, exactly like {@link recordUserLog}: