@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.
- package/dist/index.d.mts +136 -20
- package/dist/index.d.ts +136 -20
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/{DEFAULT_LOG_COLUMNS-CpZ9kd-q.mjs → DEFAULT_LOG_COLUMNS-B0imn1l0.mjs} +1 -1
- package/dist/packem_shared/{DEFAULT_REGISTRY_CACHE_TTL_MS-BBf3mNrD.mjs → DEFAULT_REGISTRY_CACHE_TTL_MS-CsNnfJzB.mjs} +1 -1
- package/dist/packem_shared/HEALTH_PATH-BEE0A_lZ.mjs +1 -0
- package/dist/packem_shared/{LOG_ARCHIVE_PATH-e8U3ftfM.mjs → LOG_ARCHIVE_PATH-DGsEec3K.mjs} +1 -1
- package/dist/packem_shared/{LunoraError-C08OP5Uq.mjs → LunoraError-ByasbDmd.mjs} +1 -1
- package/dist/packem_shared/analyticsEngineSink-CUWYuLHs.mjs +1 -0
- package/dist/packem_shared/{composeIdentityResolvers-DlBbYmBJ.mjs → composeIdentityResolvers-DwE0Jbww.mjs} +1 -1
- package/dist/packem_shared/composeWorker-IO6LYYK2.mjs +6 -0
- package/dist/packem_shared/{createCrossShardRelationCapabilities-B2EKbSEs.mjs → createCrossShardRelationCapabilities-CMWiFA5s.mjs} +1 -1
- package/dist/packem_shared/{createQueryCoordinator-DLsrcOkB.mjs → createQueryCoordinator-DqrEvhPD.mjs} +1 -1
- package/dist/packem_shared/{decorateResponse-C6TZSzID.mjs → decorateResponse-DBIWsRSZ.mjs} +1 -1
- package/dist/packem_shared/emitLogEvent-BcdChmSV.mjs +1 -0
- package/dist/packem_shared/memoizeIdentity-CZGtDc4H.mjs +1 -0
- package/dist/packem_shared/observability-DYnm-d4g.mjs +1 -0
- package/dist/packem_shared/otlp-resource-B-ByO9qo.mjs +1 -0
- package/dist/packem_shared/pipeline-log-reader-DU_CCGDA.mjs +1 -0
- package/package.json +1 -1
- package/dist/packem_shared/GET_AUTH_AUDIT_LOG_OP-DmxHQbZT.mjs +0 -1
- package/dist/packem_shared/HEALTH_PATH-DiZqjEGp.mjs +0 -1
- package/dist/packem_shared/analyticsEngineSink-DOoyBx4o.mjs +0 -1
- package/dist/packem_shared/composeWorker-D7Zi6kZR.mjs +0 -6
- package/dist/packem_shared/emitLogEvent-CEbCh6FQ.mjs +0 -1
- package/dist/packem_shared/memoizeIdentity-DRYIK1yT.mjs +0 -1
- package/dist/packem_shared/observability-B0MYwNDB.mjs +0 -1
- package/dist/packem_shared/otlp-resource-cAjGEywx.mjs +0 -1
- 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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
2145
|
-
* the
|
|
2146
|
-
*
|
|
2147
|
-
*
|
|
2148
|
-
*
|
|
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
|
|
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
|
|
3020
|
-
* `@lunora/notify` `
|
|
3021
|
-
*
|
|
3022
|
-
*
|
|
3023
|
-
*
|
|
3024
|
-
*
|
|
3025
|
-
|
|
3026
|
-
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
2145
|
-
* the
|
|
2146
|
-
*
|
|
2147
|
-
*
|
|
2148
|
-
*
|
|
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
|
|
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
|
|
3020
|
-
* `@lunora/notify` `
|
|
3021
|
-
*
|
|
3022
|
-
*
|
|
3023
|
-
*
|
|
3024
|
-
*
|
|
3025
|
-
|
|
3026
|
-
|
|
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-
|
|
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};
|
package/dist/packem_shared/{DEFAULT_LOG_COLUMNS-CpZ9kd-q.mjs → DEFAULT_LOG_COLUMNS-B0imn1l0.mjs}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"@lunora/bindings/r2sql";import{D as r,a as o,c as _}from"./pipeline-log-reader-
|
|
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-
|
|
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-
|
|
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{
|
|
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-
|
|
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};
|