@lunora/do 1.0.0-alpha.40 → 1.0.0-alpha.41

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
@@ -2378,14 +2378,66 @@ interface TraceAnchor {
2378
2378
  rootSpanId: string;
2379
2379
  traceId: string;
2380
2380
  }
2381
+ /**
2382
+ * Minimal structural shape of one **Cloudflare custom span** — the object CF's
2383
+ * `tracing.enterSpan(name, (span) => …)` callback receives (GA 2026-06-16). Only
2384
+ * the surface the bridge touches is declared, so `@lunora/do` needs no runtime
2385
+ * dependency on `cloudflare:workers`; the real platform span is structurally
2386
+ * assignable.
2387
+ */
2388
+ interface CloudflareSpanLike {
2389
+ /**
2390
+ * Whether this span is actually being recorded by the runtime's sampler.
2391
+ * `false` off the traced path (unsampled) — the bridge skips its
2392
+ * `setAttribute` work in that case rather than building attribute strings for
2393
+ * a span nobody will read.
2394
+ */
2395
+ readonly isTraced: boolean;
2396
+ /** Attach one primitive attribute to the CF span. */
2397
+ setAttribute: (key: string, value: boolean | number | string | undefined) => void;
2398
+ }
2399
+ /**
2400
+ * Minimal structural shape of the `tracing` namespace exported by
2401
+ * `cloudflare:workers`. `enterSpan` opens a custom span that auto-nests under the
2402
+ * runtime's ambient span and ends when `callback` settles.
2403
+ */
2404
+ interface CloudflareTracingLike {
2405
+ enterSpan: <T>(name: string, callback: (span: CloudflareSpanLike) => T) => T;
2406
+ }
2407
+ /**
2408
+ * Resolves CF's `tracing` namespace, or `undefined` when it is unavailable —
2409
+ * off-Cloudflare, on a compat date predating custom spans, or when
2410
+ * `tracing.enterSpan` is not a function. **Injected, never imported here**, so the
2411
+ * tracer stays pure and unit-testable without `cloudflare:workers`; the shard
2412
+ * supplies the real resolver, tests a fake or `undefined`.
2413
+ */
2414
+ type CloudflareTracingResolver = () => CloudflareTracingLike | Promise<CloudflareTracingLike | undefined> | undefined;
2381
2415
  /** What {@link createTracer} needs from the shard to build a span. */
2382
2416
  interface TracerDeps {
2383
2417
  /** The trace this ctx's spans belong to. */
2384
2418
  anchor: TraceAnchor;
2385
2419
  /** Function path the spans are attributed to. */
2386
2420
  functionPath: string;
2421
+ /**
2422
+ * **Opt-in, EXPERIMENTAL, default off.** When `true` *and*
2423
+ * {@link TracerDeps.resolveCloudflareTracing} yields a working
2424
+ * `tracing.enterSpan`, each `ctx.trace` span is ALSO emitted as a Cloudflare
2425
+ * **custom span**, so it nests inside CF's native binding/fetch/handler trace
2426
+ * tree on the hosted path. This only ADDS a CF-side span — the recorded
2427
+ * {@link SpanEvent} (our `SpanBuffer`/`otlpSink`) is untouched and remains the
2428
+ * source of truth plus the local studio waterfall. See {@link createTracer}
2429
+ * for the double-export and Durable-Object async-context caveats.
2430
+ */
2431
+ fuseCloudflareSpans?: boolean;
2387
2432
  /** Hand a finished span to the buffer + sink. */
2388
2433
  record: (span: SpanEvent) => void;
2434
+ /**
2435
+ * Injected resolver for CF's `tracing` namespace (see
2436
+ * {@link CloudflareTracingResolver}). Only consulted when
2437
+ * {@link TracerDeps.fuseCloudflareSpans} is `true`, so the default path never
2438
+ * calls it.
2439
+ */
2440
+ resolveCloudflareTracing?: CloudflareTracingResolver;
2389
2441
  /** Shard key for single-shard calls; absent for the unnamed root DO. */
2390
2442
  shardKey: string | undefined;
2391
2443
  /** Read lazily — the acting user is resolved per span, not per ctx. */
@@ -2418,6 +2470,30 @@ interface MetricsDeps {
2418
2470
  * cleared in the dispatch `finally`, and a subscription re-run builds its ctx
2419
2471
  * during* the writing mutation's flush — so reading that shared field at span
2420
2472
  * time would file the re-run's spans under the mutation's trace.
2473
+ *
2474
+ * **Cloudflare custom-spans bridge (opt-in, EXPERIMENTAL).** When
2475
+ * `deps.fuseCloudflareSpans` is `true` and `deps.resolveCloudflareTracing` yields
2476
+ * a working `tracing.enterSpan` (`cloudflare:workers`, GA 2026-06-16), each span
2477
+ * body runs inside a CF custom span so our span nests under CF's native
2478
+ * binding/fetch/handler trace tree on the hosted path, and the finished span's
2479
+ * key attributes are mirrored onto it (gated on `span.isTraced`). Two deliberate
2480
+ * boundaries hold.
2481
+ *
2482
+ * **No double-export by default, and never a replacement.** The bridge only ADDS a
2483
+ * CF-side span; the recorded {@link SpanEvent} handed to `record` (our
2484
+ * `SpanBuffer`/`otlpSink`) is byte-for-byte the same as without the bridge and
2485
+ * stays the source of truth. It is off unless explicitly enabled precisely
2486
+ * because, once on, a deployment that ALSO ships our `otlpSink` to a collector
2487
+ * AND lets CF export its trace tree emits the same logical span down two
2488
+ * pipelines — an intentional, documented trade the operator opts into, not a
2489
+ * default.
2490
+ *
2491
+ * **DO async-context caveat.** `tracing.enterSpan`'s parent-linking inside Durable
2492
+ * Objects is unverified upstream, so this is capability-probed: an
2493
+ * absent/undefined `tracing`, a missing `enterSpan`, or an off-CF/unsampled run
2494
+ * all resolve to `undefined` and the bridge is a total no-op — exact prior
2495
+ * behavior. If CF's ambient-span linkage misbehaves in a DO, the worst case is a
2496
+ * mis-parented CF span; our own recorded waterfall is unaffected.
2421
2497
  */
2422
2498
  declare const createTracer: (deps: TracerDeps) => ContextTracer;
2423
2499
  /**
@@ -4324,6 +4400,17 @@ declare const subscriptionListDeltas: (previousJson: string, nextResult: unknown
4324
4400
  * `@lunora/runtime`'s `ObservabilitySink` without taking a dependency on it.
4325
4401
  */
4326
4402
  interface TelemetrySink {
4403
+ /**
4404
+ * **Opt-in, EXPERIMENTAL, default off.** When `true`, each `ctx.trace` span is
4405
+ * ALSO emitted as a Cloudflare **custom span** (`tracing.enterSpan` from
4406
+ * `cloudflare:workers`, GA 2026-06-16) so it nests inside CF's native trace
4407
+ * tree on the hosted path — capability-probed, and a safe no-op off-CF / on an
4408
+ * older compat date / when unsampled. This only ADDS a CF-side span; the
4409
+ * `onSpan` event below (our `SpanBuffer`/`otlpSink`) is unchanged and stays the
4410
+ * source of truth. Mirror of `@lunora/runtime`'s `ObservabilitySink`
4411
+ * `fuseCloudflareTraces`; see {@link createTracer} for the double-export caveat.
4412
+ */
4413
+ fuseCloudflareTraces?: boolean;
4327
4414
  onLog?: (event: LogEventInput, context?: LogSinkContext) => void;
4328
4415
  onMetric?: (event: MetricEvent, context?: LogSinkContext) => void;
4329
4416
  onSpan?: (event: SpanEvent, context?: LogSinkContext) => void;
@@ -5921,6 +6008,9 @@ declare abstract class ShardDO {
5921
6008
  * `anchor` is the trace this ctx's spans belong to; omit it for a ctx with no
5922
6009
  * owning dispatch (an alarm, a subscription re-run) to mint a fresh anchor, so
5923
6010
  * `ctx.trace` still yields a coherent self-contained trace there.
6011
+ *
6012
+ * The Cloudflare custom-spans bridge is threaded here but stays off unless the
6013
+ * resolved sink sets `fuseCloudflareTraces` (see {@link resolveCloudflareTracing}).
5924
6014
  */
5925
6015
  protected makeTracer(functionPath: string, sink?: TelemetrySink, anchor?: TraceAnchor): ContextTracer;
5926
6016
  /**
package/dist/index.d.ts CHANGED
@@ -2378,14 +2378,66 @@ interface TraceAnchor {
2378
2378
  rootSpanId: string;
2379
2379
  traceId: string;
2380
2380
  }
2381
+ /**
2382
+ * Minimal structural shape of one **Cloudflare custom span** — the object CF's
2383
+ * `tracing.enterSpan(name, (span) => …)` callback receives (GA 2026-06-16). Only
2384
+ * the surface the bridge touches is declared, so `@lunora/do` needs no runtime
2385
+ * dependency on `cloudflare:workers`; the real platform span is structurally
2386
+ * assignable.
2387
+ */
2388
+ interface CloudflareSpanLike {
2389
+ /**
2390
+ * Whether this span is actually being recorded by the runtime's sampler.
2391
+ * `false` off the traced path (unsampled) — the bridge skips its
2392
+ * `setAttribute` work in that case rather than building attribute strings for
2393
+ * a span nobody will read.
2394
+ */
2395
+ readonly isTraced: boolean;
2396
+ /** Attach one primitive attribute to the CF span. */
2397
+ setAttribute: (key: string, value: boolean | number | string | undefined) => void;
2398
+ }
2399
+ /**
2400
+ * Minimal structural shape of the `tracing` namespace exported by
2401
+ * `cloudflare:workers`. `enterSpan` opens a custom span that auto-nests under the
2402
+ * runtime's ambient span and ends when `callback` settles.
2403
+ */
2404
+ interface CloudflareTracingLike {
2405
+ enterSpan: <T>(name: string, callback: (span: CloudflareSpanLike) => T) => T;
2406
+ }
2407
+ /**
2408
+ * Resolves CF's `tracing` namespace, or `undefined` when it is unavailable —
2409
+ * off-Cloudflare, on a compat date predating custom spans, or when
2410
+ * `tracing.enterSpan` is not a function. **Injected, never imported here**, so the
2411
+ * tracer stays pure and unit-testable without `cloudflare:workers`; the shard
2412
+ * supplies the real resolver, tests a fake or `undefined`.
2413
+ */
2414
+ type CloudflareTracingResolver = () => CloudflareTracingLike | Promise<CloudflareTracingLike | undefined> | undefined;
2381
2415
  /** What {@link createTracer} needs from the shard to build a span. */
2382
2416
  interface TracerDeps {
2383
2417
  /** The trace this ctx's spans belong to. */
2384
2418
  anchor: TraceAnchor;
2385
2419
  /** Function path the spans are attributed to. */
2386
2420
  functionPath: string;
2421
+ /**
2422
+ * **Opt-in, EXPERIMENTAL, default off.** When `true` *and*
2423
+ * {@link TracerDeps.resolveCloudflareTracing} yields a working
2424
+ * `tracing.enterSpan`, each `ctx.trace` span is ALSO emitted as a Cloudflare
2425
+ * **custom span**, so it nests inside CF's native binding/fetch/handler trace
2426
+ * tree on the hosted path. This only ADDS a CF-side span — the recorded
2427
+ * {@link SpanEvent} (our `SpanBuffer`/`otlpSink`) is untouched and remains the
2428
+ * source of truth plus the local studio waterfall. See {@link createTracer}
2429
+ * for the double-export and Durable-Object async-context caveats.
2430
+ */
2431
+ fuseCloudflareSpans?: boolean;
2387
2432
  /** Hand a finished span to the buffer + sink. */
2388
2433
  record: (span: SpanEvent) => void;
2434
+ /**
2435
+ * Injected resolver for CF's `tracing` namespace (see
2436
+ * {@link CloudflareTracingResolver}). Only consulted when
2437
+ * {@link TracerDeps.fuseCloudflareSpans} is `true`, so the default path never
2438
+ * calls it.
2439
+ */
2440
+ resolveCloudflareTracing?: CloudflareTracingResolver;
2389
2441
  /** Shard key for single-shard calls; absent for the unnamed root DO. */
2390
2442
  shardKey: string | undefined;
2391
2443
  /** Read lazily — the acting user is resolved per span, not per ctx. */
@@ -2418,6 +2470,30 @@ interface MetricsDeps {
2418
2470
  * cleared in the dispatch `finally`, and a subscription re-run builds its ctx
2419
2471
  * during* the writing mutation's flush — so reading that shared field at span
2420
2472
  * time would file the re-run's spans under the mutation's trace.
2473
+ *
2474
+ * **Cloudflare custom-spans bridge (opt-in, EXPERIMENTAL).** When
2475
+ * `deps.fuseCloudflareSpans` is `true` and `deps.resolveCloudflareTracing` yields
2476
+ * a working `tracing.enterSpan` (`cloudflare:workers`, GA 2026-06-16), each span
2477
+ * body runs inside a CF custom span so our span nests under CF's native
2478
+ * binding/fetch/handler trace tree on the hosted path, and the finished span's
2479
+ * key attributes are mirrored onto it (gated on `span.isTraced`). Two deliberate
2480
+ * boundaries hold.
2481
+ *
2482
+ * **No double-export by default, and never a replacement.** The bridge only ADDS a
2483
+ * CF-side span; the recorded {@link SpanEvent} handed to `record` (our
2484
+ * `SpanBuffer`/`otlpSink`) is byte-for-byte the same as without the bridge and
2485
+ * stays the source of truth. It is off unless explicitly enabled precisely
2486
+ * because, once on, a deployment that ALSO ships our `otlpSink` to a collector
2487
+ * AND lets CF export its trace tree emits the same logical span down two
2488
+ * pipelines — an intentional, documented trade the operator opts into, not a
2489
+ * default.
2490
+ *
2491
+ * **DO async-context caveat.** `tracing.enterSpan`'s parent-linking inside Durable
2492
+ * Objects is unverified upstream, so this is capability-probed: an
2493
+ * absent/undefined `tracing`, a missing `enterSpan`, or an off-CF/unsampled run
2494
+ * all resolve to `undefined` and the bridge is a total no-op — exact prior
2495
+ * behavior. If CF's ambient-span linkage misbehaves in a DO, the worst case is a
2496
+ * mis-parented CF span; our own recorded waterfall is unaffected.
2421
2497
  */
2422
2498
  declare const createTracer: (deps: TracerDeps) => ContextTracer;
2423
2499
  /**
@@ -4324,6 +4400,17 @@ declare const subscriptionListDeltas: (previousJson: string, nextResult: unknown
4324
4400
  * `@lunora/runtime`'s `ObservabilitySink` without taking a dependency on it.
4325
4401
  */
4326
4402
  interface TelemetrySink {
4403
+ /**
4404
+ * **Opt-in, EXPERIMENTAL, default off.** When `true`, each `ctx.trace` span is
4405
+ * ALSO emitted as a Cloudflare **custom span** (`tracing.enterSpan` from
4406
+ * `cloudflare:workers`, GA 2026-06-16) so it nests inside CF's native trace
4407
+ * tree on the hosted path — capability-probed, and a safe no-op off-CF / on an
4408
+ * older compat date / when unsampled. This only ADDS a CF-side span; the
4409
+ * `onSpan` event below (our `SpanBuffer`/`otlpSink`) is unchanged and stays the
4410
+ * source of truth. Mirror of `@lunora/runtime`'s `ObservabilitySink`
4411
+ * `fuseCloudflareTraces`; see {@link createTracer} for the double-export caveat.
4412
+ */
4413
+ fuseCloudflareTraces?: boolean;
4327
4414
  onLog?: (event: LogEventInput, context?: LogSinkContext) => void;
4328
4415
  onMetric?: (event: MetricEvent, context?: LogSinkContext) => void;
4329
4416
  onSpan?: (event: SpanEvent, context?: LogSinkContext) => void;
@@ -5921,6 +6008,9 @@ declare abstract class ShardDO {
5921
6008
  * `anchor` is the trace this ctx's spans belong to; omit it for a ctx with no
5922
6009
  * owning dispatch (an alarm, a subscription re-run) to mint a fresh anchor, so
5923
6010
  * `ctx.trace` still yields a coherent self-contained trace there.
6011
+ *
6012
+ * The Cloudflare custom-spans bridge is threaded here but stays off unless the
6013
+ * resolved sink sets `fuseCloudflareTraces` (see {@link resolveCloudflareTracing}).
5924
6014
  */
5925
6015
  protected makeTracer(functionPath: string, sink?: TelemetrySink, anchor?: TraceAnchor): ContextTracer;
5926
6016
  /**
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@ export { AGGREGATE_SQL_FUNCTION, aggregateSqlFunction, matchesStaticWhere, norma
3
3
  export { aggregateTableName, coerceAggregateNumber, encodeAggregateKey, foldAggregateTally, readAggregateValue } from './packem_shared/aggregateTableName-CxNqY1Sl.mjs';
4
4
  export { CountRlsUnsupportedError, mergeWhere, planAggregateLookup, selectIndexForAggregate, selectIndexForCount, selectIndexForGroupBy } from './packem_shared/CountRlsUnsupportedError-BGxj0pgS.mjs';
5
5
  export { AUTH_METRICS_BUCKETS_TABLE, AUTH_METRICS_BUCKET_MS, AUTH_METRICS_BUCKET_RETENTION, AUTH_METRICS_TABLE, ensureAuthMetricsTables, readAuthMetrics, recordAuthEvent } from './packem_shared/AUTH_METRICS_BUCKETS_TABLE-CiHHYeJi.mjs';
6
- export { c as createMetrics, a as createTracer, d as dispatchRootSpan } from './packem_shared/context-telemetry-BVSDl6PU.mjs';
6
+ export { c as createMetrics, a as createTracer, d as dispatchRootSpan } from './packem_shared/context-telemetry-CDpyil58.mjs';
7
7
  export { NotUniqueError, assertValidClientId, createShardCtxDb, normalizeIdStructurally } from './packem_shared/NotUniqueError-DbtlWwcG.mjs';
8
8
  export { DATA_MIGRATION_STATE_TABLE, readMigrationStatus, runDataMigration } from './packem_shared/DATA_MIGRATION_STATE_TABLE-CYwBpyTr.mjs';
9
9
  export { SCAN_DEP, createDependencyTracker, depKey } from './packem_shared/SCAN_DEP-DLJF8dsj.mjs';
@@ -28,7 +28,7 @@ export { RLS_UNWRAP_SYMBOL, RlsRequiredError, guardWriter } from './packem_share
28
28
  export { buildFtsMatch, ftsTableName, scoreDocument, stringifySearchText, tokenizeSearch } from './packem_shared/buildFtsMatch-BLEMawrp.mjs';
29
29
  export { M as MIN_ADMIN_TOKEN_LENGTH, a as MIN_AUTH_SECRET_LENGTH, b as buildSecurityAudit } from './packem_shared/security-audit-CucgBice.mjs';
30
30
  export { SESSION_DO_TTL_DEFAULT, SessionDO } from './packem_shared/SESSION_DO_TTL_DEFAULT-BnSKgVO4.mjs';
31
- export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO } from './packem_shared/ROOT_DO_SIZE_WARN_BYTES-COEnnBhO.mjs';
31
+ export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO } from './packem_shared/ROOT_DO_SIZE_WARN_BYTES-B4Cpm7Qv.mjs';
32
32
  export { SHARD_REGISTRY_DO_NAME, ShardRegistryDO } from './packem_shared/SHARD_REGISTRY_DO_NAME-D99roc-r.mjs';
33
33
  export { MAX_SQL_ROWS, assertReadonly, runReadonlySql } from './packem_shared/MAX_SQL_ROWS-iFAA8FbD.mjs';
34
34
  export { createSystemReader } from './packem_shared/createSystemReader-D12eNH13.mjs';
@@ -2,7 +2,7 @@ import { LunoraError, toErrorBody } from '@lunora/errors';
2
2
  import { drizzle } from 'drizzle-orm/durable-sqlite';
3
3
  import { c as constantTimeEqual } from './constant-time-equal-BVRWZgES.mjs';
4
4
  import { j as jsonResponse } from './json-response-BdbtpOhm.mjs';
5
- import { n as normalizeLogFields, r as resolveTraceAnchor, p as parseTraceparent, a as createTracer, c as createMetrics, d as dispatchRootSpan } from './context-telemetry-BVSDl6PU.mjs';
5
+ import { n as normalizeLogFields, r as resolveTraceAnchor, p as parseTraceparent, a as createTracer, c as createMetrics, d as dispatchRootSpan } from './context-telemetry-CDpyil58.mjs';
6
6
  import { e as encodeWire, d as decodeWire } from './wire-codec-CzQc1pvf.mjs';
7
7
  import { parseExportShardArgs, parseImportShardArgs } from './exportShardRows-Dy3oFZ26.mjs';
8
8
  import { recordAuthEvent, readAuthMetrics } from './AUTH_METRICS_BUCKETS_TABLE-CiHHYeJi.mjs';
@@ -1968,6 +1968,21 @@ const findDanglingReferences = (sql, storageColumns, liveKeys) => {
1968
1968
  const WS_KEEPALIVE_PING = "lunora-ping";
1969
1969
  const WS_KEEPALIVE_PONG = "lunora-pong";
1970
1970
  const REQUIRE_EPHEMERAL_ENV_VALUES = /* @__PURE__ */ new Set(["1", "enabled", "on", "true", "yes"]);
1971
+ let cloudflareTracingResolved = false;
1972
+ let cloudflareTracing;
1973
+ const resolveCloudflareTracing = async () => {
1974
+ if (!cloudflareTracingResolved) {
1975
+ cloudflareTracingResolved = true;
1976
+ try {
1977
+ const cloudflareModule = await import('cloudflare:workers');
1978
+ const candidate = cloudflareModule.tracing;
1979
+ cloudflareTracing = candidate !== null && typeof candidate === "object" && typeof candidate.enterSpan === "function" ? candidate : void 0;
1980
+ } catch {
1981
+ cloudflareTracing = void 0;
1982
+ }
1983
+ }
1984
+ return cloudflareTracing;
1985
+ };
1971
1986
  const UNDELIVERED_BASELINE = "<undelivered>";
1972
1987
  const ROOT_DO_SIZE_WARN_BYTES = 1073741824;
1973
1988
  const CDC_RESUME_SCAN_LIMIT = 1e4;
@@ -4786,14 +4801,19 @@ class ShardDO {
4786
4801
  * `anchor` is the trace this ctx's spans belong to; omit it for a ctx with no
4787
4802
  * owning dispatch (an alarm, a subscription re-run) to mint a fresh anchor, so
4788
4803
  * `ctx.trace` still yields a coherent self-contained trace there.
4804
+ *
4805
+ * The Cloudflare custom-spans bridge is threaded here but stays off unless the
4806
+ * resolved sink sets `fuseCloudflareTraces` (see {@link resolveCloudflareTracing}).
4789
4807
  */
4790
4808
  makeTracer(functionPath, sink, anchor) {
4791
4809
  return createTracer({
4792
4810
  anchor: anchor ?? resolveTraceAnchor(void 0),
4811
+ fuseCloudflareSpans: sink?.fuseCloudflareTraces === true,
4793
4812
  functionPath,
4794
4813
  record: (span) => {
4795
4814
  this.recordSpan(span, sink);
4796
4815
  },
4816
+ resolveCloudflareTracing,
4797
4817
  shardKey: this.state.id?.name,
4798
4818
  userId: () => this.getCurrentUserId()
4799
4819
  });
@@ -63,8 +63,31 @@ const toErrorType = (error) => {
63
63
  return error instanceof Error ? error.constructor.name : "Error";
64
64
  };
65
65
 
66
+ const applyCloudflareSpanAttributes = (span, meta) => {
67
+ if (!span.isTraced) {
68
+ return;
69
+ }
70
+ span.setAttribute("lunora.function_path", meta.functionPath);
71
+ span.setAttribute("lunora.ok", meta.ok);
72
+ span.setAttribute("lunora.duration_ms", meta.durationMs);
73
+ if (meta.shardKey !== void 0) {
74
+ span.setAttribute("lunora.shard_key", meta.shardKey);
75
+ }
76
+ if (meta.userId !== void 0) {
77
+ span.setAttribute("lunora.user_id", meta.userId);
78
+ }
79
+ if (meta.error !== void 0) {
80
+ span.setAttribute("lunora.error.type", meta.error.type);
81
+ span.setAttribute("lunora.error.message", meta.error.message);
82
+ }
83
+ for (const [key, value] of Object.entries(meta.attributes)) {
84
+ if (typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
85
+ span.setAttribute(`lunora.attr.${key}`, value);
86
+ }
87
+ }
88
+ };
66
89
  const createTracer = (deps) => {
67
- const { anchor, functionPath, record, shardKey, userId } = deps;
90
+ const { anchor, fuseCloudflareSpans, functionPath, record, resolveCloudflareTracing, shardKey, userId } = deps;
68
91
  const tracerFor = (parentSpanId) => async (name, function_, attributes) => {
69
92
  const spanId = otlpRandomHex(8);
70
93
  const startTs = Date.now();
@@ -78,39 +101,64 @@ const createTracer = (deps) => {
78
101
  Object.assign(collected, normalizeLogFields(fields));
79
102
  }
80
103
  };
81
- let ok = true;
82
- let error;
83
- try {
84
- return await function_(tracerFor(spanId), spanHandle);
85
- } catch (error_) {
86
- ok = false;
87
- error = {
88
- message: error_ instanceof Error ? error_.message : String(error_),
89
- // Prefer a LunoraError's stable `code` over the class name so
90
- // spans group by the same taxonomy the RPC spans use.
91
- type: toErrorType(error_)
92
- };
93
- throw error_;
94
- } finally {
104
+ const runRecorded = async (cfSpan) => {
105
+ let ok = true;
106
+ let error;
95
107
  try {
108
+ return await function_(tracerFor(spanId), spanHandle);
109
+ } catch (error_) {
110
+ ok = false;
111
+ error = {
112
+ message: error_ instanceof Error ? error_.message : String(error_),
113
+ // Prefer a LunoraError's stable `code` over the class name
114
+ // so spans group by the same taxonomy the RPC spans use.
115
+ type: toErrorType(error_)
116
+ };
117
+ throw error_;
118
+ } finally {
119
+ const durationMs = Date.now() - startTs;
120
+ const resolvedUserId = userId();
96
121
  const merged = { ...normalized, ...collected };
97
- record({
98
- ...Object.keys(merged).length === 0 ? {} : { attributes: merged },
99
- durationMs: Date.now() - startTs,
100
- ...error === void 0 ? {} : { error },
101
- functionPath,
102
- name,
103
- ok,
104
- parentSpanId,
105
- shardKey,
106
- spanId,
107
- startTs,
108
- traceId: anchor.traceId,
109
- userId: userId()
110
- });
111
- } catch {
122
+ try {
123
+ record({
124
+ ...Object.keys(merged).length === 0 ? {} : { attributes: merged },
125
+ durationMs,
126
+ ...error === void 0 ? {} : { error },
127
+ functionPath,
128
+ name,
129
+ ok,
130
+ parentSpanId,
131
+ shardKey,
132
+ spanId,
133
+ startTs,
134
+ traceId: anchor.traceId,
135
+ userId: resolvedUserId
136
+ });
137
+ } catch {
138
+ }
139
+ if (cfSpan !== void 0) {
140
+ try {
141
+ applyCloudflareSpanAttributes(cfSpan, {
142
+ attributes: merged,
143
+ durationMs,
144
+ error,
145
+ functionPath,
146
+ ok,
147
+ shardKey,
148
+ userId: resolvedUserId
149
+ });
150
+ } catch {
151
+ }
152
+ }
153
+ }
154
+ };
155
+ if (fuseCloudflareSpans === true && resolveCloudflareTracing !== void 0) {
156
+ const cfTracing = await resolveCloudflareTracing();
157
+ if (cfTracing !== void 0 && typeof cfTracing.enterSpan === "function") {
158
+ return await cfTracing.enterSpan(name, (span) => runRecorded(span));
112
159
  }
113
160
  }
161
+ return await runRecorded();
114
162
  };
115
163
  return tracerFor(anchor.rootSpanId);
116
164
  };
@@ -172,4 +220,4 @@ const dispatchRootSpan = (input) => {
172
220
  };
173
221
  };
174
222
 
175
- export { createTracer as a, createMetrics as c, dispatchRootSpan as d, normalizeLogFields as n, parseTraceparent as p, resolveTraceAnchor as r };
223
+ export { createTracer as a, applyCloudflareSpanAttributes as b, createMetrics as c, dispatchRootSpan as d, normalizeLogFields as n, parseTraceparent as p, resolveTraceAnchor as r };
@@ -0,0 +1 @@
1
+ export { b as applyCloudflareSpanAttributes, c as createMetrics, a as createTracer, d as dispatchRootSpan } from './context-telemetry-CDpyil58.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/do",
3
- "version": "1.0.0-alpha.40",
3
+ "version": "1.0.0-alpha.41",
4
4
  "description": "Lunora Durable Objects: ShardDO (SQLite, OCC, hibernated WebSocket subscriptions) and SessionDO",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -1 +0,0 @@
1
- export { c as createMetrics, a as createTracer, d as dispatchRootSpan } from './context-telemetry-BVSDl6PU.mjs';