@lunora/runtime 1.0.0-alpha.34 → 1.0.0-alpha.36

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.
@@ -1,12 +1,16 @@
1
1
  import { isLunoraError, toErrorBody } from '@lunora/errors';
2
2
  import { NOOP_EXECUTION_CONTEXT } from './NOOP_EXECUTION_CONTEXT-CCTu0Bf1.mjs';
3
- import { o as otlpRandomHex, b as buildTraceparent } from './otlp-DOLuy1Aj.mjs';
4
3
  import { LunoraError, toErrorResponse } from './LunoraError-Bpb9EFJ3.mjs';
4
+ import { runExportTap } from './createKvCursorStore-g8aA6B4L.mjs';
5
+ import { m as methodGuard } from './method-guard-Qzw99aCj.mjs';
6
+ import { buildHealthRoutes, durableObjectProbe, d1Probe, presenceProbe } from './HEALTH_PATH-e5J_NHBx.mjs';
5
7
  import { wrapResolverWithContract } from './composeIdentityResolvers-XGjO7V1J.mjs';
6
8
  export { composeIdentityResolvers, routeIdentityResolvers } from './composeIdentityResolvers-XGjO7V1J.mjs';
7
9
  import { buildLogArchiveAdminRoutes } from './LOG_ARCHIVE_PATH-CNs0bznX.mjs';
8
- import { emitRpcEvent } from './emitLogEvent-pEdtqAK8.mjs';
10
+ import { r as resolveTraceSampling, a as emitRpcEvent } from './observability--NOFYBFc.mjs';
11
+ import { o as otlpRandomHex, b as buildTraceparent, p as parseTraceparent, m as mergeResourceAttributes, d as detectCloudflareResource, a as detectServiceResource, r as readerFromRecord } from './otlp-resource-Dow6-F_u.mjs';
9
12
  import { resolveShard, applyJurisdiction } from './applyJurisdiction-BkZtTkct.mjs';
13
+ import { buildRestRoutes } from './argsFromQuery-c-U1WRy-.mjs';
10
14
  import { resolveSecurity, handleCorsPreflight, enforceOrigin, decorateResponse, enforceWebSocketOrigin } from './decorateResponse-DRWQFNhF.mjs';
11
15
 
12
16
  const evictOldestEntry = (map, capacity) => {
@@ -545,6 +549,52 @@ const buildAuthAdminRoutes = (deps) => {
545
549
  return routes;
546
550
  };
547
551
 
552
+ const GET_AUTH_AUDIT_LOG_OP = "__lunora_admin__:getAuthAuditLog";
553
+ const stringArgument = (value) => typeof value === "string" && value !== "" ? value : void 0;
554
+ const numericArgument = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
555
+ const buildGetAuthAuditLog = (deps) => {
556
+ const handle = async (request, args) => {
557
+ deps.assertAdmin(request);
558
+ const reader = deps.getReader();
559
+ if (reader === void 0) {
560
+ throw new LunoraError("the auth/security audit endpoint requires an `authAuditReader` on the worker", {
561
+ code: "AUTH_AUDIT_NOT_CONFIGURED",
562
+ status: 400
563
+ });
564
+ }
565
+ const query = {};
566
+ const actorId = stringArgument(args["actorId"]);
567
+ const event = stringArgument(args["event"]);
568
+ const sinceSeq = numericArgument(args["sinceSeq"]);
569
+ const limit = numericArgument(args["limit"]);
570
+ if (actorId !== void 0) {
571
+ query.actorId = actorId;
572
+ }
573
+ if (event !== void 0) {
574
+ query.event = event;
575
+ }
576
+ if (sinceSeq !== void 0) {
577
+ query.sinceSeq = sinceSeq;
578
+ }
579
+ if (limit !== void 0) {
580
+ query.limit = limit;
581
+ }
582
+ let entries;
583
+ try {
584
+ entries = await reader.read(query);
585
+ } catch (error) {
586
+ if (error instanceof LunoraError) {
587
+ throw error;
588
+ }
589
+ console.error("[lunora] auth audit read failed:", error);
590
+ throw new LunoraError("auth audit read failed", { code: "AUTH_AUDIT_READ_FAILED", status: 500 });
591
+ }
592
+ const result = { entries };
593
+ return Response.json(result, { headers: { "content-type": "application/json" }, status: 200 });
594
+ };
595
+ return handle;
596
+ };
597
+
548
598
  const MAX_BATCH_ENTRIES = 500;
549
599
 
550
600
  const normalizeBatchCall = (raw, index, defaultShard) => {
@@ -711,6 +761,7 @@ const IMPORT_PATH = "/_lunora/admin/import";
711
761
  const SYNC_PATH = "/_lunora/admin/sync";
712
762
  const CONNECTOR_SYNC_PATH = "/_lunora/admin/connector/sync";
713
763
  const APPLY_PATH = "/_lunora/admin/apply";
764
+ const EXPORT_TAP_RUN_PATH = "/_lunora/admin/export-tap/run";
714
765
  const NDJSON_ENCODER$1 = new TextEncoder();
715
766
  const parseExportBody = async (request) => {
716
767
  let body;
@@ -740,18 +791,28 @@ const parseExportBody = async (request) => {
740
791
  return { tables };
741
792
  };
742
793
  const buildDataMovementAdminRoutes = (deps) => {
743
- const { applyGlobals, isAdmin, knownTables, queryCoordinator, resolveForwardContext, shardDO, streamExportRows, streamingImport, syncGlobals } = deps;
794
+ const {
795
+ applyGlobals,
796
+ exportCursorStore,
797
+ exportSinks,
798
+ knownTables,
799
+ queryCoordinator,
800
+ requireAdminOption,
801
+ resolveForwardContext,
802
+ shardDO,
803
+ streamExportRows,
804
+ streamingImport,
805
+ syncGlobals
806
+ } = deps;
744
807
  const handleExport = async (request, env) => {
745
- if (request.method !== "POST") {
746
- throw new LunoraError("Export endpoint requires POST", { code: "METHOD_NOT_ALLOWED", status: 405 });
747
- }
748
- if (!isAdmin(request)) {
749
- throw new LunoraError("admin export endpoint requires a valid admin bearer", { code: "ADMIN_FORBIDDEN", status: 403 });
750
- }
751
- const coordinator = queryCoordinator;
752
- if (!coordinator) {
753
- throw new LunoraError("Export endpoint requires a `queryCoordinator` on the worker", { code: "BAD_REQUEST", status: 400 });
808
+ const wrongMethod = methodGuard(request, ["POST"]);
809
+ if (wrongMethod) {
810
+ return wrongMethod;
754
811
  }
812
+ const coordinator = requireAdminOption(request, queryCoordinator, {
813
+ code: "BAD_REQUEST",
814
+ message: "Export endpoint requires a `queryCoordinator` on the worker"
815
+ });
755
816
  const body = await parseExportBody(request);
756
817
  const { headers: forwardedHeaders } = await resolveForwardContext(request, env);
757
818
  const stream = new ReadableStream({
@@ -771,16 +832,14 @@ const buildDataMovementAdminRoutes = (deps) => {
771
832
  return new Response(stream, { headers: { "content-type": "application/x-ndjson" }, status: 200 });
772
833
  };
773
834
  const handleCdcSync = async (request, env) => {
774
- if (request.method !== "POST") {
775
- throw new LunoraError("Sync endpoint requires POST", { code: "METHOD_NOT_ALLOWED", status: 405 });
776
- }
777
- if (!isAdmin(request)) {
778
- throw new LunoraError("admin sync endpoint requires a valid admin bearer", { code: "ADMIN_FORBIDDEN", status: 403 });
779
- }
780
- const coordinator = queryCoordinator;
781
- if (!coordinator) {
782
- throw new LunoraError("Sync endpoint requires a `queryCoordinator` on the worker", { code: "BAD_REQUEST", status: 400 });
835
+ const wrongMethod = methodGuard(request, ["POST"]);
836
+ if (wrongMethod) {
837
+ return wrongMethod;
783
838
  }
839
+ const coordinator = requireAdminOption(request, queryCoordinator, {
840
+ code: "BAD_REQUEST",
841
+ message: "Sync endpoint requires a `queryCoordinator` on the worker"
842
+ });
784
843
  const raw = await readJsonBodyWithLimit(request);
785
844
  const cursors = typeof raw["cursors"] === "object" && raw["cursors"] !== null ? raw["cursors"] : {};
786
845
  const limit = typeof raw["limit"] === "number" ? raw["limit"] : void 0;
@@ -798,16 +857,14 @@ const buildDataMovementAdminRoutes = (deps) => {
798
857
  return Response.json({ global, shards: shardResult.shards }, { status: 200 });
799
858
  };
800
859
  const handleConnectorSync = async (request, env) => {
801
- if (request.method !== "POST") {
802
- throw new LunoraError("Connector sync endpoint requires POST", { code: "METHOD_NOT_ALLOWED", status: 405 });
803
- }
804
- if (!isAdmin(request)) {
805
- throw new LunoraError("admin connector sync endpoint requires a valid admin bearer", { code: "ADMIN_FORBIDDEN", status: 403 });
806
- }
807
- const coordinator = queryCoordinator;
808
- if (!coordinator) {
809
- throw new LunoraError("Connector sync endpoint requires a `queryCoordinator` on the worker", { code: "BAD_REQUEST", status: 400 });
860
+ const wrongMethod = methodGuard(request, ["POST"]);
861
+ if (wrongMethod) {
862
+ return wrongMethod;
810
863
  }
864
+ const coordinator = requireAdminOption(request, queryCoordinator, {
865
+ code: "BAD_REQUEST",
866
+ message: "Connector sync endpoint requires a `queryCoordinator` on the worker"
867
+ });
811
868
  const raw = await readJsonBodyWithLimit(request);
812
869
  const state = decodeConnectorCursor(raw["cursor"]);
813
870
  const limit = typeof raw["limit"] === "number" && raw["limit"] > 0 ? raw["limit"] : void 0;
@@ -838,16 +895,14 @@ const buildDataMovementAdminRoutes = (deps) => {
838
895
  return Response.json(page, { status: 200 });
839
896
  };
840
897
  const handleApplyCdc = async (request, env) => {
841
- if (request.method !== "POST") {
842
- throw new LunoraError("Apply endpoint requires POST", { code: "METHOD_NOT_ALLOWED", status: 405 });
843
- }
844
- if (!isAdmin(request)) {
845
- throw new LunoraError("admin apply endpoint requires a valid admin bearer", { code: "ADMIN_FORBIDDEN", status: 403 });
846
- }
847
- const coordinator = queryCoordinator;
848
- if (!coordinator) {
849
- throw new LunoraError("Apply endpoint requires a `queryCoordinator` on the worker", { code: "BAD_REQUEST", status: 400 });
898
+ const wrongMethod = methodGuard(request, ["POST"]);
899
+ if (wrongMethod) {
900
+ return wrongMethod;
850
901
  }
902
+ const coordinator = requireAdminOption(request, queryCoordinator, {
903
+ code: "BAD_REQUEST",
904
+ message: "Apply endpoint requires a `queryCoordinator` on the worker"
905
+ });
851
906
  const raw = await readJsonBodyWithLimit(request);
852
907
  const rawBatches = Array.isArray(raw["batches"]) ? raw["batches"] : [];
853
908
  const batches = rawBatches.map((batch) => batch).filter(
@@ -865,15 +920,11 @@ const buildDataMovementAdminRoutes = (deps) => {
865
920
  return Response.json({ applied: shardResult.applied + globalApplied, failed: shardResult.failed, ok: shardResult.ok }, { status: 200 });
866
921
  };
867
922
  const handleImport = async (request, env) => {
868
- if (request.method !== "POST") {
869
- throw new LunoraError("Import endpoint requires POST", { code: "METHOD_NOT_ALLOWED", status: 405 });
870
- }
871
- if (!isAdmin(request)) {
872
- throw new LunoraError("admin import endpoint requires a valid admin bearer", { code: "ADMIN_FORBIDDEN", status: 403 });
873
- }
874
- if (!queryCoordinator) {
875
- throw new LunoraError("Import endpoint requires a `queryCoordinator` on the worker", { code: "BAD_REQUEST", status: 400 });
923
+ const wrongMethod = methodGuard(request, ["POST"]);
924
+ if (wrongMethod) {
925
+ return wrongMethod;
876
926
  }
927
+ requireAdminOption(request, queryCoordinator, { code: "BAD_REQUEST", message: "Import endpoint requires a `queryCoordinator` on the worker" });
877
928
  const { headers: forwardedHeaders } = await resolveForwardContext(request, env);
878
929
  const result = await streamingImport(request, forwardedHeaders);
879
930
  return Response.json(result, {
@@ -881,10 +932,50 @@ const buildDataMovementAdminRoutes = (deps) => {
881
932
  status: 200
882
933
  });
883
934
  };
935
+ const handleExportTapRun = async (request, env) => {
936
+ const wrongMethod = methodGuard(request, ["POST"]);
937
+ if (wrongMethod) {
938
+ return wrongMethod;
939
+ }
940
+ const coordinator = requireAdminOption(request, queryCoordinator, {
941
+ code: "BAD_REQUEST",
942
+ message: "Export-tap endpoint requires a `queryCoordinator` on the worker"
943
+ });
944
+ if (exportSinks === void 0 || Object.keys(exportSinks).length === 0 || exportCursorStore === void 0) {
945
+ throw new LunoraError("Export-tap endpoint requires `exportSinks` + `exportCursorStore` on the worker", {
946
+ code: "EXPORT_TAP_NOT_CONFIGURED",
947
+ status: 400
948
+ });
949
+ }
950
+ const raw = await readJsonBodyWithLimit(request);
951
+ const sinkName = typeof raw["sink"] === "string" ? raw["sink"] : void 0;
952
+ const limit = typeof raw["limit"] === "number" && raw["limit"] > 0 ? raw["limit"] : void 0;
953
+ const requestedTables = Array.isArray(raw["tables"]) ? raw["tables"].filter((table) => typeof table === "string") : void 0;
954
+ if (sinkName === void 0) {
955
+ throw new LunoraError("Export-tap `sink` must name a configured sink", { code: "BAD_REQUEST", status: 400 });
956
+ }
957
+ const sink = exportSinks[sinkName];
958
+ if (sink === void 0) {
959
+ throw new LunoraError(`Export-tap sink "${sinkName}" is not configured`, { code: "NOT_FOUND", status: 404 });
960
+ }
961
+ const { headers: forwardedHeaders } = await resolveForwardContext(request, env);
962
+ const probeTables = requestedTables ?? knownTables();
963
+ const result = await runExportTap({
964
+ coordinator,
965
+ cursorStore: exportCursorStore,
966
+ headers: forwardedHeaders,
967
+ limit,
968
+ shardDO,
969
+ sink,
970
+ tables: probeTables
971
+ });
972
+ return Response.json(result, { headers: { "content-type": "application/json" }, status: 200 });
973
+ };
884
974
  return {
885
975
  [APPLY_PATH]: handleApplyCdc,
886
976
  [CONNECTOR_SYNC_PATH]: handleConnectorSync,
887
977
  [EXPORT_PATH]: handleExport,
978
+ [EXPORT_TAP_RUN_PATH]: handleExportTapRun,
888
979
  [IMPORT_PATH]: handleImport,
889
980
  [SYNC_PATH]: handleCdcSync
890
981
  };
@@ -1662,6 +1753,83 @@ const buildOrchestrationAdminRoutes = (deps) => {
1662
1753
  };
1663
1754
  };
1664
1755
 
1756
+ const SAMPLED_FLAG = 1;
1757
+ const UNSAMPLED_FLAG = 0;
1758
+ const MAX_TRACE_STATE_MEMBERS = 32;
1759
+ const MAX_TRACE_STATE_LENGTH = 512;
1760
+ const TRACE_STATE_MEMBER = /^[a-z][\d_a-z*/-]{0,255}(?:@[a-z][\d_a-z*/-]{0,13})?=[\u0020-\u002B\u002D-\u003C\u003E-\u007E]{1,256}$/;
1761
+ const sanitizeTraceState = (header) => {
1762
+ if (header === null || header === void 0) {
1763
+ return void 0;
1764
+ }
1765
+ const trimmed = header.trim();
1766
+ if (trimmed.length === 0 || trimmed.length > MAX_TRACE_STATE_LENGTH) {
1767
+ return void 0;
1768
+ }
1769
+ const members = trimmed.split(",");
1770
+ if (members.length > MAX_TRACE_STATE_MEMBERS) {
1771
+ return void 0;
1772
+ }
1773
+ for (const member of members) {
1774
+ if (!TRACE_STATE_MEMBER.test(member.trim())) {
1775
+ return void 0;
1776
+ }
1777
+ }
1778
+ return trimmed;
1779
+ };
1780
+ const extractTraceContext = (request) => {
1781
+ const parsed = parseTraceparent(request.headers.get("traceparent"));
1782
+ if (parsed === void 0) {
1783
+ return void 0;
1784
+ }
1785
+ const traceState = sanitizeTraceState(request.headers.get("tracestate"));
1786
+ return {
1787
+ parentSpanId: parsed.parentSpanId,
1788
+ sampled: parsed.sampled,
1789
+ traceId: parsed.traceId,
1790
+ ...traceState === void 0 ? {} : { traceState }
1791
+ };
1792
+ };
1793
+ const beginDispatchTrace = (request, options = {}) => {
1794
+ const upstream = extractTraceContext(request);
1795
+ const trusted = options.trustInbound === true ? upstream : void 0;
1796
+ const spanId = otlpRandomHex(8);
1797
+ const traceId = trusted?.traceId ?? otlpRandomHex(16);
1798
+ const decision = resolveTraceSampling(options.sampling, trusted === void 0 ? spanId : traceId);
1799
+ const sampled = decision.isTraced && (trusted === void 0 || trusted.sampled);
1800
+ return {
1801
+ decision,
1802
+ // A parseable upstream trace existed, and we chose not to join it.
1803
+ ignoredUpstream: upstream !== void 0 && trusted === void 0,
1804
+ trace: {
1805
+ sampled,
1806
+ spanId,
1807
+ traceFlags: sampled ? SAMPLED_FLAG : UNSAMPLED_FLAG,
1808
+ traceId,
1809
+ ...trusted?.parentSpanId === void 0 ? {} : { parentSpanId: trusted.parentSpanId },
1810
+ ...trusted?.traceState === void 0 ? {} : { traceState: trusted.traceState }
1811
+ }
1812
+ };
1813
+ };
1814
+ const injectTraceContext = (trace, headers) => {
1815
+ headers.traceparent = buildTraceparent(trace.traceId, trace.spanId, trace.sampled);
1816
+ if (trace.traceState !== void 0) {
1817
+ headers.tracestate = trace.traceState;
1818
+ }
1819
+ };
1820
+
1821
+ const createResourceAttributeResolver = (environment, request) => {
1822
+ let resolved;
1823
+ return () => {
1824
+ if (resolved === void 0) {
1825
+ const read = readerFromRecord(environment);
1826
+ const cf = request === void 0 ? void 0 : request.cf;
1827
+ resolved = mergeResourceAttributes(detectServiceResource(read), detectCloudflareResource(read, cf));
1828
+ }
1829
+ return resolved;
1830
+ };
1831
+ };
1832
+
1665
1833
  const SCHEDULED_PATH = "/_lunora/admin/scheduled";
1666
1834
  const SCHEDULED_STATUS_PATH = "/_lunora/admin/scheduled/status";
1667
1835
  const SCHEDULED_WS_PATH = "/_lunora/admin/scheduled/ws";
@@ -1843,6 +2011,51 @@ const buildStorageAdminRoutes = (deps) => {
1843
2011
  };
1844
2012
  };
1845
2013
 
2014
+ const cfString = (request, ...path) => {
2015
+ let current = request.cf;
2016
+ for (const key of path) {
2017
+ if (typeof current !== "object" || current === null) {
2018
+ return void 0;
2019
+ }
2020
+ current = current[key];
2021
+ }
2022
+ return typeof current === "string" ? current : void 0;
2023
+ };
2024
+ const SIGNAL_CHECKS = {
2025
+ // Edge-verified client certificate — the platform sets `certVerified`, so
2026
+ // unlike a header this cannot be spoofed by the caller.
2027
+ mtls: (request) => cfString(request, "tlsClientAuth", "certVerified") === "SUCCESS"
2028
+ };
2029
+ const resolveTraceTrust = (option) => {
2030
+ if (option === void 0 || option === false) {
2031
+ return () => false;
2032
+ }
2033
+ if (option === true) {
2034
+ return () => true;
2035
+ }
2036
+ if (typeof option === "function") {
2037
+ return option;
2038
+ }
2039
+ const check = Object.entries(SIGNAL_CHECKS).find(([signal]) => signal === option)?.[1];
2040
+ return check ?? (() => false);
2041
+ };
2042
+ const createDroppedTraceNotice = (option) => {
2043
+ if (option !== void 0) {
2044
+ return () => {
2045
+ };
2046
+ }
2047
+ let notified = false;
2048
+ return () => {
2049
+ if (notified) {
2050
+ return;
2051
+ }
2052
+ notified = true;
2053
+ console.warn(
2054
+ '[lunora] Ignored an inbound `traceparent`, so this request starts a new trace instead of joining the caller\'s. That is the safe default: the header is caller-supplied, and trusting it lets any client choose which trace its spans and logs join. If this worker sits behind a gateway, service mesh, or Cloudflare Access that sets `traceparent` itself, set `trustInboundTraceContext: true` on createWorker() (or `"mtls"` to trust only edge-verified client certificates). Set it to `false` to keep this behaviour and silence this message.'
2055
+ );
2056
+ };
2057
+ };
2058
+
1846
2059
  const VECTOR_INDEXES_PATH = "/_lunora/admin/vector/indexes";
1847
2060
  const VECTOR_QUERY_PATH = "/_lunora/admin/vector/query";
1848
2061
  const buildVectorAdminRoutes = (deps) => {
@@ -1987,6 +2200,39 @@ const NDJSON_ENCODER = new TextEncoder();
1987
2200
  const RPC_PATH = "/_lunora/rpc";
1988
2201
  const RPC_BATCH_PATH = "/_lunora/rpc-batch";
1989
2202
  const WS_PATH = "/_lunora/ws";
2203
+ const buildSinkContext = (environment, request, waitUntil) => {
2204
+ return {
2205
+ resourceAttributes: createResourceAttributeResolver(environment, request),
2206
+ ...waitUntil === void 0 ? {} : { waitUntil }
2207
+ };
2208
+ };
2209
+ const traceEventFields = (trace) => {
2210
+ return {
2211
+ spanId: trace.spanId,
2212
+ traceFlags: trace.traceFlags,
2213
+ traceId: trace.traceId,
2214
+ ...trace.parentSpanId === void 0 ? {} : { parentSpanId: trace.parentSpanId }
2215
+ };
2216
+ };
2217
+ const requestTelemetryMeta = (request) => {
2218
+ const { method } = request;
2219
+ const userAgent = request.headers.get("user-agent") ?? void 0;
2220
+ let url;
2221
+ try {
2222
+ url = new URL(request.url);
2223
+ } catch {
2224
+ return { method, userAgent };
2225
+ }
2226
+ const port = url.port === "" ? void 0 : Number(url.port);
2227
+ return {
2228
+ host: url.hostname,
2229
+ method,
2230
+ path: url.pathname,
2231
+ port: Number.isNaN(port) ? void 0 : port,
2232
+ scheme: url.protocol.replace(":", ""),
2233
+ userAgent
2234
+ };
2235
+ };
1990
2236
  const VOICE_PATH_PREFIX = "/_lunora/voice/";
1991
2237
  const SCHEDULER_DISPATCH_PATH = "/_lunora/scheduler/dispatch";
1992
2238
  const CRON_JOBS_RUN_PATH = "/_lunora/admin/cron-jobs/run";
@@ -2009,6 +2255,7 @@ const readForwardedIdentity = (request) => {
2009
2255
  };
2010
2256
  const DEFAULT_AUTH_BASE_PATH = "/api/auth";
2011
2257
  const RECORD_AUTH_EVENT_OP = "__lunora_admin__:recordAuthEvent";
2258
+ const LIST_PUSH_SUBSCRIPTIONS_OP = "__lunora_admin__:listPushSubscriptions";
2012
2259
  const AUTH_ATTEMPT_SEGMENTS = ["/sign-in", "/sign-up", "/callback"];
2013
2260
  const isAuthAttemptPath = (pathname, basePath) => {
2014
2261
  const base = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
@@ -2276,7 +2523,28 @@ const checkAdminWsToken = async (request, expected, requireEphemeral) => {
2276
2523
  }
2277
2524
  return constantTimeEqual(expected, supplied);
2278
2525
  };
2526
+ const detectBindingProbe = (key, value) => {
2527
+ if (value === null || typeof value !== "object" && typeof value !== "function") {
2528
+ return void 0;
2529
+ }
2530
+ const shape = value;
2531
+ if (typeof shape["prepare"] === "function" && typeof shape["batch"] === "function" && typeof shape["dump"] === "function") {
2532
+ return d1Probe(`d1:${key}`, value);
2533
+ }
2534
+ if (typeof shape["list"] === "function" && typeof shape["head"] === "function" && typeof shape["createMultipartUpload"] === "function") {
2535
+ return presenceProbe(`r2:${key}`, true);
2536
+ }
2537
+ if (typeof shape["send"] === "function" && typeof shape["sendBatch"] === "function" && typeof shape["get"] !== "function") {
2538
+ return presenceProbe(`queue:${key}`, true);
2539
+ }
2540
+ if (typeof shape["connectionString"] === "string") {
2541
+ return presenceProbe(`hyperdrive:${key}`, true);
2542
+ }
2543
+ return void 0;
2544
+ };
2279
2545
  const createWorker = (options) => {
2546
+ const isTrustedUpstream = resolveTraceTrust(options.trustInboundTraceContext);
2547
+ const noticeDroppedTrace = createDroppedTraceNotice(options.trustInboundTraceContext);
2280
2548
  const defaultShard = options.defaultShardKey ?? "__root__";
2281
2549
  const publicResolveIdentity = wrapResolverWithContract(options.resolveIdentity, options.identity);
2282
2550
  const shardDO = applyJurisdiction(options.shardDO, options.jurisdiction);
@@ -2488,17 +2756,6 @@ const createWorker = (options) => {
2488
2756
  await releasePoolSlot(candidate);
2489
2757
  return response;
2490
2758
  };
2491
- const dataMovementAdminRoutes = buildDataMovementAdminRoutes({
2492
- applyGlobals: options.applyGlobals,
2493
- isAdmin: requestIsAdmin,
2494
- knownTables: () => collectKnownTables(),
2495
- queryCoordinator: options.queryCoordinator,
2496
- resolveForwardContext: resolveAdminForwardContext,
2497
- shardDO,
2498
- streamExportRows: (coordinator, headers, tables, writeRow) => streamExportRows(options, coordinator, headers, tables, writeRow, shardDO),
2499
- streamingImport: (request, headers) => streamingImport(request, options, headers, shardDO),
2500
- syncGlobals: options.syncGlobals
2501
- });
2502
2759
  const assertAdminAuthorized = (request) => {
2503
2760
  if (!requestIsAdmin(request)) {
2504
2761
  throw new LunoraError("admin endpoint requires a valid admin bearer", { code: "ADMIN_FORBIDDEN", status: 403 });
@@ -2511,6 +2768,49 @@ const createWorker = (options) => {
2511
2768
  }
2512
2769
  return value;
2513
2770
  };
2771
+ const getAuthAuditLog = buildGetAuthAuditLog({
2772
+ assertAdmin: assertAdminAuthorized,
2773
+ getReader: () => options.authAuditReader
2774
+ });
2775
+ const listPushSubscriptions = async (request, args) => {
2776
+ assertAdminAuthorized(request);
2777
+ const store = options.notifySubscriptionStore;
2778
+ if (store === void 0) {
2779
+ return Response.json({ subscriptions: [] }, { headers: { "content-type": "application/json" }, status: 200 });
2780
+ }
2781
+ const rawKind = args?.["kind"];
2782
+ const rawUserId = args?.["userId"];
2783
+ const kindFilter = typeof rawKind === "string" && rawKind !== "" ? rawKind : void 0;
2784
+ const userIdFilter = typeof rawUserId === "string" && rawUserId !== "" ? rawUserId : void 0;
2785
+ const stored = await store.list();
2786
+ const subscriptions = stored.filter((device) => (kindFilter === void 0 || device.kind === kindFilter) && (userIdFilter === void 0 || device.userId === userIdFilter)).map(({ keys: _keys, token: _token, ...device }) => device);
2787
+ return Response.json({ subscriptions }, { headers: { "content-type": "application/json" }, status: 200 });
2788
+ };
2789
+ const serveReservedWorkerRpc = async (request, envelope) => {
2790
+ if (envelope.fanOut) {
2791
+ return void 0;
2792
+ }
2793
+ if (envelope.functionPath === GET_AUTH_AUDIT_LOG_OP) {
2794
+ return getAuthAuditLog(request, envelope.args ?? {});
2795
+ }
2796
+ if (envelope.functionPath === LIST_PUSH_SUBSCRIPTIONS_OP) {
2797
+ return listPushSubscriptions(request, envelope.args);
2798
+ }
2799
+ return void 0;
2800
+ };
2801
+ const dataMovementAdminRoutes = buildDataMovementAdminRoutes({
2802
+ applyGlobals: options.applyGlobals,
2803
+ exportCursorStore: options.exportCursorStore,
2804
+ exportSinks: options.exportSinks,
2805
+ knownTables: () => collectKnownTables(),
2806
+ queryCoordinator: options.queryCoordinator,
2807
+ requireAdminOption,
2808
+ resolveForwardContext: resolveAdminForwardContext,
2809
+ shardDO,
2810
+ streamExportRows: (coordinator, headers, tables, writeRow) => streamExportRows(options, coordinator, headers, tables, writeRow, shardDO),
2811
+ streamingImport: (request, headers) => streamingImport(request, options, headers, shardDO),
2812
+ syncGlobals: options.syncGlobals
2813
+ });
2514
2814
  const queryParameter = (url, name) => {
2515
2815
  const value = url.searchParams.get(name);
2516
2816
  return value === null || value === "" ? void 0 : value;
@@ -2588,6 +2888,32 @@ const createWorker = (options) => {
2588
2888
  queryParameter,
2589
2889
  requireAdminOption
2590
2890
  });
2891
+ const resolveHealthProbes = (env) => {
2892
+ const probes = [];
2893
+ const namespace = shardDO ?? env?.SHARD;
2894
+ if (namespace !== void 0) {
2895
+ probes.push(durableObjectProbe("durable-object", namespace, defaultShard));
2896
+ }
2897
+ if (options.health?.disableBindingProbes !== true) {
2898
+ for (const [key, value] of Object.entries(env ?? {})) {
2899
+ const probe = detectBindingProbe(key, value);
2900
+ if (probe !== void 0) {
2901
+ probes.push(probe);
2902
+ }
2903
+ }
2904
+ }
2905
+ for (const probe of options.health?.probes ?? []) {
2906
+ probes.push(probe);
2907
+ }
2908
+ return probes;
2909
+ };
2910
+ const healthRoutes = buildHealthRoutes({
2911
+ appName: options.health?.appName,
2912
+ appVersion: options.health?.appVersion,
2913
+ auth: options.health?.auth ?? "public",
2914
+ isAdmin: requestIsAdmin,
2915
+ resolveProbes: resolveHealthProbes
2916
+ });
2591
2917
  const buildHttpActionContext = async (request, env, context) => {
2592
2918
  const { claims, headers, userId } = await resolveForwardContext(request, env, publicResolveIdentity);
2593
2919
  const run = async (reference, args = {}) => {
@@ -2775,14 +3101,25 @@ const createWorker = (options) => {
2775
3101
  guardUnauthenticatedShardAccess("shard");
2776
3102
  }
2777
3103
  };
2778
- const dispatchSingleShard = async (functionPath, args, shardKey, forwardedHeaders, sinkContext) => {
3104
+ const dispatchSingleShard = async (request, functionPath, args, shardKey, forwardedHeaders, sinkContext) => {
2779
3105
  const rpcStartedAt = Date.now();
2780
- const { observability } = options;
2781
- const traceId = otlpRandomHex(16);
2782
- const spanId = otlpRandomHex(8);
3106
+ const { observability, sampling } = options;
3107
+ const requestMeta = requestTelemetryMeta(request);
3108
+ const { decision, ignoredUpstream, trace } = beginDispatchTrace(request, {
3109
+ ...sampling === void 0 ? {} : { sampling },
3110
+ trustInbound: isTrustedUpstream(request)
3111
+ });
3112
+ if (ignoredUpstream) {
3113
+ noticeDroppedTrace();
3114
+ }
3115
+ const outgoingHeaders = {
3116
+ ...forwardedHeaders,
3117
+ "x-lunora-sample-errors": decision.keepErrors ? "1" : "0"
3118
+ };
3119
+ injectTraceContext(trace, outgoingHeaders);
2783
3120
  const forwarded = new Request(`https://shard.internal/rpc`, {
2784
3121
  body: JSON.stringify({ args, functionPath }),
2785
- headers: { ...forwardedHeaders, traceparent: buildTraceparent(traceId, spanId) },
3122
+ headers: outgoingHeaders,
2786
3123
  method: "POST"
2787
3124
  });
2788
3125
  try {
@@ -2790,28 +3127,33 @@ const createWorker = (options) => {
2790
3127
  emitRpcEvent(
2791
3128
  observability,
2792
3129
  {
3130
+ ...requestMeta,
3131
+ ...traceEventFields(trace),
2793
3132
  durationMs: Date.now() - rpcStartedAt,
2794
3133
  functionPath,
2795
3134
  ok: response.ok,
2796
3135
  shardKey,
2797
- spanId,
2798
- traceId,
2799
3136
  ...response.ok ? {} : { error: { code: "SHARD_ERROR", message: `shard returned ${String(response.status)}`, status: response.status } }
2800
3137
  },
2801
- sinkContext
3138
+ sinkContext,
3139
+ sampling
2802
3140
  );
2803
3141
  return response;
2804
3142
  } catch (error) {
2805
- emitRpcEvent(observability, { ...buildErrorEvent(functionPath, Date.now() - rpcStartedAt, error, { shardKey }), spanId, traceId }, sinkContext);
3143
+ emitRpcEvent(
3144
+ observability,
3145
+ {
3146
+ ...requestMeta,
3147
+ ...traceEventFields(trace),
3148
+ ...buildErrorEvent(functionPath, Date.now() - rpcStartedAt, error, { shardKey })
3149
+ },
3150
+ sinkContext,
3151
+ sampling
3152
+ );
2806
3153
  throw error;
2807
3154
  }
2808
3155
  };
2809
- const handleRpc = async (request, env, context) => {
2810
- if (request.method !== "POST") {
2811
- throw new LunoraError("RPC endpoint requires POST", { code: "METHOD_NOT_ALLOWED", status: 405 });
2812
- }
2813
- const envelope = await parseEnvelope(request);
2814
- logRpcDebug(env, envelope);
3156
+ const assertDispatchableEnvelope = (envelope) => {
2815
3157
  if (envelope.fanOut && envelope.shardKey) {
2816
3158
  throw new LunoraError("RPC envelope cannot set both `shardKey` and `fanOut`", { code: "BAD_REQUEST", status: 400 });
2817
3159
  }
@@ -2827,17 +3169,26 @@ const createWorker = (options) => {
2827
3169
  status: 400
2828
3170
  });
2829
3171
  }
3172
+ };
3173
+ const handleRpc = async (request, env, context) => {
3174
+ if (request.method !== "POST") {
3175
+ throw new LunoraError("RPC endpoint requires POST", { code: "METHOD_NOT_ALLOWED", status: 405 });
3176
+ }
3177
+ const envelope = await parseEnvelope(request);
3178
+ logRpcDebug(env, envelope);
3179
+ assertDispatchableEnvelope(envelope);
3180
+ const reservedResponse = await serveReservedWorkerRpc(request, envelope);
3181
+ if (reservedResponse !== void 0) {
3182
+ return reservedResponse;
3183
+ }
2830
3184
  const { headers: forwardedHeaders, identity } = await resolveForwardContext(request, env, publicResolveIdentity);
2831
3185
  await authorizeRpcEnvelope(envelope, identity);
2832
3186
  const x402Tag = resolveX402Charge(envelope, options);
2833
3187
  {
2834
3188
  const rpcStartedAt = Date.now();
2835
3189
  const { observability } = options;
2836
- const sinkContext = context ? {
2837
- waitUntil: (promise) => {
2838
- context.waitUntil?.(promise);
2839
- }
2840
- } : void 0;
3190
+ const requestMeta = requestTelemetryMeta(request);
3191
+ const sinkContext = buildSinkContext(env, request, context && ((promise) => context.waitUntil?.(promise)));
2841
3192
  if (envelope.fanOut) {
2842
3193
  const coordinator = options.queryCoordinator;
2843
3194
  if (!coordinator) {
@@ -2863,6 +3214,7 @@ const createWorker = (options) => {
2863
3214
  table: envelope.fanOut.table
2864
3215
  },
2865
3216
  functionPath: envelope.functionPath,
3217
+ ...requestMeta,
2866
3218
  ok: true
2867
3219
  },
2868
3220
  sinkContext
@@ -2874,14 +3226,17 @@ const createWorker = (options) => {
2874
3226
  } catch (error) {
2875
3227
  emitRpcEvent(
2876
3228
  observability,
2877
- buildErrorEvent(envelope.functionPath, Date.now() - rpcStartedAt, error, { fanOut: { table: envelope.fanOut.table } }),
3229
+ {
3230
+ ...buildErrorEvent(envelope.functionPath, Date.now() - rpcStartedAt, error, { fanOut: { table: envelope.fanOut.table } }),
3231
+ ...requestMeta
3232
+ },
2878
3233
  sinkContext
2879
3234
  );
2880
3235
  throw error;
2881
3236
  }
2882
3237
  }
2883
3238
  const shardKey = envelope.shardKey ?? defaultShard;
2884
- const dispatch = () => dispatchSingleShard(envelope.functionPath, envelope.args ?? {}, shardKey, forwardedHeaders, sinkContext);
3239
+ const dispatch = () => dispatchSingleShard(request, envelope.functionPath, envelope.args ?? {}, shardKey, forwardedHeaders, sinkContext);
2885
3240
  if (x402Tag && options.x402Charge) {
2886
3241
  return options.x402Charge(request, { functionPath: envelope.functionPath, price: x402Tag.price }, dispatch);
2887
3242
  }
@@ -2927,11 +3282,8 @@ const createWorker = (options) => {
2927
3282
  )
2928
3283
  );
2929
3284
  const { observability } = options;
2930
- const sinkContext = context ? {
2931
- waitUntil: (promise) => {
2932
- context.waitUntil?.(promise);
2933
- }
2934
- } : void 0;
3285
+ const sinkContext = buildSinkContext(env, request, context && ((promise) => context.waitUntil?.(promise)));
3286
+ const requestMeta = requestTelemetryMeta(request);
2935
3287
  const results = [];
2936
3288
  const bookmarks = [];
2937
3289
  const slotError = (entry, status, code, message) => {
@@ -2952,6 +3304,7 @@ const createWorker = (options) => {
2952
3304
  {
2953
3305
  durationMs,
2954
3306
  functionPath: entry.functionPath,
3307
+ ...requestMeta,
2955
3308
  ok,
2956
3309
  shardKey,
2957
3310
  ...ok ? {} : { error: { code: "SHARD_ERROR", message: `batched call returned ${String(status)}`, status } }
@@ -2972,13 +3325,12 @@ const createWorker = (options) => {
2972
3325
  } catch (error) {
2973
3326
  const durationMs2 = Date.now() - subStartedAt;
2974
3327
  const { body: errorBody } = toErrorBody(error, { fallbackCode: "SHARD_UNAVAILABLE", redactedMessage: "shard unavailable" });
2975
- failSubBatch(
2976
- entries,
2977
- 502,
2978
- errorBody.code,
2979
- errorBody.message,
2980
- (entry) => buildErrorEvent(entry.functionPath, durationMs2, error, { shardKey })
2981
- );
3328
+ failSubBatch(entries, 502, errorBody.code, errorBody.message, (entry) => {
3329
+ return {
3330
+ ...buildErrorEvent(entry.functionPath, durationMs2, error, { shardKey }),
3331
+ ...requestMeta
3332
+ };
3333
+ });
2982
3334
  return;
2983
3335
  }
2984
3336
  const durationMs = Date.now() - subStartedAt;
@@ -2996,6 +3348,7 @@ const createWorker = (options) => {
2996
3348
  durationMs,
2997
3349
  error: { code: "SHARD_ERROR", message, status: response.status },
2998
3350
  functionPath: entry.functionPath,
3351
+ ...requestMeta,
2999
3352
  ok: false,
3000
3353
  shardKey
3001
3354
  };
@@ -3030,7 +3383,8 @@ const createWorker = (options) => {
3030
3383
  const { headers: forwardedHeaders, identity } = await resolveForwardContext(request, env, publicResolveIdentity);
3031
3384
  await authorizeRpcEnvelope({ functionPath, shardKey: callOptions.shardKey }, identity);
3032
3385
  const shardKey = callOptions.shardKey ?? defaultShard;
3033
- return await dispatchSingleShard(functionPath, args, shardKey, forwardedHeaders);
3386
+ const serverSinkContext = buildSinkContext(env, request, callOptions.waitUntil);
3387
+ return await dispatchSingleShard(request, functionPath, args, shardKey, forwardedHeaders, serverSinkContext);
3034
3388
  } catch (error) {
3035
3389
  return toErrorResponse(error);
3036
3390
  }
@@ -3171,6 +3525,25 @@ const createWorker = (options) => {
3171
3525
  }
3172
3526
  return authResponse;
3173
3527
  };
3528
+ const invokeExposed = async ({ args, env, functionPath, request, shardKey, waitUntil }) => {
3529
+ const envelope = { functionPath, ...shardKey === void 0 ? {} : { shardKey } };
3530
+ const { headers: forwardedHeaders, identity } = await resolveForwardContext(request, env, publicResolveIdentity);
3531
+ await authorizeRpcEnvelope(envelope, identity);
3532
+ const resolvedShardKey = shardKey ?? defaultShard;
3533
+ const restSinkContext = buildSinkContext(env, request, waitUntil);
3534
+ const dispatch = () => dispatchSingleShard(request, functionPath, args, resolvedShardKey, forwardedHeaders, restSinkContext);
3535
+ const x402Tag = resolveX402Charge(envelope, options);
3536
+ if (x402Tag && options.x402Charge) {
3537
+ return options.x402Charge(request, { functionPath, price: x402Tag.price }, dispatch);
3538
+ }
3539
+ return dispatch();
3540
+ };
3541
+ const restRoutes = buildRestRoutes({
3542
+ functions: options.functions ?? {},
3543
+ invoke: invokeExposed,
3544
+ readJsonBody: readJsonBodyWithLimit,
3545
+ ...options.restRateLimit ? { rateLimit: options.restRateLimit } : {}
3546
+ });
3174
3547
  const customRoutes = options.routes !== void 0 && Object.keys(options.routes).length > 0 ? options.routes : void 0;
3175
3548
  const internalRoutes = {
3176
3549
  [STATUS_PATH]: (request) => {
@@ -3216,6 +3589,14 @@ const createWorker = (options) => {
3216
3589
  ...kvAdminRoutes,
3217
3590
  ...logArchiveAdminRoutes,
3218
3591
  ...introspectionAdminRoutes,
3592
+ // `/_lunora/health` + `/_lunora/health/ready` — public (or admin-gated)
3593
+ // liveness/readiness probes; not under the admin prefix, so `applyAdminGate`
3594
+ // never runs on them (the handler self-gates when `auth: "admin"`).
3595
+ ...healthRoutes,
3596
+ // `/_lunora/rest/<namespace>/<fn>` — the opt-in public REST surface, one
3597
+ // route per `.expose({ rest: true })` procedure (default-closed). Routes
3598
+ // THROUGH the procedure, so auth/RLS/validators are enforced at the shard.
3599
+ ...restRoutes,
3219
3600
  // `/_lunora/admin/auth/*` — the whole user-management plane, one route per
3220
3601
  // `AuthAdmin` op, dispatched by the descriptor table in `./auth-admin-routes`.
3221
3602
  ...buildAuthAdminRoutes({
@@ -3354,4 +3735,4 @@ const resolveLunoraOptions = (options, env) => {
3354
3735
  const createLunoraHandler = (options = {}) => (request, env, context) => createWorker(resolveLunoraOptions(options, env)).fetch(request, env, context ?? NOOP_EXECUTION_CONTEXT);
3355
3736
  const defineRpcEnvelope = (envelope) => envelope;
3356
3737
 
3357
- export { NOOP_EXECUTION_CONTEXT, composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, probeRelayCount, resolveLunoraOptions, withFrameworkWorker };
3738
+ export { GET_AUTH_AUDIT_LOG_OP, NOOP_EXECUTION_CONTEXT, composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, probeRelayCount, resolveLunoraOptions, withFrameworkWorker };