@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.
- package/dist/index.d.mts +1853 -1012
- package/dist/index.d.ts +1853 -1012
- package/dist/index.mjs +6 -3
- package/dist/packem_shared/HEALTH_PATH-e5J_NHBx.mjs +150 -0
- package/dist/packem_shared/{analyticsEngineSink-DE48mQA9.mjs → analyticsEngineSink-yLFNjHDt.mjs} +74 -17
- package/dist/packem_shared/argsFromQuery-c-U1WRy-.mjs +121 -0
- package/dist/packem_shared/{composeWorker-DAwO9LLs.mjs → composeWorker-DiWwOXXt.mjs} +475 -94
- package/dist/packem_shared/createKvCursorStore-g8aA6B4L.mjs +165 -0
- package/dist/packem_shared/emitLogEvent-BlGMnKZK.mjs +1 -0
- package/dist/packem_shared/method-guard-Qzw99aCj.mjs +3 -0
- package/dist/packem_shared/observability--NOFYBFc.mjs +47 -0
- package/dist/packem_shared/otlp-resource-Dow6-F_u.mjs +163 -0
- package/package.json +3 -3
- package/dist/packem_shared/emitLogEvent-pEdtqAK8.mjs +0 -20
- package/dist/packem_shared/otlp-DOLuy1Aj.mjs +0 -95
|
@@ -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 './
|
|
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 {
|
|
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
|
-
|
|
746
|
-
|
|
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
|
-
|
|
775
|
-
|
|
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
|
-
|
|
802
|
-
|
|
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
|
-
|
|
842
|
-
|
|
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
|
-
|
|
869
|
-
|
|
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
|
|
2782
|
-
const
|
|
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:
|
|
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(
|
|
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
|
|
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
|
|
2837
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
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
|
-
|
|
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 };
|