@lunora/runtime 1.0.0-alpha.39 → 1.0.0-alpha.40

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.
Files changed (29) hide show
  1. package/dist/index.d.mts +136 -20
  2. package/dist/index.d.ts +136 -20
  3. package/dist/index.mjs +1 -1
  4. package/dist/packem_shared/{DEFAULT_LOG_COLUMNS-CpZ9kd-q.mjs → DEFAULT_LOG_COLUMNS-B0imn1l0.mjs} +1 -1
  5. package/dist/packem_shared/{DEFAULT_REGISTRY_CACHE_TTL_MS-BBf3mNrD.mjs → DEFAULT_REGISTRY_CACHE_TTL_MS-CsNnfJzB.mjs} +1 -1
  6. package/dist/packem_shared/HEALTH_PATH-BEE0A_lZ.mjs +1 -0
  7. package/dist/packem_shared/{LOG_ARCHIVE_PATH-e8U3ftfM.mjs → LOG_ARCHIVE_PATH-DGsEec3K.mjs} +1 -1
  8. package/dist/packem_shared/{LunoraError-C08OP5Uq.mjs → LunoraError-ByasbDmd.mjs} +1 -1
  9. package/dist/packem_shared/analyticsEngineSink-CUWYuLHs.mjs +1 -0
  10. package/dist/packem_shared/{composeIdentityResolvers-DlBbYmBJ.mjs → composeIdentityResolvers-DwE0Jbww.mjs} +1 -1
  11. package/dist/packem_shared/composeWorker-IO6LYYK2.mjs +6 -0
  12. package/dist/packem_shared/{createCrossShardRelationCapabilities-B2EKbSEs.mjs → createCrossShardRelationCapabilities-CMWiFA5s.mjs} +1 -1
  13. package/dist/packem_shared/{createQueryCoordinator-DLsrcOkB.mjs → createQueryCoordinator-DqrEvhPD.mjs} +1 -1
  14. package/dist/packem_shared/{decorateResponse-C6TZSzID.mjs → decorateResponse-DBIWsRSZ.mjs} +1 -1
  15. package/dist/packem_shared/emitLogEvent-BcdChmSV.mjs +1 -0
  16. package/dist/packem_shared/memoizeIdentity-CZGtDc4H.mjs +1 -0
  17. package/dist/packem_shared/observability-DYnm-d4g.mjs +1 -0
  18. package/dist/packem_shared/otlp-resource-B-ByO9qo.mjs +1 -0
  19. package/dist/packem_shared/pipeline-log-reader-DU_CCGDA.mjs +1 -0
  20. package/package.json +1 -1
  21. package/dist/packem_shared/GET_AUTH_AUDIT_LOG_OP-DmxHQbZT.mjs +0 -1
  22. package/dist/packem_shared/HEALTH_PATH-DiZqjEGp.mjs +0 -1
  23. package/dist/packem_shared/analyticsEngineSink-DOoyBx4o.mjs +0 -1
  24. package/dist/packem_shared/composeWorker-D7Zi6kZR.mjs +0 -6
  25. package/dist/packem_shared/emitLogEvent-CEbCh6FQ.mjs +0 -1
  26. package/dist/packem_shared/memoizeIdentity-DRYIK1yT.mjs +0 -1
  27. package/dist/packem_shared/observability-B0MYwNDB.mjs +0 -1
  28. package/dist/packem_shared/otlp-resource-cAjGEywx.mjs +0 -1
  29. package/dist/packem_shared/pipeline-log-reader-tDuvc_Ty.mjs +0 -1
package/dist/index.d.mts CHANGED
@@ -168,6 +168,21 @@ interface TraceSamplingConfig {
168
168
  */
169
169
  headRate?: number;
170
170
  }
171
+ /**
172
+ * The resolved per-trace decision. `isTraced` is the cheap head verdict callers
173
+ * check to skip work up front; `keepErrors` carries the tail-bias toggle to the
174
+ * export boundary, where the trace's final error status is known.
175
+ */
176
+ interface TraceSamplingDecision {
177
+ /**
178
+ * True when the trace passed head sampling. Callers may cheaply skip buffering
179
+ * or exporting a trace's spans when this is false — subject to the error-keep
180
+ * re-check at the export boundary (see {@link shouldExportTrace}).
181
+ */
182
+ isTraced: boolean;
183
+ /** Whether error traces are force-kept at export even when `isTraced` is false. */
184
+ keepErrors: boolean;
185
+ }
171
186
  /** A timestamp as better-auth stores it: epoch-ms, an ISO string, or absent. */
172
187
  type AuthTimestamp = null | number | string;
173
188
  /**
@@ -1288,6 +1303,7 @@ interface HealthCheckReport {
1288
1303
  critical: boolean;
1289
1304
  /** Present only in the `admin` posture. */
1290
1305
  message?: string;
1306
+ /** The redacted probe kind (`d1`, `r2`, `d1#2`, …) in the `public` posture; the full `kind:key` name in `admin`. */
1291
1307
  name: string;
1292
1308
  status: "down" | "up";
1293
1309
  }
@@ -1309,7 +1325,12 @@ interface HealthRouteDeps {
1309
1325
  auth?: HealthAuthPosture;
1310
1326
  /**
1311
1327
  * Cache the last computed report for this many ms so an orchestrator polling
1312
- * every few seconds does not hammer the bindings. Defaults to `0` (no cache).
1328
+ * every few seconds does not hammer the bindings (each uncached request runs
1329
+ * a real Durable Object subrequest plus one `SELECT 1` per detected D1
1330
+ * binding, which an unauthenticated flood would otherwise amplify). Cached
1331
+ * independently for the aggregate and readiness probes, since their bodies
1332
+ * differ. When omitted it defaults to `5000` in the `public` posture and `0`
1333
+ * (no cache) in the bearer-gated `admin` posture.
1313
1334
  */
1314
1335
  cacheTtlMs?: number;
1315
1336
  /** Admin-bearer predicate, consulted only when `auth === "admin"`. */
@@ -1630,14 +1651,30 @@ type PipelineLogField = keyof typeof DEFAULT_COLUMNS;
1630
1651
  * Data Catalog tables.
1631
1652
  */
1632
1653
  type PipelineLogColumnMap = Partial<Record<PipelineLogField, string>>;
1633
- /** An opaque keyset cursor: the `ts` of the row after the last one returned. */
1654
+ /**
1655
+ * An opaque keyset cursor. `ts` is the epoch-millis of the **last returned** row
1656
+ * (not an un-returned overflow row), so the next page resumes *inclusively* at
1657
+ * that boundary and never skips a row that shares that millisecond. Because `ts`
1658
+ * is not unique, `seen` carries the identity hashes of the already-returned rows
1659
+ * sitting exactly on that boundary `ts`, so the next page can drop them without
1660
+ * re-emitting them. Both fields are JSON-serialisable; consumers pass the whole
1661
+ * object back unchanged.
1662
+ */
1634
1663
  interface PipelineLogCursor {
1635
- /** Epoch-millis boundary; the next page is every row strictly older than this. */
1664
+ /**
1665
+ * Identity hashes (see the internal `hashRow`) of the rows already returned
1666
+ * that share the boundary `ts`. The next page fetches every row at-or-older-than
1667
+ * the boundary and filters out any whose hash is in this set, so boundary rows
1668
+ * are neither dropped nor duplicated. Only ever holds rows at the single
1669
+ * boundary `ts`; omitted (or empty) when the boundary carries no already-returned ties.
1670
+ */
1671
+ seen?: string[];
1672
+ /** Epoch-millis boundary; the next page is every row at or older than this. */
1636
1673
  ts: number;
1637
1674
  }
1638
1675
  /** Filters for one {@link PipelineLogReader} query. Every value is inlined safely (`lit`/`sql`). */
1639
1676
  interface PipelineLogQuery {
1640
- /** Continue after a previous page (keyset on `ts DESC`). Combined with the other filters. */
1677
+ /** Continue after a previous page (inclusive keyset on `ts DESC`, dedup'd by {@link PipelineLogCursor} `seen`). Combined with the other filters. */
1641
1678
  cursor?: PipelineLogCursor;
1642
1679
  /** Match only this exact function path's records. Prefer `functionPathPrefix` for a namespace sweep. */
1643
1680
  functionPath?: string;
@@ -2105,6 +2142,29 @@ interface ObservabilitySink {
2105
2142
  * Applies only when a sink is configured; with none, `ctx.db` is untouched.
2106
2143
  */
2107
2144
  instrumentDatabase?: "off" | "spans" | "summary";
2145
+ /**
2146
+ * **Opt-in, default `false`.** Durable per-minute rollups of every
2147
+ * `ctx.metrics.*` measurement, kept in a reserved per-shard SQLite table so the
2148
+ * Studio can chart a 24h local trend without an external collector.
2149
+ *
2150
+ * Off by default because it is **not free**: each `ctx.metrics.count/gauge/record`
2151
+ * becomes a durable SQLite write (a billed, rate-limited storage op that competes
2152
+ * with your app's own data for the shard's write budget), on the request path. The
2153
+ * live cross-instance path is `onMetric` → your collector; this is only the local
2154
+ * trend convenience, so enable it deliberately.
2155
+ *
2156
+ * `true` uses the built-in caps (≈24h retention, 1000 distinct series). Pass an
2157
+ * object to tune them: `maxSeries` bounds the distinct series tracked (a
2158
+ * high-cardinality attribute otherwise mints a series per value), `retentionBuckets`
2159
+ * the minute-buckets kept per series before older ones are trimmed.
2160
+ *
2161
+ * Pass this on the SAME sink object you give `createShardDO` — the DO reads it
2162
+ * when recording a measurement.
2163
+ */
2164
+ metricHistory?: boolean | {
2165
+ maxSeries?: number;
2166
+ retentionBuckets?: number;
2167
+ };
2108
2168
  /** Invoked once per `ctx.log.*` call from a function handler. */
2109
2169
  onLog?: (event: LogEvent, context?: ObservabilitySinkContext) => void;
2110
2170
  /**
@@ -2141,13 +2201,22 @@ interface ObservabilitySink {
2141
2201
  * sink-originating throw bubble up past this point. `context.waitUntil`, when
2142
2202
  * supplied, lets a network sink keep its send alive past the response.
2143
2203
  *
2144
- * `sampling` applies the trace-sampling verdict to this dispatch's SERVER span:
2145
- * the event is dropped unless the trace was head-sampled or (with errors
2146
- * force-kept) this dispatch errored the tail bias. A dispatch with no
2147
- * `traceId` (a fan-out aggregation, which mints none) is always kept, and an
2148
- * absent `sampling` keeps everything, so both are backward-compatible.
2204
+ * The verdict applied to this dispatch's SERVER span comes from `decision` when
2205
+ * the caller settled one pass the dispatch's already-settled decision so the
2206
+ * export gate can never disagree with the propagated `traceparent` (a trace kept
2207
+ * or dropped as a whole, per PR #191). Its `isTraced` must carry the propagated
2208
+ * sampled bit (`trace.sampled`), not the raw head verdict, so a trusted upstream
2209
+ * that sampled the trace out keeps its SERVER span out here too. The event is
2210
+ * dropped unless the trace was sampled or (with errors force-kept) this dispatch
2211
+ * errored — the tail bias.
2212
+ *
2213
+ * `sampling` is the legacy fallback for callers with no settled decision: it
2214
+ * re-derives the verdict from `event.traceId`. A dispatch with no `traceId` (a
2215
+ * fan-out aggregation, which mints none) is always kept, and both an absent
2216
+ * `decision` and an absent `sampling` keep everything, so all are
2217
+ * backward-compatible.
2149
2218
  */
2150
- declare const emitRpcEvent: (sink: ObservabilitySink | undefined, event: ObservabilityEvent, context?: ObservabilitySinkContext, sampling?: TraceSamplingConfig) => void;
2219
+ declare const emitRpcEvent: (sink: ObservabilitySink | undefined, event: ObservabilityEvent, context?: ObservabilitySinkContext, sampling?: TraceSamplingConfig, decision?: TraceSamplingDecision) => void;
2151
2220
  /**
2152
2221
  * Invoke `sink.onLog` with the given log event, swallowing any error the sink
2153
2222
  * throws. The same failure model as {@link emitRpcEvent}: a buggy log sink must
@@ -2709,7 +2778,9 @@ type FunctionRegistryLike = Record<string, FunctionRegistryEntry>;
2709
2778
  type X402ChargeGate = (request: Request, spec: {
2710
2779
  functionPath: string;
2711
2780
  price: number | string;
2712
- }, dispatch: () => Promise<Response>) => Promise<Response>;
2781
+ }, dispatch: () => Promise<Response>, deps?: {
2782
+ waitUntil?: (promise: Promise<unknown>) => void;
2783
+ }) => Promise<Response>;
2713
2784
  /**
2714
2785
  * Lists objects in the storage bucket for the admin file browser. Structurally
2715
2786
  * compatible with `@lunora/storage`'s `Storage["list"]` — the runtime stays free
@@ -2971,7 +3042,7 @@ interface HealthOptions {
2971
3042
  * includes the (runtime-authored) messages.
2972
3043
  */
2973
3044
  auth?: "admin" | "public";
2974
- /** Cache the computed report for this many ms so a frequent poller does not re-run every probe. Defaults to `0`. */
3045
+ /** Cache the computed report for this many ms so a frequent poller (or an unauthenticated flood) does not re-run every probe. Defaults to `5000` for the public posture and `0` (no cache) for the bearer-gated admin posture. */
2975
3046
  cacheTtlMs?: number;
2976
3047
  /** Skip the auto-registered D1 / R2 / queue / Hyperdrive binding probes (keep only the DO probe + `probes`). Defaults to `false`. */
2977
3048
  disableBindingProbes?: boolean;
@@ -3016,18 +3087,35 @@ interface NotifySubscriptionDevice {
3016
3087
  */
3017
3088
  interface NotifySubscriptionStoreLike {
3018
3089
  /**
3019
- * List every stored subscription. Declared with NO parameter so a concrete
3020
- * `@lunora/notify` `SubscriptionStore` — whose `list(filter?)` narrows `kind`
3021
- * to the `"web-push" | "fcm"` union assigns cleanly under
3022
- * `strictFunctionTypes` (an extra optional parameter on the source is fine).
3023
- * The RPC handler applies the `{ kind, userId }` filter in-memory, so no typed
3024
- * filter needs to cross this dependency-free structural boundary.
3025
- */
3026
- list: () => Promise<ReadonlyArray<NotifySubscriptionDevice & {
3090
+ * List stored subscriptions, optionally narrowed by `filter`. The parameter
3091
+ * type EXACTLY MIRRORS `@lunora/notify`'s `SubscriptionFilter` (the runtime
3092
+ * carries no `@lunora/notify` dependency, so it is re-declared structurally):
3093
+ * an identical shape keeps a concrete `SubscriptionStore` assignable here under
3094
+ * `strictFunctionTypes` (contravariant parameter), while letting the RPC push
3095
+ * `{ kind, userId, limit }` DOWN to the store so a large device table is
3096
+ * filtered + bounded server-side (indexed in the D1 store) instead of
3097
+ * list-all-then-filter-in-memory. `filter` is optional, so an existing caller
3098
+ * that lists everything is unaffected.
3099
+ */
3100
+ list: (filter?: NotifySubscriptionFilter) => Promise<ReadonlyArray<NotifySubscriptionDevice & {
3027
3101
  keys?: unknown;
3028
3102
  token?: unknown;
3029
3103
  }>>;
3030
3104
  }
3105
+ /**
3106
+ * Structural mirror of `@lunora/notify`'s `SubscriptionFilter` — kept byte-for-byte
3107
+ * compatible (same optional fields, same `kind` union) so threading it through
3108
+ * {@link NotifySubscriptionStoreLike} does NOT change that cross-package contract's
3109
+ * assignability. See the note on {@link NotifySubscriptionStoreLike.list}.
3110
+ */
3111
+ interface NotifySubscriptionFilter {
3112
+ /** Restrict to a delivery kind. */
3113
+ kind?: "fcm" | "web-push";
3114
+ /** Cap the number of rows returned (a server-side `LIMIT`). */
3115
+ limit?: number;
3116
+ /** Restrict to a single owning user. */
3117
+ userId?: null | string;
3118
+ }
3031
3119
  interface WorkerOptions {
3032
3120
  /**
3033
3121
  * An additional, async authorization gate for the `/_lunora/admin/*` plane
@@ -3914,6 +4002,28 @@ declare class LunoraError extends LunoraError$1 {
3914
4002
  declare const memoizeIdentityPerRequest: (resolver: IdentityResolver) => IdentityResolver;
3915
4003
  /** Tuning for {@link memoizeIdentity}. */
3916
4004
  interface MemoizeIdentityOptions {
4005
+ /**
4006
+ * Override how a request is reduced to its cache key — **required for
4007
+ * correctness** if your resolver authenticates off anything other than the
4008
+ * `Cookie` / `Authorization` headers.
4009
+ *
4010
+ * The default key ({@link credentialKey}) is the raw `Cookie` + `Authorization`
4011
+ * pair. That is a *safe* key only when the resolver reads nothing else: two
4012
+ * requests share a cache entry precisely when they present identical
4013
+ * credentials, so no principal can ever be served another's identity. The
4014
+ * moment the resolver keys off some other attribute — an `X-API-Key` header, a
4015
+ * client-certificate fingerprint, `new URL(request.url).origin`, a
4016
+ * `CF-Access-Jwt-Assertion` — the default is **unsafe**: two distinct
4017
+ * principals that happen to share (or both omit) Cookie/Authorization collide
4018
+ * onto one entry and the cache serves the WRONG identity across the security
4019
+ * boundary.
4020
+ *
4021
+ * Supply `cacheKey` so the key covers *exactly* the attributes the resolver
4022
+ * authenticates on. Return `undefined` for a request that must never be cached
4023
+ * (e.g. one carrying no credential at all) — it falls back to the
4024
+ * per-request memoization, same as the default key's anonymous case.
4025
+ */
4026
+ cacheKey?: (request: Request) => string | undefined;
3917
4027
  /**
3918
4028
  * Cache size before the oldest entry is evicted. Defaults to 500 — an isolate
3919
4029
  * serves a bounded set of concurrent users, and an unbounded map in a
@@ -3943,6 +4053,12 @@ interface MemoizeIdentityOptions {
3943
4053
  * is reused regardless of TTL.
3944
4054
  *
3945
4055
  * An anonymous request (no cookie, no bearer) is never cached.
4056
+ *
4057
+ * **The cross-request cache is keyed on the credential, not the request.** By
4058
+ * default that key is the `Cookie` + `Authorization` pair, which is correct *only*
4059
+ * if the resolver reads nothing else. A resolver that authenticates off any other
4060
+ * attribute MUST supply {@link MemoizeIdentityOptions.cacheKey}, or the cache will
4061
+ * serve one principal's identity to another — see that option's contract.
3946
4062
  */
3947
4063
  declare const memoizeIdentity: (resolver: IdentityResolver, options?: MemoizeIdentityOptions) => IdentityResolver;
3948
4064
  /** Shared shape for sinks that can be limited to error events only. */
package/dist/index.d.ts CHANGED
@@ -168,6 +168,21 @@ interface TraceSamplingConfig {
168
168
  */
169
169
  headRate?: number;
170
170
  }
171
+ /**
172
+ * The resolved per-trace decision. `isTraced` is the cheap head verdict callers
173
+ * check to skip work up front; `keepErrors` carries the tail-bias toggle to the
174
+ * export boundary, where the trace's final error status is known.
175
+ */
176
+ interface TraceSamplingDecision {
177
+ /**
178
+ * True when the trace passed head sampling. Callers may cheaply skip buffering
179
+ * or exporting a trace's spans when this is false — subject to the error-keep
180
+ * re-check at the export boundary (see {@link shouldExportTrace}).
181
+ */
182
+ isTraced: boolean;
183
+ /** Whether error traces are force-kept at export even when `isTraced` is false. */
184
+ keepErrors: boolean;
185
+ }
171
186
  /** A timestamp as better-auth stores it: epoch-ms, an ISO string, or absent. */
172
187
  type AuthTimestamp = null | number | string;
173
188
  /**
@@ -1288,6 +1303,7 @@ interface HealthCheckReport {
1288
1303
  critical: boolean;
1289
1304
  /** Present only in the `admin` posture. */
1290
1305
  message?: string;
1306
+ /** The redacted probe kind (`d1`, `r2`, `d1#2`, …) in the `public` posture; the full `kind:key` name in `admin`. */
1291
1307
  name: string;
1292
1308
  status: "down" | "up";
1293
1309
  }
@@ -1309,7 +1325,12 @@ interface HealthRouteDeps {
1309
1325
  auth?: HealthAuthPosture;
1310
1326
  /**
1311
1327
  * Cache the last computed report for this many ms so an orchestrator polling
1312
- * every few seconds does not hammer the bindings. Defaults to `0` (no cache).
1328
+ * every few seconds does not hammer the bindings (each uncached request runs
1329
+ * a real Durable Object subrequest plus one `SELECT 1` per detected D1
1330
+ * binding, which an unauthenticated flood would otherwise amplify). Cached
1331
+ * independently for the aggregate and readiness probes, since their bodies
1332
+ * differ. When omitted it defaults to `5000` in the `public` posture and `0`
1333
+ * (no cache) in the bearer-gated `admin` posture.
1313
1334
  */
1314
1335
  cacheTtlMs?: number;
1315
1336
  /** Admin-bearer predicate, consulted only when `auth === "admin"`. */
@@ -1630,14 +1651,30 @@ type PipelineLogField = keyof typeof DEFAULT_COLUMNS;
1630
1651
  * Data Catalog tables.
1631
1652
  */
1632
1653
  type PipelineLogColumnMap = Partial<Record<PipelineLogField, string>>;
1633
- /** An opaque keyset cursor: the `ts` of the row after the last one returned. */
1654
+ /**
1655
+ * An opaque keyset cursor. `ts` is the epoch-millis of the **last returned** row
1656
+ * (not an un-returned overflow row), so the next page resumes *inclusively* at
1657
+ * that boundary and never skips a row that shares that millisecond. Because `ts`
1658
+ * is not unique, `seen` carries the identity hashes of the already-returned rows
1659
+ * sitting exactly on that boundary `ts`, so the next page can drop them without
1660
+ * re-emitting them. Both fields are JSON-serialisable; consumers pass the whole
1661
+ * object back unchanged.
1662
+ */
1634
1663
  interface PipelineLogCursor {
1635
- /** Epoch-millis boundary; the next page is every row strictly older than this. */
1664
+ /**
1665
+ * Identity hashes (see the internal `hashRow`) of the rows already returned
1666
+ * that share the boundary `ts`. The next page fetches every row at-or-older-than
1667
+ * the boundary and filters out any whose hash is in this set, so boundary rows
1668
+ * are neither dropped nor duplicated. Only ever holds rows at the single
1669
+ * boundary `ts`; omitted (or empty) when the boundary carries no already-returned ties.
1670
+ */
1671
+ seen?: string[];
1672
+ /** Epoch-millis boundary; the next page is every row at or older than this. */
1636
1673
  ts: number;
1637
1674
  }
1638
1675
  /** Filters for one {@link PipelineLogReader} query. Every value is inlined safely (`lit`/`sql`). */
1639
1676
  interface PipelineLogQuery {
1640
- /** Continue after a previous page (keyset on `ts DESC`). Combined with the other filters. */
1677
+ /** Continue after a previous page (inclusive keyset on `ts DESC`, dedup'd by {@link PipelineLogCursor} `seen`). Combined with the other filters. */
1641
1678
  cursor?: PipelineLogCursor;
1642
1679
  /** Match only this exact function path's records. Prefer `functionPathPrefix` for a namespace sweep. */
1643
1680
  functionPath?: string;
@@ -2105,6 +2142,29 @@ interface ObservabilitySink {
2105
2142
  * Applies only when a sink is configured; with none, `ctx.db` is untouched.
2106
2143
  */
2107
2144
  instrumentDatabase?: "off" | "spans" | "summary";
2145
+ /**
2146
+ * **Opt-in, default `false`.** Durable per-minute rollups of every
2147
+ * `ctx.metrics.*` measurement, kept in a reserved per-shard SQLite table so the
2148
+ * Studio can chart a 24h local trend without an external collector.
2149
+ *
2150
+ * Off by default because it is **not free**: each `ctx.metrics.count/gauge/record`
2151
+ * becomes a durable SQLite write (a billed, rate-limited storage op that competes
2152
+ * with your app's own data for the shard's write budget), on the request path. The
2153
+ * live cross-instance path is `onMetric` → your collector; this is only the local
2154
+ * trend convenience, so enable it deliberately.
2155
+ *
2156
+ * `true` uses the built-in caps (≈24h retention, 1000 distinct series). Pass an
2157
+ * object to tune them: `maxSeries` bounds the distinct series tracked (a
2158
+ * high-cardinality attribute otherwise mints a series per value), `retentionBuckets`
2159
+ * the minute-buckets kept per series before older ones are trimmed.
2160
+ *
2161
+ * Pass this on the SAME sink object you give `createShardDO` — the DO reads it
2162
+ * when recording a measurement.
2163
+ */
2164
+ metricHistory?: boolean | {
2165
+ maxSeries?: number;
2166
+ retentionBuckets?: number;
2167
+ };
2108
2168
  /** Invoked once per `ctx.log.*` call from a function handler. */
2109
2169
  onLog?: (event: LogEvent, context?: ObservabilitySinkContext) => void;
2110
2170
  /**
@@ -2141,13 +2201,22 @@ interface ObservabilitySink {
2141
2201
  * sink-originating throw bubble up past this point. `context.waitUntil`, when
2142
2202
  * supplied, lets a network sink keep its send alive past the response.
2143
2203
  *
2144
- * `sampling` applies the trace-sampling verdict to this dispatch's SERVER span:
2145
- * the event is dropped unless the trace was head-sampled or (with errors
2146
- * force-kept) this dispatch errored the tail bias. A dispatch with no
2147
- * `traceId` (a fan-out aggregation, which mints none) is always kept, and an
2148
- * absent `sampling` keeps everything, so both are backward-compatible.
2204
+ * The verdict applied to this dispatch's SERVER span comes from `decision` when
2205
+ * the caller settled one pass the dispatch's already-settled decision so the
2206
+ * export gate can never disagree with the propagated `traceparent` (a trace kept
2207
+ * or dropped as a whole, per PR #191). Its `isTraced` must carry the propagated
2208
+ * sampled bit (`trace.sampled`), not the raw head verdict, so a trusted upstream
2209
+ * that sampled the trace out keeps its SERVER span out here too. The event is
2210
+ * dropped unless the trace was sampled or (with errors force-kept) this dispatch
2211
+ * errored — the tail bias.
2212
+ *
2213
+ * `sampling` is the legacy fallback for callers with no settled decision: it
2214
+ * re-derives the verdict from `event.traceId`. A dispatch with no `traceId` (a
2215
+ * fan-out aggregation, which mints none) is always kept, and both an absent
2216
+ * `decision` and an absent `sampling` keep everything, so all are
2217
+ * backward-compatible.
2149
2218
  */
2150
- declare const emitRpcEvent: (sink: ObservabilitySink | undefined, event: ObservabilityEvent, context?: ObservabilitySinkContext, sampling?: TraceSamplingConfig) => void;
2219
+ declare const emitRpcEvent: (sink: ObservabilitySink | undefined, event: ObservabilityEvent, context?: ObservabilitySinkContext, sampling?: TraceSamplingConfig, decision?: TraceSamplingDecision) => void;
2151
2220
  /**
2152
2221
  * Invoke `sink.onLog` with the given log event, swallowing any error the sink
2153
2222
  * throws. The same failure model as {@link emitRpcEvent}: a buggy log sink must
@@ -2709,7 +2778,9 @@ type FunctionRegistryLike = Record<string, FunctionRegistryEntry>;
2709
2778
  type X402ChargeGate = (request: Request, spec: {
2710
2779
  functionPath: string;
2711
2780
  price: number | string;
2712
- }, dispatch: () => Promise<Response>) => Promise<Response>;
2781
+ }, dispatch: () => Promise<Response>, deps?: {
2782
+ waitUntil?: (promise: Promise<unknown>) => void;
2783
+ }) => Promise<Response>;
2713
2784
  /**
2714
2785
  * Lists objects in the storage bucket for the admin file browser. Structurally
2715
2786
  * compatible with `@lunora/storage`'s `Storage["list"]` — the runtime stays free
@@ -2971,7 +3042,7 @@ interface HealthOptions {
2971
3042
  * includes the (runtime-authored) messages.
2972
3043
  */
2973
3044
  auth?: "admin" | "public";
2974
- /** Cache the computed report for this many ms so a frequent poller does not re-run every probe. Defaults to `0`. */
3045
+ /** Cache the computed report for this many ms so a frequent poller (or an unauthenticated flood) does not re-run every probe. Defaults to `5000` for the public posture and `0` (no cache) for the bearer-gated admin posture. */
2975
3046
  cacheTtlMs?: number;
2976
3047
  /** Skip the auto-registered D1 / R2 / queue / Hyperdrive binding probes (keep only the DO probe + `probes`). Defaults to `false`. */
2977
3048
  disableBindingProbes?: boolean;
@@ -3016,18 +3087,35 @@ interface NotifySubscriptionDevice {
3016
3087
  */
3017
3088
  interface NotifySubscriptionStoreLike {
3018
3089
  /**
3019
- * List every stored subscription. Declared with NO parameter so a concrete
3020
- * `@lunora/notify` `SubscriptionStore` — whose `list(filter?)` narrows `kind`
3021
- * to the `"web-push" | "fcm"` union assigns cleanly under
3022
- * `strictFunctionTypes` (an extra optional parameter on the source is fine).
3023
- * The RPC handler applies the `{ kind, userId }` filter in-memory, so no typed
3024
- * filter needs to cross this dependency-free structural boundary.
3025
- */
3026
- list: () => Promise<ReadonlyArray<NotifySubscriptionDevice & {
3090
+ * List stored subscriptions, optionally narrowed by `filter`. The parameter
3091
+ * type EXACTLY MIRRORS `@lunora/notify`'s `SubscriptionFilter` (the runtime
3092
+ * carries no `@lunora/notify` dependency, so it is re-declared structurally):
3093
+ * an identical shape keeps a concrete `SubscriptionStore` assignable here under
3094
+ * `strictFunctionTypes` (contravariant parameter), while letting the RPC push
3095
+ * `{ kind, userId, limit }` DOWN to the store so a large device table is
3096
+ * filtered + bounded server-side (indexed in the D1 store) instead of
3097
+ * list-all-then-filter-in-memory. `filter` is optional, so an existing caller
3098
+ * that lists everything is unaffected.
3099
+ */
3100
+ list: (filter?: NotifySubscriptionFilter) => Promise<ReadonlyArray<NotifySubscriptionDevice & {
3027
3101
  keys?: unknown;
3028
3102
  token?: unknown;
3029
3103
  }>>;
3030
3104
  }
3105
+ /**
3106
+ * Structural mirror of `@lunora/notify`'s `SubscriptionFilter` — kept byte-for-byte
3107
+ * compatible (same optional fields, same `kind` union) so threading it through
3108
+ * {@link NotifySubscriptionStoreLike} does NOT change that cross-package contract's
3109
+ * assignability. See the note on {@link NotifySubscriptionStoreLike.list}.
3110
+ */
3111
+ interface NotifySubscriptionFilter {
3112
+ /** Restrict to a delivery kind. */
3113
+ kind?: "fcm" | "web-push";
3114
+ /** Cap the number of rows returned (a server-side `LIMIT`). */
3115
+ limit?: number;
3116
+ /** Restrict to a single owning user. */
3117
+ userId?: null | string;
3118
+ }
3031
3119
  interface WorkerOptions {
3032
3120
  /**
3033
3121
  * An additional, async authorization gate for the `/_lunora/admin/*` plane
@@ -3914,6 +4002,28 @@ declare class LunoraError extends LunoraError$1 {
3914
4002
  declare const memoizeIdentityPerRequest: (resolver: IdentityResolver) => IdentityResolver;
3915
4003
  /** Tuning for {@link memoizeIdentity}. */
3916
4004
  interface MemoizeIdentityOptions {
4005
+ /**
4006
+ * Override how a request is reduced to its cache key — **required for
4007
+ * correctness** if your resolver authenticates off anything other than the
4008
+ * `Cookie` / `Authorization` headers.
4009
+ *
4010
+ * The default key ({@link credentialKey}) is the raw `Cookie` + `Authorization`
4011
+ * pair. That is a *safe* key only when the resolver reads nothing else: two
4012
+ * requests share a cache entry precisely when they present identical
4013
+ * credentials, so no principal can ever be served another's identity. The
4014
+ * moment the resolver keys off some other attribute — an `X-API-Key` header, a
4015
+ * client-certificate fingerprint, `new URL(request.url).origin`, a
4016
+ * `CF-Access-Jwt-Assertion` — the default is **unsafe**: two distinct
4017
+ * principals that happen to share (or both omit) Cookie/Authorization collide
4018
+ * onto one entry and the cache serves the WRONG identity across the security
4019
+ * boundary.
4020
+ *
4021
+ * Supply `cacheKey` so the key covers *exactly* the attributes the resolver
4022
+ * authenticates on. Return `undefined` for a request that must never be cached
4023
+ * (e.g. one carrying no credential at all) — it falls back to the
4024
+ * per-request memoization, same as the default key's anonymous case.
4025
+ */
4026
+ cacheKey?: (request: Request) => string | undefined;
3917
4027
  /**
3918
4028
  * Cache size before the oldest entry is evicted. Defaults to 500 — an isolate
3919
4029
  * serves a bounded set of concurrent users, and an unbounded map in a
@@ -3943,6 +4053,12 @@ interface MemoizeIdentityOptions {
3943
4053
  * is reused regardless of TTL.
3944
4054
  *
3945
4055
  * An anonymous request (no cookie, no bearer) is never cached.
4056
+ *
4057
+ * **The cross-request cache is keyed on the credential, not the request.** By
4058
+ * default that key is the `Cookie` + `Authorization` pair, which is correct *only*
4059
+ * if the resolver reads nothing else. A resolver that authenticates off any other
4060
+ * attribute MUST supply {@link MemoizeIdentityOptions.cacheKey}, or the cache will
4061
+ * serve one principal's identity to another — see that option's contract.
3946
4062
  */
3947
4063
  declare const memoizeIdentity: (resolver: IdentityResolver, options?: MemoizeIdentityOptions) => IdentityResolver;
3948
4064
  /** Shared shape for sinks that can be limited to error events only. */
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{toAirbyteMessages as t,toFivetranResponse as i}from"./packem_shared/toAirbyteMessages-DBTuFjb5.mjs";import{composeWorker as n,createLunoraHandler as s,createWorker as p,defineRpcEnvelope as m,resolveLunoraOptions as c,withFrameworkWorker as R}from"./packem_shared/composeWorker-D7Zi6kZR.mjs";import{createCrossShardRelationCapabilities as E}from"./packem_shared/createCrossShardRelationCapabilities-B2EKbSEs.mjs";import{DEFAULT_REGISTRY_CACHE_TTL_MS as x,SHARD_REGISTRY_DO_NAME as d,createDynamicShardRegistry as l}from"./packem_shared/DEFAULT_REGISTRY_CACHE_TTL_MS-BBf3mNrD.mjs";import{LunoraError as L,toErrorResponse as g}from"./packem_shared/LunoraError-C08OP5Uq.mjs";import{createKvCursorStore as y,createMemoryCursorStore as T,defineExportSink as A,r2Sink as k,runExportTap as C,sanitizeChange as O,webhookExportSink as h}from"./packem_shared/createKvCursorStore-C24tEuYk.mjs";import{HEALTH_PATH as H,HEALTH_READY_PATH as I,buildHealthRoutes as b,d1Probe as F,durableObjectProbe as P,presenceProbe as D}from"./packem_shared/HEALTH_PATH-DiZqjEGp.mjs";import{LOG_ARCHIVE_PATH as G,resolveLogArchiveFromEnv as M}from"./packem_shared/LOG_ARCHIVE_PATH-e8U3ftfM.mjs";import{memoizeIdentity as w,memoizeIdentityPerRequest as z}from"./packem_shared/memoizeIdentity-DRYIK1yT.mjs";import{e as W,a as Y}from"./packem_shared/observability-B0MYwNDB.mjs";import{analyticsEngineSink as Q,combineSinks as X,consoleSink as j,otlpSink as q,pipelineLogSink as J,sentrySink as B,webhookSink as Z}from"./packem_shared/analyticsEngineSink-DOoyBx4o.mjs";import{D as ee,a as re,c as oe}from"./packem_shared/pipeline-log-reader-tDuvc_Ty.mjs";import{createQueryCoordinator as ie,createStaticShardRegistry as ae,mergeStrategyForAggregate as ne}from"./packem_shared/createQueryCoordinator-DLsrcOkB.mjs";import{applyJurisdiction as pe,resolveShard as me}from"./packem_shared/applyJurisdiction-uRQLx282.mjs";import{argsFromQuery as Re,buildRestRoutes as Se,createRestRateLimit as Ee,readShardKey as fe,restSurfaceFromRegistry as xe}from"./packem_shared/argsFromQuery-0KrWTkNx.mjs";import{decorateResponse as le,enforceOrigin as _e,handleCorsPreflight as Le,resolveSecurity as ge}from"./packem_shared/decorateResponse-C6TZSzID.mjs";import{createShardClient as ye}from"./packem_shared/createShardClient-62qcYKGl.mjs";import{LOG_ARCHIVE_NOT_CONFIGURED as Ae}from"./packem_shared/LOG_ARCHIVE_NOT_CONFIGURED-CDpi4yFD.mjs";import{NOOP_EXECUTION_CONTEXT as Ce}from"./packem_shared/NOOP_EXECUTION_CONTEXT-YmXqH-jH.mjs";import{composeIdentityResolvers as he,routeIdentityResolvers as ve}from"./packem_shared/composeIdentityResolvers-DlBbYmBJ.mjs";const e="0.0.0";export{ee as DEFAULT_LOG_COLUMNS,re as DEFAULT_LOG_LIMIT,x as DEFAULT_REGISTRY_CACHE_TTL_MS,H as HEALTH_PATH,I as HEALTH_READY_PATH,Ae as LOG_ARCHIVE_NOT_CONFIGURED,G as LOG_ARCHIVE_PATH,L as LunoraError,Ce as NOOP_EXECUTION_CONTEXT,d as SHARD_REGISTRY_DO_NAME,e as VERSION,Q as analyticsEngineSink,pe as applyJurisdiction,Re as argsFromQuery,b as buildHealthRoutes,Se as buildRestRoutes,X as combineSinks,he as composeIdentityResolvers,n as composeWorker,j as consoleSink,E as createCrossShardRelationCapabilities,l as createDynamicShardRegistry,y as createKvCursorStore,s as createLunoraHandler,T as createMemoryCursorStore,oe as createPipelineLogReader,ie as createQueryCoordinator,Ee as createRestRateLimit,ye as createShardClient,ae as createStaticShardRegistry,p as createWorker,F as d1Probe,le as decorateResponse,A as defineExportSink,m as defineRpcEnvelope,P as durableObjectProbe,W as emitLogEvent,Y as emitRpcEvent,_e as enforceOrigin,Le as handleCorsPreflight,w as memoizeIdentity,z as memoizeIdentityPerRequest,ne as mergeStrategyForAggregate,q as otlpSink,J as pipelineLogSink,D as presenceProbe,k as r2Sink,fe as readShardKey,M as resolveLogArchiveFromEnv,c as resolveLunoraOptions,ge as resolveSecurity,me as resolveShard,xe as restSurfaceFromRegistry,ve as routeIdentityResolvers,C as runExportTap,O as sanitizeChange,B as sentrySink,t as toAirbyteMessages,g as toErrorResponse,i as toFivetranResponse,h as webhookExportSink,Z as webhookSink,R as withFrameworkWorker};
1
+ import{toAirbyteMessages as t,toFivetranResponse as i}from"./packem_shared/toAirbyteMessages-DBTuFjb5.mjs";import{composeWorker as n,createLunoraHandler as s,createWorker as p,defineRpcEnvelope as m,resolveLunoraOptions as c,withFrameworkWorker as R}from"./packem_shared/composeWorker-IO6LYYK2.mjs";import{createCrossShardRelationCapabilities as E}from"./packem_shared/createCrossShardRelationCapabilities-CMWiFA5s.mjs";import{DEFAULT_REGISTRY_CACHE_TTL_MS as x,SHARD_REGISTRY_DO_NAME as d,createDynamicShardRegistry as l}from"./packem_shared/DEFAULT_REGISTRY_CACHE_TTL_MS-CsNnfJzB.mjs";import{LunoraError as L,toErrorResponse as g}from"./packem_shared/LunoraError-ByasbDmd.mjs";import{createKvCursorStore as y,createMemoryCursorStore as T,defineExportSink as A,r2Sink as k,runExportTap as C,sanitizeChange as O,webhookExportSink as h}from"./packem_shared/createKvCursorStore-C24tEuYk.mjs";import{HEALTH_PATH as H,HEALTH_READY_PATH as I,buildHealthRoutes as b,d1Probe as F,durableObjectProbe as P,presenceProbe as D}from"./packem_shared/HEALTH_PATH-BEE0A_lZ.mjs";import{LOG_ARCHIVE_PATH as G,resolveLogArchiveFromEnv as M}from"./packem_shared/LOG_ARCHIVE_PATH-DGsEec3K.mjs";import{memoizeIdentity as w,memoizeIdentityPerRequest as z}from"./packem_shared/memoizeIdentity-CZGtDc4H.mjs";import{e as W,a as Y}from"./packem_shared/observability-DYnm-d4g.mjs";import{analyticsEngineSink as Q,combineSinks as X,consoleSink as j,otlpSink as q,pipelineLogSink as J,sentrySink as B,webhookSink as Z}from"./packem_shared/analyticsEngineSink-CUWYuLHs.mjs";import{D as ee,a as re,c as oe}from"./packem_shared/pipeline-log-reader-DU_CCGDA.mjs";import{createQueryCoordinator as ie,createStaticShardRegistry as ae,mergeStrategyForAggregate as ne}from"./packem_shared/createQueryCoordinator-DqrEvhPD.mjs";import{applyJurisdiction as pe,resolveShard as me}from"./packem_shared/applyJurisdiction-uRQLx282.mjs";import{argsFromQuery as Re,buildRestRoutes as Se,createRestRateLimit as Ee,readShardKey as fe,restSurfaceFromRegistry as xe}from"./packem_shared/argsFromQuery-0KrWTkNx.mjs";import{decorateResponse as le,enforceOrigin as _e,handleCorsPreflight as Le,resolveSecurity as ge}from"./packem_shared/decorateResponse-DBIWsRSZ.mjs";import{createShardClient as ye}from"./packem_shared/createShardClient-62qcYKGl.mjs";import{LOG_ARCHIVE_NOT_CONFIGURED as Ae}from"./packem_shared/LOG_ARCHIVE_NOT_CONFIGURED-CDpi4yFD.mjs";import{NOOP_EXECUTION_CONTEXT as Ce}from"./packem_shared/NOOP_EXECUTION_CONTEXT-YmXqH-jH.mjs";import{composeIdentityResolvers as he,routeIdentityResolvers as ve}from"./packem_shared/composeIdentityResolvers-DwE0Jbww.mjs";const e="0.0.0";export{ee as DEFAULT_LOG_COLUMNS,re as DEFAULT_LOG_LIMIT,x as DEFAULT_REGISTRY_CACHE_TTL_MS,H as HEALTH_PATH,I as HEALTH_READY_PATH,Ae as LOG_ARCHIVE_NOT_CONFIGURED,G as LOG_ARCHIVE_PATH,L as LunoraError,Ce as NOOP_EXECUTION_CONTEXT,d as SHARD_REGISTRY_DO_NAME,e as VERSION,Q as analyticsEngineSink,pe as applyJurisdiction,Re as argsFromQuery,b as buildHealthRoutes,Se as buildRestRoutes,X as combineSinks,he as composeIdentityResolvers,n as composeWorker,j as consoleSink,E as createCrossShardRelationCapabilities,l as createDynamicShardRegistry,y as createKvCursorStore,s as createLunoraHandler,T as createMemoryCursorStore,oe as createPipelineLogReader,ie as createQueryCoordinator,Ee as createRestRateLimit,ye as createShardClient,ae as createStaticShardRegistry,p as createWorker,F as d1Probe,le as decorateResponse,A as defineExportSink,m as defineRpcEnvelope,P as durableObjectProbe,W as emitLogEvent,Y as emitRpcEvent,_e as enforceOrigin,Le as handleCorsPreflight,w as memoizeIdentity,z as memoizeIdentityPerRequest,ne as mergeStrategyForAggregate,q as otlpSink,J as pipelineLogSink,D as presenceProbe,k as r2Sink,fe as readShardKey,M as resolveLogArchiveFromEnv,c as resolveLunoraOptions,ge as resolveSecurity,me as resolveShard,xe as restSurfaceFromRegistry,ve as routeIdentityResolvers,C as runExportTap,O as sanitizeChange,B as sentrySink,t as toAirbyteMessages,g as toErrorResponse,i as toFivetranResponse,h as webhookExportSink,Z as webhookSink,R as withFrameworkWorker};
@@ -1 +1 @@
1
- import"@lunora/bindings/r2sql";import{D as r,a as o,c as _}from"./pipeline-log-reader-tDuvc_Ty.mjs";export{r as DEFAULT_LOG_COLUMNS,o as DEFAULT_LOG_LIMIT,_ as createPipelineLogReader};
1
+ import"@lunora/bindings/r2sql";import{D as r,a as o,c as _}from"./pipeline-log-reader-DU_CCGDA.mjs";export{r as DEFAULT_LOG_COLUMNS,o as DEFAULT_LOG_LIMIT,_ as createPipelineLogReader};
@@ -1 +1 @@
1
- import{LunoraError as n}from"./LunoraError-C08OP5Uq.mjs";import{applyJurisdiction as m}from"./applyJurisdiction-uRQLx282.mjs";const S="__lunora_shard_registry__",_=3e4,l="https://shard-registry.internal",w=async s=>await s.json(),$=s=>{const p=s.instanceName??S,o=s.cacheTtlMs??_,a=new Map,c=m(s.namespace,s.jurisdiction);let d;const h=()=>(d??=c.get(c.idFromName(p)),d),y=async(t,e)=>h().fetch(new Request(`${l}${t}`,{body:JSON.stringify(e),headers:{"content-type":"application/json"},method:"POST"})),u=async t=>h().fetch(new Request(`${l}${t}`,{method:"GET"}));return{invalidate(t){t===void 0?a.clear():a.delete(t)},async listShardKeys(t){const e=Date.now(),r=a.get(t);if(r&&r.expiresAt>e)return r.shardKeys;const i=await u(`/list?table=${encodeURIComponent(t)}`);if(!i.ok)throw new n(`shard registry /list returned ${String(i.status)}`);const{shardKeys:g}=await w(i);return o>0&&a.set(t,{expiresAt:e+o,shardKeys:g}),g},async register(t,e){const r=await y("/register",{shardKey:e,table:t});if(!r.ok)throw new n(`shard registry /register returned ${String(r.status)}`);a.delete(t)},async snapshot(){const t=await u("/snapshot");if(!t.ok)throw new n(`shard registry /snapshot returned ${String(t.status)}`);const{tables:e}=await w(t);return e},async unregister(t,e){const r=await y("/unregister",{shardKey:e,table:t});if(!r.ok)throw new n(`shard registry /unregister returned ${String(r.status)}`);a.delete(t)}}};export{_ as DEFAULT_REGISTRY_CACHE_TTL_MS,S as SHARD_REGISTRY_DO_NAME,$ as createDynamicShardRegistry};
1
+ import{LunoraError as n}from"./LunoraError-ByasbDmd.mjs";import{applyJurisdiction as m}from"./applyJurisdiction-uRQLx282.mjs";const S="__lunora_shard_registry__",_=3e4,l="https://shard-registry.internal",w=async s=>await s.json(),$=s=>{const p=s.instanceName??S,o=s.cacheTtlMs??_,a=new Map,c=m(s.namespace,s.jurisdiction);let d;const h=()=>(d??=c.get(c.idFromName(p)),d),y=async(t,e)=>h().fetch(new Request(`${l}${t}`,{body:JSON.stringify(e),headers:{"content-type":"application/json"},method:"POST"})),u=async t=>h().fetch(new Request(`${l}${t}`,{method:"GET"}));return{invalidate(t){t===void 0?a.clear():a.delete(t)},async listShardKeys(t){const e=Date.now(),r=a.get(t);if(r&&r.expiresAt>e)return r.shardKeys;const i=await u(`/list?table=${encodeURIComponent(t)}`);if(!i.ok)throw new n(`shard registry /list returned ${String(i.status)}`);const{shardKeys:g}=await w(i);return o>0&&a.set(t,{expiresAt:e+o,shardKeys:g}),g},async register(t,e){const r=await y("/register",{shardKey:e,table:t});if(!r.ok)throw new n(`shard registry /register returned ${String(r.status)}`);a.delete(t)},async snapshot(){const t=await u("/snapshot");if(!t.ok)throw new n(`shard registry /snapshot returned ${String(t.status)}`);const{tables:e}=await w(t);return e},async unregister(t,e){const r=await y("/unregister",{shardKey:e,table:t});if(!r.ok)throw new n(`shard registry /unregister returned ${String(r.status)}`);a.delete(t)}}};export{_ as DEFAULT_REGISTRY_CACHE_TTL_MS,S as SHARD_REGISTRY_DO_NAME,$ as createDynamicShardRegistry};
@@ -0,0 +1 @@
1
+ import{LunoraError as k}from"./LunoraError-ByasbDmd.mjs";import{d as H}from"./method-guard-BbuR0VfS.mjs";const R="/_lunora/health",T="/_lunora/health/ready",N=a=>a==="liveness"?["liveness"]:a==="readiness"?["readiness"]:["liveness","readiness"];class P{#e=new Map;addChecker(e,s,t){this.#e.set(e,{run:s,types:t.type})}async getReport(e){const s=[...this.#e].filter(([,r])=>e===void 0||r.types.includes(e)),t=await Promise.all(s.map(async([r,o])=>[r,await o.run()]));return{healthy:t.every(([,r])=>r.health.healthy),report:Object.fromEntries(t)}}}const _=a=>{const e=new P,s=new Set;for(const t of a)t.critical&&s.add(t.name),e.addChecker(t.name,async()=>{let r;try{r=await t.check()}catch(o){r={healthy:!1,message:o instanceof Error?o.message:"probe failed"}}return{health:{healthy:r.healthy,...r.message===void 0?{}:{message:r.message}}}},{type:N(t.kind)});return{criticalNames:s,registry:e}},j=(a,e)=>{const s=a.includes(":")?a.slice(0,a.indexOf(":")):"probe",t=(e.get(s)??0)+1;return e.set(s,t),t===1?s:`${s}#${String(t)}`},C=(a,e)=>a?"unhealthy":e?"degraded":"healthy",O=(a,e,s,t,r)=>{const o=[];let d=!1,u=!1;const m=new Map;for(const[h,c]of Object.entries(a)){const n=e.has(h),i=c.health.healthy;u=u||!i,d=d||!i&&n;const l=s==="admin"?h:j(h,m);o.push({critical:n,...s==="admin"&&c.health.message!==void 0?{message:c.health.message}:{},name:l,status:i?"up":"down"})}return o.sort((h,c)=>h.name.localeCompare(c.name)),{anyCriticalDown:d,body:{appName:t,appVersion:r,checks:o,status:C(d,u),timestamp:new Date().toISOString()}}},M=a=>{const{appName:e="lunora",appVersion:s="0.0.0",auth:t="public",cacheTtlMs:r,isAdmin:o,resolveProbes:d}=a,u=n=>r!==void 0?r:n==="readiness"?0:t==="public"?5e3:0,m={},h=n=>{if(t==="admin"&&!o(n))throw new k("health endpoint requires a valid admin bearer",{code:"ADMIN_FORBIDDEN",status:403})},c=async(n,i,l)=>{const f=H(n,["GET","HEAD"]);if(f)return f;h(n);const y=u(l);if(y>0){const p=m[l];if(p!==void 0&&Date.now()<p.expiresAt)return Response.json(p.body,{headers:{"cache-control":"no-store"},status:p.down?503:200})}const{criticalNames:w,registry:E}=_(d(i)),{healthy:v,report:A}=await E.getReport(l==="readiness"?"readiness":void 0),{anyCriticalDown:D,body:g}=O(A,w,t,e,s),b=l==="readiness"?!v:D;return y>0&&(m[l]={body:g,down:b,expiresAt:Date.now()+y}),Response.json(g,{headers:{"cache-control":"no-store"},status:b?503:200})};return{[R]:(n,i)=>c(n,i,"aggregate"),[T]:(n,i)=>c(n,i,"readiness")}},q=(a,e,s)=>({check:async()=>{try{return await e.get(e.idFromName(s)).fetch(new Request("https://shard.internal/_lunora/status",{method:"GET"})),{healthy:!0}}catch{return{healthy:!1,message:"durable object unreachable"}}},critical:!0,name:a}),L=(a,e)=>({check:async()=>{try{return await e.prepare("SELECT 1").first(),{healthy:!0}}catch{return{healthy:!1,message:"d1 query failed"}}},critical:!0,name:a}),$=(a,e)=>({check:()=>e?{healthy:!0}:{healthy:!1,message:"binding not configured"},critical:!1,name:a});export{R as HEALTH_PATH,T as HEALTH_READY_PATH,M as buildHealthRoutes,L as d1Probe,q as durableObjectProbe,$ as presenceProbe};
@@ -1 +1 @@
1
- import{createR2Sql as h}from"@lunora/bindings/r2sql";import{LOG_ARCHIVE_NOT_CONFIGURED as p}from"./LOG_ARCHIVE_NOT_CONFIGURED-CDpi4yFD.mjs";import{L as m,c as f}from"./pipeline-log-reader-tDuvc_Ty.mjs";import{LunoraError as _}from"./LunoraError-C08OP5Uq.mjs";const E="/_lunora/admin/logs/archive",A="LUNORA_LOG_ARCHIVE_TABLE",O="LUNORA_LOG_ARCHIVE_NAMESPACE",D=e=>{if(typeof e!="object"||e===null)return;const t=e,o=t[A];if(typeof o!="string"||o==="")return;const i=t[O];return{table:o,...typeof i=="string"&&i!==""?{namespace:i}:{}}},g=new Set(m),b=e=>typeof e=="string"&&e!==""?e:void 0,v=(e,t)=>{if(e!==void 0){if(typeof e!="string"||!g.has(e))throw new _(`logs archive: invalid \`${t}\` — expected one of ${m.join(", ")}`,{code:"BAD_REQUEST",status:400});return e}},d=(e,t)=>{if(e!==void 0){if(typeof e!="number"||!Number.isFinite(e))throw new _(`logs archive: invalid \`${t}\` — expected a finite number`,{code:"BAD_REQUEST",status:400});return e}},C=e=>{const t={},o=r=>{const l=b(e[r]);l!==void 0&&(t[r]=l)};o("functionPath"),o("functionPathPrefix"),o("traceId"),o("shardKey"),o("userId");const i=v(e.level,"level");i!==void 0&&(t.level=i);const n=v(e.minLevel,"minLevel");n!==void 0&&(t.minLevel=n);const s=d(e.sinceTs,"sinceTs");s!==void 0&&(t.sinceTs=s);const u=d(e.untilTs,"untilTs");u!==void 0&&(t.untilTs=u);const a=d(e.limit,"limit");a!==void 0&&(t.limit=a);const c=e.cursor;if(typeof c=="object"&&c!==null){const r=d(c.ts,"cursor.ts");r!==void 0&&(t.cursor={ts:r})}return t},S=e=>{const t=e.R2_SQL_ACCOUNT_ID??e.CLOUDFLARE_ACCOUNT_ID,o=e.R2_SQL_TOKEN,i=e.R2_SQL_BUCKET,n=[];if((t===void 0||t==="")&&n.push("R2_SQL_ACCOUNT_ID"),(o===void 0||o==="")&&n.push("R2_SQL_TOKEN"),(i===void 0||i==="")&&n.push("R2_SQL_BUCKET"),n.length>0)throw new _(`log archive not configured (missing ${n.join(", ")}). The Pipeline must write to an R2 Data Catalog (Iceberg) table, and R2_SQL_ACCOUNT_ID / R2_SQL_TOKEN / R2_SQL_BUCKET must be set — see the observability docs.`,{code:p,status:400});return{accountId:t,apiToken:o,bucket:i}},U=e=>{const{createReader:t=f,readJsonBody:o,requireAdminOption:i}=e,n=async(s,u)=>{if(s.method!=="POST")throw new _("Log-archive endpoint requires POST",{code:"METHOD_NOT_ALLOWED",status:405});const a=i(s,e.logArchive,{code:p,message:"log archive requires a `logArchive` config (an R2 Data Catalog table) on the worker — see the observability docs"}),{accountId:c,apiToken:r,bucket:l}=S(u??{}),L=C(await o(s)),T=h({accountId:c,apiToken:r,bucket:l}),R=await t(T,{columnMap:a.columnMap,namespace:a.namespace,table:a.table}).query(L);return Response.json(R,{headers:{"content-type":"application/json"},status:200})};return{[E]:n}};export{p as LOG_ARCHIVE_NOT_CONFIGURED,E as LOG_ARCHIVE_PATH,U as buildLogArchiveAdminRoutes,D as resolveLogArchiveFromEnv};
1
+ import{createR2Sql as h}from"@lunora/bindings/r2sql";import{LOG_ARCHIVE_NOT_CONFIGURED as p}from"./LOG_ARCHIVE_NOT_CONFIGURED-CDpi4yFD.mjs";import{L as m,c as f}from"./pipeline-log-reader-DU_CCGDA.mjs";import{LunoraError as _}from"./LunoraError-ByasbDmd.mjs";const E="/_lunora/admin/logs/archive",A="LUNORA_LOG_ARCHIVE_TABLE",O="LUNORA_LOG_ARCHIVE_NAMESPACE",D=e=>{if(typeof e!="object"||e===null)return;const t=e,o=t[A];if(typeof o!="string"||o==="")return;const i=t[O];return{table:o,...typeof i=="string"&&i!==""?{namespace:i}:{}}},g=new Set(m),b=e=>typeof e=="string"&&e!==""?e:void 0,v=(e,t)=>{if(e!==void 0){if(typeof e!="string"||!g.has(e))throw new _(`logs archive: invalid \`${t}\` — expected one of ${m.join(", ")}`,{code:"BAD_REQUEST",status:400});return e}},d=(e,t)=>{if(e!==void 0){if(typeof e!="number"||!Number.isFinite(e))throw new _(`logs archive: invalid \`${t}\` — expected a finite number`,{code:"BAD_REQUEST",status:400});return e}},C=e=>{const t={},o=r=>{const l=b(e[r]);l!==void 0&&(t[r]=l)};o("functionPath"),o("functionPathPrefix"),o("traceId"),o("shardKey"),o("userId");const i=v(e.level,"level");i!==void 0&&(t.level=i);const n=v(e.minLevel,"minLevel");n!==void 0&&(t.minLevel=n);const s=d(e.sinceTs,"sinceTs");s!==void 0&&(t.sinceTs=s);const u=d(e.untilTs,"untilTs");u!==void 0&&(t.untilTs=u);const a=d(e.limit,"limit");a!==void 0&&(t.limit=a);const c=e.cursor;if(typeof c=="object"&&c!==null){const r=d(c.ts,"cursor.ts");r!==void 0&&(t.cursor={ts:r})}return t},S=e=>{const t=e.R2_SQL_ACCOUNT_ID??e.CLOUDFLARE_ACCOUNT_ID,o=e.R2_SQL_TOKEN,i=e.R2_SQL_BUCKET,n=[];if((t===void 0||t==="")&&n.push("R2_SQL_ACCOUNT_ID"),(o===void 0||o==="")&&n.push("R2_SQL_TOKEN"),(i===void 0||i==="")&&n.push("R2_SQL_BUCKET"),n.length>0)throw new _(`log archive not configured (missing ${n.join(", ")}). The Pipeline must write to an R2 Data Catalog (Iceberg) table, and R2_SQL_ACCOUNT_ID / R2_SQL_TOKEN / R2_SQL_BUCKET must be set — see the observability docs.`,{code:p,status:400});return{accountId:t,apiToken:o,bucket:i}},U=e=>{const{createReader:t=f,readJsonBody:o,requireAdminOption:i}=e,n=async(s,u)=>{if(s.method!=="POST")throw new _("Log-archive endpoint requires POST",{code:"METHOD_NOT_ALLOWED",status:405});const a=i(s,e.logArchive,{code:p,message:"log archive requires a `logArchive` config (an R2 Data Catalog table) on the worker — see the observability docs"}),{accountId:c,apiToken:r,bucket:l}=S(u??{}),L=C(await o(s)),T=h({accountId:c,apiToken:r,bucket:l}),R=await t(T,{columnMap:a.columnMap,namespace:a.namespace,table:a.table}).query(L);return Response.json(R,{headers:{"content-type":"application/json"},status:200})};return{[E]:n}};export{p as LOG_ARCHIVE_NOT_CONFIGURED,E as LOG_ARCHIVE_PATH,U as buildLogArchiveAdminRoutes,D as resolveLogArchiveFromEnv};
@@ -1 +1 @@
1
- import{toErrorBody as t,LunoraError as n}from"@lunora/errors";const a=e=>{const{body:o,redacted:r,status:s}=t(e,{fallbackCode:"INTERNAL",redactedMessage:"Internal error"});return r&&console.error("[lunora] internal error:",e),Response.json({error:o},{headers:{"content-type":"application/json"},status:s})};class u extends n{constructor(o,r){super(r?.code??"INTERNAL",o,{cause:r?.cause,status:r?.status})}toResponse(){return a(this)}}export{u as LunoraError,a as toErrorResponse};
1
+ import{LunoraError as t,toErrorBody as n}from"@lunora/errors";const a=e=>{const{body:o,redacted:r,status:s}=n(e,{fallbackCode:"INTERNAL",redactedMessage:"Internal error"});return r&&console.error("[lunora] internal error:",e),Response.json({error:o},{headers:{"content-type":"application/json"},status:s})};class u extends t{constructor(o,r){super(r?.code??"INTERNAL",o,{cause:r?.cause,status:r?.status})}toResponse(){return a(this)}}export{u as LunoraError,a as toErrorResponse};
@@ -0,0 +1 @@
1
+ import{V as q,f as k,a as V,O as z,l as d,b as te,w as v,y as G,h as j,S as W,L as C,R as re}from"./otlp-resource-B-ByO9qo.mjs";const oe=e=>{if(typeof e=="string")return e;try{return JSON.stringify(e)??String(e)}catch{return String(e)}},L=e=>typeof e=="boolean"||typeof e=="number"||typeof e=="string"?e:oe(e),se=e=>{const r=e.maxItems??512,n=e.maxDelayMs??200;let t=[],o,s,l;const h=()=>{o!==void 0&&(clearTimeout(o),o=void 0)},f=async()=>{h();const c=t;t=[];const u=l;s=void 0,l=void 0;try{c.length>0&&await e.export(c)}catch{}finally{u?.()}},m=c=>{s===void 0&&(s=new Promise(u=>{l=u}),o=setTimeout(()=>{f()},n)),c?.(s)};return{add:(c,u)=>{for(t.push(c);t.length>r;)t.shift();m(u),t.length>=r&&f()},flush:async c=>{if(t.length===0){h();return}const u=f();return c?.(u),u},get size(){return t.length}}},ae=(e,r)=>e<r?-1:e>r?1:0,R=e=>{if(e===void 0)return"null";if(typeof e=="bigint")throw new TypeError("stableStringify: cannot use a bigint in a stable JSON cache key — pass it as a string, or use stableWireKey");if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(s=>R(s)).join(",")}]`;const r=Object.getPrototypeOf(e);if(r!==null&&r!==Object.prototype){const s=e.constructor?.name??"value";throw new TypeError(`stableStringify: cannot use a ${s} in a stable JSON cache key — only plain objects, arrays, and JSON primitives are supported (wire-typed values key via stableWireKey)`)}const n=e,t=Object.keys(n).toSorted(ae),o=[];for(const s of t){const l=n[s];l!==void 0&&o.push(`${JSON.stringify(s)}:${R(l)}`)}return`{${o.join(",")}}`},H=(e,r)=>{const n=[d(v.functionPath,e.functionPath),d(v.ok,e.ok)];e.method!==void 0&&n.push(d("http.request.method",e.method)),e.path!==void 0&&n.push(d("url.path",e.path)),n.push(d("http.route",e.functionPath)),e.scheme!==void 0&&n.push(d("url.scheme",e.scheme)),e.host!==void 0&&n.push(d("server.address",e.host)),e.port!==void 0&&n.push(d("server.port",e.port)),e.userAgent!==void 0&&n.push(d("user_agent.original",e.userAgent)),e.shardKey!==void 0&&n.push(d(v.shardKey,e.shardKey)),n.push(d("http.response.status_code",e.error?.status??200)),e.error&&n.push(d(v.errorType,e.error.code),d("lunora.error_status",e.error.status)),e.fanOut&&n.push(d("lunora.fanout.table",e.fanOut.table),d("lunora.fanout.shards",e.fanOut.shards),d("lunora.fanout.failed",e.fanOut.failed));const t={attributes:n,endTimeUnixNano:k(r),kind:q.server,name:e.functionPath,...e.parentSpanId===void 0?{}:{parentSpanId:e.parentSpanId},spanId:e.spanId??z(8),startTimeUnixNano:k(r-e.durationMs),status:e.ok?{code:1}:{code:2,message:e.error?.message??""},traceId:e.traceId??z(16)};return e.traceFlags!==void 0&&(t.flags=e.traceFlags),e.error&&(t.events=[{attributes:[d("exception.type",e.error.code),d("exception.message",e.error.message)],name:"exception",timeUnixNano:k(r)}]),t},E=(e,r)=>{const n=new Map([[v.functionPath,d(v.functionPath,e.functionPath)]]);e.shardKey!==void 0&&n.set(v.shardKey,d(v.shardKey,e.shardKey)),e.userId!==void 0&&n.set(v.userId,d(v.userId,e.userId)),e.errorType!==void 0&&n.set(v.errorType,d(v.errorType,e.errorType));for(const[t,o]of Object.entries(r??{}))n.set(t,d(t,L(o)));return[...n.values()]},Q=e=>{const r={attributes:E({errorType:e.error?.type,functionPath:e.functionPath,shardKey:e.shardKey,userId:e.userId},e.attributes),endTimeUnixNano:k(e.startTs+e.durationMs),kind:q[e.kind??"internal"],name:e.name,parentSpanId:e.parentSpanId,spanId:e.spanId,startTimeUnixNano:k(e.startTs),status:e.ok?{code:1}:{code:2,message:e.error?.message??""},traceId:e.traceId};return e.events!==void 0&&e.events.length>0&&(r.events=e.events.map(n=>({attributes:V(Object.fromEntries(Object.entries(n.attributes??{}).map(([t,o])=>[t,L(o)]))),name:n.name,timeUnixNano:k(n.ts)}))),e.links!==void 0&&e.links.length>0&&(r.links=e.links.map(n=>({attributes:V(Object.fromEntries(Object.entries(n.attributes??{}).map(([t,o])=>[t,L(o)]))),spanId:n.spanId,traceId:n.traceId}))),r},Y=e=>{const r=k(e.ts),n=E({functionPath:e.functionPath,shardKey:e.shardKey},e.attributes),t={asDouble:e.value,attributes:n,timeUnixNano:r};return e.kind==="gauge"?{gauge:{dataPoints:[t]},name:e.name}:e.kind==="histogram"?{histogram:{aggregationTemporality:1,dataPoints:[{attributes:n,bucketCounts:["1"],count:"1",explicitBounds:[],max:e.value,min:e.value,sum:e.value,timeUnixNano:r}]},name:e.name}:{name:e.name,sum:{aggregationTemporality:1,dataPoints:[t],isMonotonic:!0}}},Z=e=>{const r={attributes:E({functionPath:e.functionPath,shardKey:e.shardKey,userId:e.userId},e.fields),body:{stringValue:e.message},severityNumber:te[e.level],severityText:e.level.toUpperCase(),timeUnixNano:k(e.ts)};return e.traceId!==void 0&&(r.traceId=e.traceId),e.spanId!==void 0&&(r.spanId=e.spanId),e.eventName!==void 0&&(r.eventName=e.eventName,r.attributes.push(d("event.name",e.eventName))),r},ie=1024,ce=async e=>{const r=new Blob([e]).stream().pipeThrough(new CompressionStream("gzip"));return new Response(r).arrayBuffer()},U=async(e,r,n,t)=>{try{const o=JSON.stringify(r),{byteLength:s}=new TextEncoder().encode(o),l=(s<ie?fetch(e,{body:o,headers:n,method:"POST"}):ce(o).then(h=>fetch(e,{body:h,headers:{...n,"content-encoding":"gzip"},method:"POST"}))).then(()=>{},()=>{});t?.(l),await l}catch{}},$=(e,r,n,t)=>{U(e,r,n,t?.waitUntil).catch(()=>{})},P=(e,r)=>r===!0&&e.ok,ue=e=>e.kind==="metric"?void 0:e.event.traceId,de=e=>{const r=new Map,n=[];for(const t of e){const o=ue(t);if(o===void 0){n.push(t);continue}const s=r.get(o);s===void 0?r.set(o,[t]):s.push(t)}return{byTrace:r,untraced:n}},_=5,le=(e,r,n)=>{if(r===void 0)return e;const{byTrace:t,untraced:o}=de(e),s=[...o];let l=0,h;for(const[f,m]of t){let c;try{c=r({logs:m.filter(u=>u.kind==="log").map(u=>u.event),rpc:m.filter(u=>u.kind==="rpc").map(u=>u.event),spans:m.filter(u=>u.kind==="span").map(u=>u.event),traceId:f})}catch(u){l+=1,l===1&&(h=u),c=!0}c&&s.push(...m)}return l>0&&n(h,l),s},b=(e,r)=>{if(r===void 0)return e;try{return r(e)}catch{return}},pe=(e,r)=>{if(e.kind==="rpc"){const t=b(e.event,r?.rpc);return t===void 0?void 0:{bucket:"spans",encoded:H(t,e.endMs)}}if(e.kind==="span"){const t=b(e.event,r?.span);return t===void 0?void 0:{bucket:"spans",encoded:Q(t)}}if(e.kind==="log"){const t=b(e.event,r?.log);return t===void 0?void 0:{bucket:"logs",encoded:Z(t)}}const n=b(e.event,r?.metric);return n===void 0?void 0:{bucket:"metrics",encoded:Y(n)}},fe=(e={})=>{const{onlyErrors:r}=e;return{onLog:n=>{n.level==="error"||n.level==="fatal"?console.error("[lunora:log]",n.functionPath,n.message):console.log("[lunora:log]",n.functionPath,n.message)},onMetric:n=>{console.log("[lunora:metric]",`${n.name}=${String(n.value)}`,n.kind,n.functionPath)},onRpc:n=>{P(n,r)||(n.ok?console.log("[lunora:rpc]",n):console.error("[lunora:rpc]",n))},onSpan:n=>{const t=n.ok?"ok":`error ${n.error?.type??""}`.trim();console.log("[lunora:span]",n.name,`${String(n.durationMs)}ms`,t,n.functionPath)}}},me=e=>{const{headers:r,onlyErrors:n,transform:t,transformLog:o,url:s}=e,l=G({"content-type":"application/json"},r),h=(f,m)=>{try{const c=fetch(s,{body:JSON.stringify(f),headers:l,method:"POST"}).catch(()=>{});m?.waitUntil&&m.waitUntil(c)}catch{}};return{onLog:(f,m)=>{let c=f;if(o)try{c=o(f)}catch{return}c!=null&&h(c,m)},onRpc:(f,m)=>{if(!P(f,n))try{let c=f;if(t)try{c=t(f)}catch{return}if(c==null)return;h(c,m)}catch{}}}},ve=e=>{const{capture:r,captureLog:n}=e,t=e.onlyErrors??!0;return{onLog:n?o=>{try{n(o)}catch{}}:void 0,onRpc:o=>{if(!P(o,t))try{r(o)}catch{}}}},ye=e=>{const{dataset:r,onlyErrors:n}=e;return{onRpc:t=>{if(!P(t,n))try{r.writeDataPoint({blobs:[t.functionPath,t.ok?"ok":"error",t.shardKey??"",t.error?.code??"",t.fanOut?.table??""],doubles:[t.durationMs,t.ok?0:1,t.fanOut?.shards??0,t.fanOut?.failed??0],indexes:[t.functionPath]})}catch{}}}},ge=e=>{const{pipeline:r,serializeFields:n}=e;return{onLog:(t,o)=>{try{const s={functionPath:t.functionPath,level:t.level,message:t.message,ts:t.ts};t.fields&&(s.fields=n===!0?JSON.stringify(t.fields):t.fields),t.shardKey!==void 0&&(s.shardKey=t.shardKey),t.userId!==void 0&&(s.userId=t.userId),t.traceId!==void 0&&(s.traceId=t.traceId),t.spanId!==void 0&&(s.spanId=t.spanId);const l=r.send([s]).catch(()=>{});o?.waitUntil&&o.waitUntil(l)}catch{}}}},be=e=>{const{batch:r,deploymentEnvironment:n,detectResources:t,endpoint:o,headers:s,onlyErrors:l,postProcessor:h,resourceAttributes:f,serviceNamespace:m,serviceVersion:c,tailSampler:u,token:X}=e,I=e.serviceName??"lunora",J={...c===void 0?{}:{"service.version":c},...m===void 0?{}:{"service.namespace":m},...n===void 0?{}:{"deployment.environment":n},...f},D=new WeakMap,g=i=>{if(t!==!0||i?.resourceAttributes===void 0)return J;const a=D.get(i);if(a!==void 0)return a;const p=re(i.resourceAttributes(),J);return D.set(i,p),p};let O=o;for(;O.endsWith("/");)O=O.slice(0,-1);const M=`${O}/v1/traces`,A=`${O}/v1/logs`,F=`${O}/v1/metrics`,S=G({"content-type":"application/json"},s,X);let K=0;const ee=(i,a)=>{if(K>=_)return;K+=1;const p=K===_?" Further tailSampler failures from this sink are silenced until the isolate restarts.":"";console.error(`[lunora:otlp] tailSampler threw for ${String(a)} trace(s) in this flush window; keeping them (fail-open), so the sampling policy did NOT apply.${p}`,i)},ne=async i=>{const a=le(i,u,ee),p=new Map;for(const y of a){const w=pe(y,h);if(w===void 0)continue;const B=R(y.resource);let N=p.get(B);N===void 0&&(N={logs:[],metrics:[],resource:y.resource,spans:[]},p.set(B,N)),N[w.bucket].push(w.encoded)}const T=[];for(const[,y]of p){const{resource:w}=y;y.spans.length>0&&T.push(U(M,j(y.spans,"@lunora/runtime",I,w),S)),y.logs.length>0&&T.push(U(A,C(y.logs,"@lunora/runtime",I,w),S)),y.metrics.length>0&&T.push(U(F,W(y.metrics,"@lunora/runtime",I,w),S))}await Promise.all(T)};if(r===!1)return{onLog:(i,a)=>{const p=b(i,h?.log);p!==void 0&&$(A,C(Z(p),"@lunora/runtime",I,g(a)),S,a)},onMetric:(i,a)=>{const p=b(i,h?.metric);p!==void 0&&$(F,W(Y(p),"@lunora/runtime",I,g(a)),S,a)},onRpc:(i,a)=>{if(P(i,l))return;const p=b(i,h?.rpc);p!==void 0&&$(M,j(H(p,Date.now()),"@lunora/runtime",I,g(a)),S,a)},onSpan:(i,a)=>{const p=b(i,h?.span);p!==void 0&&$(M,j(Q(p),"@lunora/runtime",I,g(a)),S,a)}};const x=se({export:ne,...r?.maxDelayMs===void 0?{}:{maxDelayMs:r.maxDelayMs},...r?.maxItems===void 0?{}:{maxItems:r.maxItems}});return{flush:i=>{x.flush(i?.waitUntil).catch(()=>{})},onLog:(i,a)=>{x.add({event:i,kind:"log",resource:g(a)},a?.waitUntil)},onMetric:(i,a)=>{x.add({event:i,kind:"metric",resource:g(a)},a?.waitUntil)},onRpc:(i,a)=>{P(i,l)||x.add({endMs:Date.now(),event:i,kind:"rpc",resource:g(a)},a?.waitUntil)},onSpan:(i,a)=>{x.add({event:i,kind:"span",resource:g(a)},a?.waitUntil)}}},ke=(...e)=>{const r=(n,t)=>{for(const o of e){const s=o[n];if(s)try{s.apply(o,t)}catch{}}};return{flush:n=>{r("flush",[n])},onLog:(n,t)=>{r("onLog",[n,t])},onMetric:(n,t)=>{r("onMetric",[n,t])},onRpc:(n,t)=>{r("onRpc",[n,t])},onSpan:(n,t)=>{r("onSpan",[n,t])}}};export{ye as analyticsEngineSink,ke as combineSinks,fe as consoleSink,be as otlpSink,ge as pipelineLogSink,ve as sentrySink,me as webhookSink};
@@ -1 +1 @@
1
- import{LunoraError as c}from"./LunoraError-C08OP5Uq.mjs";const u=(r,n={})=>{const t=n.onError??"fail-closed";return async(o,e)=>{for(const i of r){let s;try{s=await i(o,e)}catch(a){if(t==="skip")continue;throw a}if(s)return s}return null}},d=r=>{const n=Object.keys(r).filter(t=>t!=="*").toSorted((t,o)=>o.length-t.length);return(t,o)=>{const{pathname:e}=new URL(t.url),i=n.find(a=>e===a||e.startsWith(a.endsWith("/")?a:`${a}/`)),s=i===void 0?r["*"]:r[i];return s===void 0?null:s(t,o)}},f=(r,n)=>n===void 0||r===void 0?r:async(t,o)=>{const e=await r(t,o);if(!e)return e;const i=n.validate(e);if(i.ok)return e;if(n.onInvalid==="reject")throw new c(`identity claims failed the declared contract: ${i.error}`,{code:"UNAUTHENTICATED",status:401});return null};export{u as composeIdentityResolvers,d as routeIdentityResolvers,f as wrapResolverWithContract};
1
+ import{LunoraError as c}from"./LunoraError-ByasbDmd.mjs";const u=(r,n={})=>{const t=n.onError??"fail-closed";return async(o,e)=>{for(const i of r){let s;try{s=await i(o,e)}catch(a){if(t==="skip")continue;throw a}if(s)return s}return null}},d=r=>{const n=Object.keys(r).filter(t=>t!=="*").toSorted((t,o)=>o.length-t.length);return(t,o)=>{const{pathname:e}=new URL(t.url),i=n.find(a=>e===a||e.startsWith(a.endsWith("/")?a:`${a}/`)),s=i===void 0?r["*"]:r[i];return s===void 0?null:s(t,o)}},f=(r,n)=>n===void 0||r===void 0?r:async(t,o)=>{const e=await r(t,o);if(!e)return e;const i=n.validate(e);if(i.ok)return e;if(n.onInvalid==="reject")throw new c(`identity claims failed the declared contract: ${i.error}`,{code:"UNAUTHENTICATED",status:401});return null};export{u as composeIdentityResolvers,d as routeIdentityResolvers,f as wrapResolverWithContract};