@lunora/runtime 1.0.0-alpha.27 → 1.0.0-alpha.29
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 +1431 -1412
- package/dist/index.d.ts +1431 -1412
- package/dist/index.mjs +2 -2
- package/dist/packem_shared/{analyticsEngineSink-F-5ZAdUA.mjs → analyticsEngineSink-DKZvbDFG.mjs} +102 -18
- package/dist/packem_shared/{composeWorker-UZKPkF3Q.mjs → composeWorker-Br4_GgSW.mjs} +127 -32
- package/dist/packem_shared/{otlp-D_YzGXu1.mjs → otlp-0FQT3AI8.mjs} +4 -0
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export { toAirbyteMessages, toFivetranResponse } from './packem_shared/toAirbyteMessages-DrHdplb4.mjs';
|
|
2
|
-
export { composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, resolveLunoraOptions, withFrameworkWorker } from './packem_shared/composeWorker-
|
|
2
|
+
export { composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, resolveLunoraOptions, withFrameworkWorker } from './packem_shared/composeWorker-Br4_GgSW.mjs';
|
|
3
3
|
export { createCrossShardRelationCapabilities } from './packem_shared/createCrossShardRelationCapabilities-CbcWjkAn.mjs';
|
|
4
4
|
export { DEFAULT_REGISTRY_CACHE_TTL_MS, SHARD_REGISTRY_DO_NAME, createDynamicShardRegistry } from './packem_shared/DEFAULT_REGISTRY_CACHE_TTL_MS-B3pA7aXp.mjs';
|
|
5
5
|
export { LunoraError, toErrorResponse } from './packem_shared/LunoraError-Bpb9EFJ3.mjs';
|
|
6
6
|
export { emitLogEvent, emitRpcEvent } from './packem_shared/emitLogEvent-pEdtqAK8.mjs';
|
|
7
|
-
export { analyticsEngineSink, combineSinks, consoleSink, otlpSink, sentrySink, webhookSink } from './packem_shared/analyticsEngineSink-
|
|
7
|
+
export { analyticsEngineSink, combineSinks, consoleSink, otlpSink, pipelineLogSink, sentrySink, webhookSink } from './packem_shared/analyticsEngineSink-DKZvbDFG.mjs';
|
|
8
8
|
export { createQueryCoordinator, createStaticShardRegistry, mergeStrategyForAggregate } from './packem_shared/createQueryCoordinator-DNCJzOZE.mjs';
|
|
9
9
|
export { applyJurisdiction, resolveShard } from './packem_shared/applyJurisdiction-BkZtTkct.mjs';
|
|
10
10
|
export { decorateResponse, enforceOrigin, handleCorsPreflight, resolveSecurity } from './packem_shared/decorateResponse-DRWQFNhF.mjs';
|
package/dist/packem_shared/{analyticsEngineSink-F-5ZAdUA.mjs → analyticsEngineSink-DKZvbDFG.mjs}
RENAMED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
import { m as mergeHeaders, o as otlpRandomHex, w as wrapResourceSpans,
|
|
1
|
+
import { m as mergeHeaders, o as otlpRandomHex, w as wrapResourceSpans, e as encodeAttribute, a as wrapResourceLogs, O as OTLP_SEVERITY, c as otlpUnixNano } from './otlp-0FQT3AI8.mjs';
|
|
2
|
+
|
|
3
|
+
const stringifyFieldValue = (value) => {
|
|
4
|
+
if (typeof value === "string") {
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
try {
|
|
8
|
+
return JSON.stringify(value) ?? String(value);
|
|
9
|
+
} catch {
|
|
10
|
+
return String(value);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const coerceFieldValue = (value) => typeof value === "boolean" || typeof value === "number" || typeof value === "string" ? value : stringifyFieldValue(value);
|
|
2
14
|
|
|
3
15
|
const shouldSkip = (event, onlyErrors) => onlyErrors === true && event.ok;
|
|
4
16
|
const otlpTraceBody = (event, serviceName, endMs) => {
|
|
@@ -33,20 +45,32 @@ const otlpTraceBody = (event, serviceName, endMs) => {
|
|
|
33
45
|
return wrapResourceSpans(span, "@lunora/runtime", serviceName);
|
|
34
46
|
};
|
|
35
47
|
const otlpLogBody = (event, serviceName) => {
|
|
36
|
-
const
|
|
48
|
+
const attributeByKey = /* @__PURE__ */ new Map();
|
|
49
|
+
attributeByKey.set("lunora.function_path", encodeAttribute("lunora.function_path", event.functionPath));
|
|
37
50
|
if (event.shardKey !== void 0) {
|
|
38
|
-
|
|
51
|
+
attributeByKey.set("lunora.shard_key", encodeAttribute("lunora.shard_key", event.shardKey));
|
|
39
52
|
}
|
|
40
53
|
if (event.userId !== void 0) {
|
|
41
|
-
|
|
54
|
+
attributeByKey.set("lunora.user_id", encodeAttribute("lunora.user_id", event.userId));
|
|
55
|
+
}
|
|
56
|
+
if (event.fields) {
|
|
57
|
+
for (const [key, value] of Object.entries(event.fields)) {
|
|
58
|
+
attributeByKey.set(key, encodeAttribute(key, coerceFieldValue(value)));
|
|
59
|
+
}
|
|
42
60
|
}
|
|
43
61
|
const logRecord = {
|
|
44
|
-
attributes,
|
|
62
|
+
attributes: [...attributeByKey.values()],
|
|
45
63
|
body: { stringValue: event.message },
|
|
46
64
|
severityNumber: OTLP_SEVERITY[event.level],
|
|
47
65
|
severityText: event.level.toUpperCase(),
|
|
48
66
|
timeUnixNano: otlpUnixNano(event.ts)
|
|
49
67
|
};
|
|
68
|
+
if (event.traceId !== void 0) {
|
|
69
|
+
logRecord.traceId = event.traceId;
|
|
70
|
+
}
|
|
71
|
+
if (event.spanId !== void 0) {
|
|
72
|
+
logRecord.spanId = event.spanId;
|
|
73
|
+
}
|
|
50
74
|
return wrapResourceLogs(logRecord, "@lunora/runtime", serviceName);
|
|
51
75
|
};
|
|
52
76
|
const otlpPost = (url, body, headers, context) => {
|
|
@@ -63,7 +87,7 @@ const consoleSink = (options = {}) => {
|
|
|
63
87
|
const { onlyErrors } = options;
|
|
64
88
|
return {
|
|
65
89
|
onLog: (event) => {
|
|
66
|
-
if (event.level === "error") {
|
|
90
|
+
if (event.level === "error" || event.level === "fatal") {
|
|
67
91
|
console.error("[lunora:log]", event.functionPath, event.message);
|
|
68
92
|
} else {
|
|
69
93
|
console.log("[lunora:log]", event.functionPath, event.message);
|
|
@@ -82,9 +106,33 @@ const consoleSink = (options = {}) => {
|
|
|
82
106
|
};
|
|
83
107
|
};
|
|
84
108
|
const webhookSink = (options) => {
|
|
85
|
-
const { headers, onlyErrors, transform, url } = options;
|
|
109
|
+
const { headers, onlyErrors, transform, transformLog, url } = options;
|
|
86
110
|
const mergedHeaders = mergeHeaders({ "content-type": "application/json" }, headers);
|
|
111
|
+
const post = (payload, context) => {
|
|
112
|
+
try {
|
|
113
|
+
const sent = fetch(url, { body: JSON.stringify(payload), headers: mergedHeaders, method: "POST" }).catch(() => {
|
|
114
|
+
});
|
|
115
|
+
if (context?.waitUntil) {
|
|
116
|
+
context.waitUntil(sent);
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
}
|
|
120
|
+
};
|
|
87
121
|
return {
|
|
122
|
+
onLog: (event, context) => {
|
|
123
|
+
let payload = event;
|
|
124
|
+
if (transformLog) {
|
|
125
|
+
try {
|
|
126
|
+
payload = transformLog(event);
|
|
127
|
+
} catch {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (payload === null || payload === void 0) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
post(payload, context);
|
|
135
|
+
},
|
|
88
136
|
onRpc: (event, context) => {
|
|
89
137
|
if (shouldSkip(event, onlyErrors)) {
|
|
90
138
|
return;
|
|
@@ -101,24 +149,24 @@ const webhookSink = (options) => {
|
|
|
101
149
|
if (payload === null || payload === void 0) {
|
|
102
150
|
return;
|
|
103
151
|
}
|
|
104
|
-
|
|
105
|
-
body: JSON.stringify(payload),
|
|
106
|
-
headers: mergedHeaders,
|
|
107
|
-
method: "POST"
|
|
108
|
-
}).catch(() => {
|
|
109
|
-
});
|
|
110
|
-
if (context?.waitUntil) {
|
|
111
|
-
context.waitUntil(sent);
|
|
112
|
-
}
|
|
152
|
+
post(payload, context);
|
|
113
153
|
} catch {
|
|
114
154
|
}
|
|
115
155
|
}
|
|
116
156
|
};
|
|
117
157
|
};
|
|
118
158
|
const sentrySink = (options) => {
|
|
119
|
-
const { capture } = options;
|
|
159
|
+
const { capture, captureLog } = options;
|
|
120
160
|
const onlyErrors = options.onlyErrors ?? true;
|
|
121
161
|
return {
|
|
162
|
+
// Only forward log lines when the caller wired `captureLog`; otherwise
|
|
163
|
+
// `ctx.log` output stays out of Sentry.
|
|
164
|
+
onLog: captureLog ? (event) => {
|
|
165
|
+
try {
|
|
166
|
+
captureLog(event);
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
} : void 0,
|
|
122
170
|
onRpc: (event) => {
|
|
123
171
|
if (shouldSkip(event, onlyErrors)) {
|
|
124
172
|
return;
|
|
@@ -148,6 +196,42 @@ const analyticsEngineSink = (options) => {
|
|
|
148
196
|
}
|
|
149
197
|
};
|
|
150
198
|
};
|
|
199
|
+
const pipelineLogSink = (options) => {
|
|
200
|
+
const { pipeline } = options;
|
|
201
|
+
return {
|
|
202
|
+
onLog: (event, context) => {
|
|
203
|
+
try {
|
|
204
|
+
const record = {
|
|
205
|
+
functionPath: event.functionPath,
|
|
206
|
+
level: event.level,
|
|
207
|
+
message: event.message,
|
|
208
|
+
ts: event.ts
|
|
209
|
+
};
|
|
210
|
+
if (event.fields) {
|
|
211
|
+
record.fields = event.fields;
|
|
212
|
+
}
|
|
213
|
+
if (event.shardKey !== void 0) {
|
|
214
|
+
record.shardKey = event.shardKey;
|
|
215
|
+
}
|
|
216
|
+
if (event.userId !== void 0) {
|
|
217
|
+
record.userId = event.userId;
|
|
218
|
+
}
|
|
219
|
+
if (event.traceId !== void 0) {
|
|
220
|
+
record.traceId = event.traceId;
|
|
221
|
+
}
|
|
222
|
+
if (event.spanId !== void 0) {
|
|
223
|
+
record.spanId = event.spanId;
|
|
224
|
+
}
|
|
225
|
+
const sent = pipeline.send([record]).catch(() => {
|
|
226
|
+
});
|
|
227
|
+
if (context?.waitUntil) {
|
|
228
|
+
context.waitUntil(sent);
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
};
|
|
151
235
|
const otlpSink = (options) => {
|
|
152
236
|
const { endpoint, headers, onlyErrors, token } = options;
|
|
153
237
|
const serviceName = options.serviceName ?? "lunora";
|
|
@@ -197,4 +281,4 @@ const combineSinks = (...sinks) => {
|
|
|
197
281
|
};
|
|
198
282
|
};
|
|
199
283
|
|
|
200
|
-
export { analyticsEngineSink, combineSinks, consoleSink, otlpSink, sentrySink, webhookSink };
|
|
284
|
+
export { analyticsEngineSink, combineSinks, consoleSink, otlpSink, pipelineLogSink, sentrySink, webhookSink };
|
|
@@ -1,6 +1,6 @@
|
|
|
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-
|
|
3
|
+
import { o as otlpRandomHex, b as buildTraceparent } from './otlp-0FQT3AI8.mjs';
|
|
4
4
|
import { LunoraError, toErrorResponse } from './LunoraError-Bpb9EFJ3.mjs';
|
|
5
5
|
import { wrapResolverWithContract } from './composeIdentityResolvers-XGjO7V1J.mjs';
|
|
6
6
|
export { composeIdentityResolvers, routeIdentityResolvers } from './composeIdentityResolvers-XGjO7V1J.mjs';
|
|
@@ -21,6 +21,75 @@ const evictOldestEntry = (map, capacity) => {
|
|
|
21
21
|
const RELAY_NAME_INFIX = "::relay::";
|
|
22
22
|
const relayName = (ownerKey, index) => `${ownerKey}${RELAY_NAME_INFIX}${String(index)}`;
|
|
23
23
|
|
|
24
|
+
const textEncoder = new TextEncoder();
|
|
25
|
+
const toBase64Url = (bytes) => {
|
|
26
|
+
const binary = String.fromCodePoint(...bytes);
|
|
27
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
28
|
+
};
|
|
29
|
+
const fromBase64Url = (input) => {
|
|
30
|
+
const padded = input.replaceAll("-", "+").replaceAll("_", "/") + "===".slice((input.length + 3) % 4);
|
|
31
|
+
const binary = atob(padded);
|
|
32
|
+
const bytes = new Uint8Array(binary.length);
|
|
33
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
34
|
+
bytes[index] = binary.codePointAt(index) ?? 0;
|
|
35
|
+
}
|
|
36
|
+
return bytes;
|
|
37
|
+
};
|
|
38
|
+
const KEY_CACHE_MAX = 64;
|
|
39
|
+
const keyCache = /* @__PURE__ */ new Map();
|
|
40
|
+
const importHmacKey = async (secret) => {
|
|
41
|
+
const cached = keyCache.get(secret);
|
|
42
|
+
if (cached) {
|
|
43
|
+
return cached;
|
|
44
|
+
}
|
|
45
|
+
evictOldestEntry(keyCache, KEY_CACHE_MAX);
|
|
46
|
+
const keyPromise = crypto.subtle.importKey("raw", textEncoder.encode(secret), { hash: "SHA-256", name: "HMAC" }, false, ["sign", "verify"]);
|
|
47
|
+
keyCache.set(secret, keyPromise);
|
|
48
|
+
return keyPromise;
|
|
49
|
+
};
|
|
50
|
+
const signCanonical = async (secret, canonical) => {
|
|
51
|
+
const cryptoKey = await importHmacKey(secret);
|
|
52
|
+
const signature = await crypto.subtle.sign("HMAC", cryptoKey, textEncoder.encode(canonical));
|
|
53
|
+
return toBase64Url(new Uint8Array(signature));
|
|
54
|
+
};
|
|
55
|
+
const verifyCanonical = async (secret, canonical, sigBytes) => {
|
|
56
|
+
const cryptoKey = await importHmacKey(secret);
|
|
57
|
+
return crypto.subtle.verify("HMAC", cryptoKey, sigBytes, textEncoder.encode(canonical));
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const WS_ADMIN_TOKEN_VERSION = "v1";
|
|
61
|
+
const WS_ADMIN_TOKEN_TTL_MS = 6e4;
|
|
62
|
+
const mintWsAdminToken = async (secret, options = {}) => {
|
|
63
|
+
const expiresAtMs = (options.now ?? Date.now()) + (options.ttlMs ?? WS_ADMIN_TOKEN_TTL_MS);
|
|
64
|
+
const canonical = `${WS_ADMIN_TOKEN_VERSION}.${String(expiresAtMs)}`;
|
|
65
|
+
const signature = await signCanonical(secret, canonical);
|
|
66
|
+
return { expiresAtMs, token: `${canonical}.${signature}` };
|
|
67
|
+
};
|
|
68
|
+
const verifyWsAdminToken = async (secret, token, now = Date.now()) => {
|
|
69
|
+
if (secret.length === 0 || token.length === 0) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const parts = token.split(".");
|
|
73
|
+
if (parts.length !== 3) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const [version, expString, signature] = parts;
|
|
77
|
+
if (version !== WS_ADMIN_TOKEN_VERSION || signature.length === 0) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const expiresAtMs = Number(expString);
|
|
81
|
+
if (!Number.isFinite(expiresAtMs) || expiresAtMs <= now) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
let signatureBytes;
|
|
85
|
+
try {
|
|
86
|
+
signatureBytes = fromBase64Url(signature);
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return verifyCanonical(secret, `${version}.${expString}`, signatureBytes);
|
|
91
|
+
};
|
|
92
|
+
|
|
24
93
|
const AUTH_BASE = "/_lunora/admin/auth";
|
|
25
94
|
const AUTH_ADMIN_ERROR_STATUS = {
|
|
26
95
|
INVITER_REQUIRED: 400,
|
|
@@ -1615,11 +1684,11 @@ const buildScheduledAdminRoutes = (deps) => {
|
|
|
1615
1684
|
const stub = resolveSchedulerStub(request);
|
|
1616
1685
|
return stub.fetch(new Request("https://scheduler.internal/status", { method: "GET" }));
|
|
1617
1686
|
};
|
|
1618
|
-
const handleScheduledWebSocket = (request) => {
|
|
1687
|
+
const handleScheduledWebSocket = async (request) => {
|
|
1619
1688
|
if (request.headers.get("Upgrade") !== "websocket") {
|
|
1620
1689
|
throw new LunoraError("WebSocket upgrade header missing", { code: "BAD_REQUEST", status: 426 });
|
|
1621
1690
|
}
|
|
1622
|
-
if (!checkWsAdmin(request)) {
|
|
1691
|
+
if (!await checkWsAdmin(request)) {
|
|
1623
1692
|
throw new LunoraError("admin authorization required", { code: "ADMIN_FORBIDDEN", status: 403 });
|
|
1624
1693
|
}
|
|
1625
1694
|
const namespace = requireSchedulerNamespace();
|
|
@@ -1920,10 +1989,12 @@ const WS_PATH = "/_lunora/ws";
|
|
|
1920
1989
|
const VOICE_PATH_PREFIX = "/_lunora/voice/";
|
|
1921
1990
|
const SCHEDULER_DISPATCH_PATH = "/_lunora/scheduler/dispatch";
|
|
1922
1991
|
const CRON_JOBS_RUN_PATH = "/_lunora/admin/cron-jobs/run";
|
|
1992
|
+
const ADMIN_WS_TOKEN_PATH = "/_lunora/admin/ws-token";
|
|
1923
1993
|
const ADMIN_PATH_PREFIX = "/_lunora/admin/";
|
|
1924
1994
|
const MIGRATE_PATH = "/_lunora/migrate";
|
|
1925
1995
|
const STATUS_PATH = "/_lunora/status";
|
|
1926
1996
|
const isAdminPath = (pathname) => pathname.startsWith(ADMIN_PATH_PREFIX) || pathname === MIGRATE_PATH;
|
|
1997
|
+
const REQUIRE_EPHEMERAL_ENV_VALUES = /* @__PURE__ */ new Set(["1", "enabled", "on", "true", "yes"]);
|
|
1927
1998
|
const readForwardedIdentity = (request) => {
|
|
1928
1999
|
const forwardedUserId = request.headers.get("x-lunora-userid");
|
|
1929
2000
|
const forwardedIdentity = request.headers.get("x-lunora-identity");
|
|
@@ -2188,12 +2259,21 @@ const checkAdminAuth = (request, expected) => {
|
|
|
2188
2259
|
}
|
|
2189
2260
|
return constantTimeEqual(expected, rest.join(" ").trim());
|
|
2190
2261
|
};
|
|
2191
|
-
const checkAdminWsToken = (request, expected) => {
|
|
2262
|
+
const checkAdminWsToken = async (request, expected, requireEphemeral) => {
|
|
2192
2263
|
if (!expected || expected.length === 0) {
|
|
2193
2264
|
return false;
|
|
2194
2265
|
}
|
|
2195
2266
|
const supplied = new URL(request.url).searchParams.get("token");
|
|
2196
|
-
|
|
2267
|
+
if (supplied === null) {
|
|
2268
|
+
return false;
|
|
2269
|
+
}
|
|
2270
|
+
if (await verifyWsAdminToken(expected, supplied)) {
|
|
2271
|
+
return true;
|
|
2272
|
+
}
|
|
2273
|
+
if (requireEphemeral) {
|
|
2274
|
+
return false;
|
|
2275
|
+
}
|
|
2276
|
+
return constantTimeEqual(expected, supplied);
|
|
2197
2277
|
};
|
|
2198
2278
|
const createWorker = (options) => {
|
|
2199
2279
|
const defaultShard = options.defaultShardKey ?? "__root__";
|
|
@@ -2202,11 +2282,20 @@ const createWorker = (options) => {
|
|
|
2202
2282
|
const schedulerDO = options.schedulerDO === void 0 ? void 0 : applyJurisdiction(options.schedulerDO, options.jurisdiction);
|
|
2203
2283
|
let envAdminToken;
|
|
2204
2284
|
const effectiveAdminToken = () => options.adminToken ?? envAdminToken;
|
|
2285
|
+
let envRequireEphemeralWsToken;
|
|
2286
|
+
const effectiveRequireEphemeralWsToken = () => options.requireEphemeralWsToken ?? envRequireEphemeralWsToken ?? false;
|
|
2205
2287
|
const resolveAdminTokenFromEnv = (env) => {
|
|
2288
|
+
const record = env ?? {};
|
|
2289
|
+
if (envRequireEphemeralWsToken === void 0 && options.requireEphemeralWsToken === void 0) {
|
|
2290
|
+
const raw = record["LUNORA_REQUIRE_EPHEMERAL_WS_TOKEN"];
|
|
2291
|
+
if (typeof raw === "string" && raw.length > 0) {
|
|
2292
|
+
envRequireEphemeralWsToken = REQUIRE_EPHEMERAL_ENV_VALUES.has(raw.trim().toLowerCase());
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2206
2295
|
if (envAdminToken !== void 0 || options.adminToken !== void 0) {
|
|
2207
2296
|
return;
|
|
2208
2297
|
}
|
|
2209
|
-
const value =
|
|
2298
|
+
const value = record["LUNORA_ADMIN_TOKEN"];
|
|
2210
2299
|
if (typeof value === "string" && value.length > 0) {
|
|
2211
2300
|
envAdminToken = value;
|
|
2212
2301
|
}
|
|
@@ -2447,7 +2536,7 @@ const createWorker = (options) => {
|
|
|
2447
2536
|
return resolveShard(requireSchedulerNamespace(), options.schedulerInstanceName ?? "default");
|
|
2448
2537
|
};
|
|
2449
2538
|
const scheduledAdminRoutes = buildScheduledAdminRoutes({
|
|
2450
|
-
checkWsAdmin: (request) => requestIsAdmin(request) || checkAdminWsToken(request, effectiveAdminToken()),
|
|
2539
|
+
checkWsAdmin: async (request) => requestIsAdmin(request) || checkAdminWsToken(request, effectiveAdminToken(), effectiveRequireEphemeralWsToken()),
|
|
2451
2540
|
requireSchedulerNamespace,
|
|
2452
2541
|
resolveSchedulerStub,
|
|
2453
2542
|
schedulerInstanceName: options.schedulerInstanceName ?? "default"
|
|
@@ -2988,33 +3077,22 @@ const createWorker = (options) => {
|
|
|
2988
3077
|
const tables = options.backupTables;
|
|
2989
3078
|
let rows = 0;
|
|
2990
3079
|
let bytes = 0;
|
|
2991
|
-
|
|
2992
|
-
const
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
};
|
|
3001
|
-
try {
|
|
3002
|
-
await streamExportRows(options, coordinator, forwardedHeaders, tables, writeRow, shardDO);
|
|
3003
|
-
streamController.close();
|
|
3004
|
-
} catch (error) {
|
|
3005
|
-
streamError = error instanceof Error ? error : new Error(String(error));
|
|
3006
|
-
streamController.error(error);
|
|
3007
|
-
}
|
|
3008
|
-
}
|
|
3009
|
-
});
|
|
3080
|
+
const parts = [];
|
|
3081
|
+
const writeRow = (row) => {
|
|
3082
|
+
const line = `${JSON.stringify(row)}
|
|
3083
|
+
`;
|
|
3084
|
+
rows += 1;
|
|
3085
|
+
bytes += NDJSON_ENCODER.encode(line).byteLength;
|
|
3086
|
+
parts.push(line);
|
|
3087
|
+
};
|
|
3088
|
+
await streamExportRows(options, coordinator, forwardedHeaders, tables, writeRow, shardDO);
|
|
3010
3089
|
const prefix = options.backupPrefix ?? "backups/";
|
|
3011
3090
|
const timestamp = new Date(controller.scheduledTime).toISOString();
|
|
3012
3091
|
const fileKey = `${prefix}lunora-backup-${timestamp.replaceAll(/[.:]/gu, "-")}.ndjson`;
|
|
3013
3092
|
const manifestKey = `${fileKey}.manifest.json`;
|
|
3014
|
-
await store.put(fileKey,
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
}
|
|
3093
|
+
await store.put(fileKey, new Blob(parts, { type: "application/x-ndjson" }), {
|
|
3094
|
+
httpMetadata: { contentType: "application/x-ndjson" }
|
|
3095
|
+
});
|
|
3018
3096
|
const manifest = {
|
|
3019
3097
|
bytes,
|
|
3020
3098
|
createdAt: timestamp,
|
|
@@ -3100,6 +3178,24 @@ const createWorker = (options) => {
|
|
|
3100
3178
|
[RPC_BATCH_PATH]: (request, env, _url, context) => handleBatchRpc(request, env, context),
|
|
3101
3179
|
[SCHEDULER_DISPATCH_PATH]: (request, env) => handleSchedulerDispatch(request, env),
|
|
3102
3180
|
[CRON_JOBS_RUN_PATH]: (request, env) => handleRunCronJob(request, env),
|
|
3181
|
+
// Mint a short-lived HMAC-signed WS admin sub-token. Gated by the master
|
|
3182
|
+
// admin bearer (header) / `adminGate`; the studio then sends the minted
|
|
3183
|
+
// token — not the master credential — in the WS `?token=`
|
|
3184
|
+
// query string. Signed with the master token itself, so both isolates
|
|
3185
|
+
// verify statelessly and rotating `LUNORA_ADMIN_TOKEN` invalidates every
|
|
3186
|
+
// outstanding sub-token. `no-store` keeps the token out of caches.
|
|
3187
|
+
[ADMIN_WS_TOKEN_PATH]: async (request) => {
|
|
3188
|
+
if (request.method !== "POST") {
|
|
3189
|
+
throw new LunoraError("ws-token endpoint requires POST", { code: "METHOD_NOT_ALLOWED", status: 405 });
|
|
3190
|
+
}
|
|
3191
|
+
assertAdminAuthorized(request);
|
|
3192
|
+
const signingSecret = effectiveAdminToken();
|
|
3193
|
+
if (signingSecret === void 0) {
|
|
3194
|
+
throw new LunoraError("ws-token minting requires a configured admin token", { code: "ADMIN_TOKEN_NOT_CONFIGURED", status: 400 });
|
|
3195
|
+
}
|
|
3196
|
+
const minted = await mintWsAdminToken(signingSecret);
|
|
3197
|
+
return Response.json(minted, { headers: { "cache-control": "no-store" } });
|
|
3198
|
+
},
|
|
3103
3199
|
// Extracted handler clusters built above, merged in (mirroring the auth
|
|
3104
3200
|
// plane below): orchestration (migrate / rank / rankpage / shard-traffic /
|
|
3105
3201
|
// pitr), data-movement (export / import / sync / connector-sync / apply),
|
|
@@ -3117,8 +3213,7 @@ const createWorker = (options) => {
|
|
|
3117
3213
|
// `AuthAdmin` op, dispatched by the descriptor table in `./auth-admin-routes`.
|
|
3118
3214
|
...buildAuthAdminRoutes({
|
|
3119
3215
|
assertAdmin: assertAdminAuthorized,
|
|
3120
|
-
|
|
3121
|
-
getAuthAdmin: () => options.authAdmin ?? options.authIntrospector,
|
|
3216
|
+
getAuthAdmin: () => options.authAdmin,
|
|
3122
3217
|
parsePaging,
|
|
3123
3218
|
queryParameter,
|
|
3124
3219
|
readJsonBody: readJsonBodyWithLimit
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/runtime",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.29",
|
|
4
4
|
"description": "Lunora Worker runtime: the RPC router, shard resolver, and query coordinator",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
49
|
+
"@lunora/errors": "1.0.0-alpha.6"
|
|
50
50
|
},
|
|
51
51
|
"engines": {
|
|
52
52
|
"node": "^22.15.0 || >=24.11.0"
|