@lunora/runtime 1.0.0-alpha.2 → 1.0.0-alpha.21
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/LICENSE.md +6 -0
- package/README.md +17 -0
- package/__assets__/package-og.svg +1 -1
- package/dist/index.d.mts +561 -80
- package/dist/index.d.ts +561 -80
- package/dist/index.mjs +9 -7
- package/dist/packem_shared/{DEFAULT_REGISTRY_CACHE_TTL_MS-BpCwo_mo.mjs → DEFAULT_REGISTRY_CACHE_TTL_MS-B3pA7aXp.mjs} +9 -5
- package/dist/packem_shared/LunoraError-Bpb9EFJ3.mjs +22 -0
- package/dist/packem_shared/NOOP_EXECUTION_CONTEXT-CCTu0Bf1.mjs +8 -0
- package/dist/packem_shared/applyJurisdiction-BkZtTkct.mjs +20 -0
- package/dist/packem_shared/composeIdentityResolvers-XGjO7V1J.mjs +55 -0
- package/dist/packem_shared/{composeWorker-BmfKl1ei.mjs → composeWorker-Dpw9d1s5.mjs} +707 -91
- package/dist/packem_shared/{createCrossShardRelationCapabilities-C0KOf7er.mjs → createCrossShardRelationCapabilities-CbcWjkAn.mjs} +4 -2
- package/dist/packem_shared/{createQueryCoordinator-DbxC7iUz.mjs → createQueryCoordinator-DNCJzOZE.mjs} +12 -3
- package/dist/packem_shared/{decorateResponse-DbISh_Wi.mjs → decorateResponse-CsZc49QC.mjs} +42 -9
- package/package.json +5 -2
- package/dist/packem_shared/LunoraError-CL0aOtpo.mjs +0 -46
- package/dist/packem_shared/resolveShard-DDkzWtrU.mjs +0 -9
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { LunoraError } from './LunoraError-Bpb9EFJ3.mjs';
|
|
2
|
+
|
|
1
3
|
const RPC_ENDPOINT = "/_lunora/rpc";
|
|
2
4
|
const buildIdentityHeaders = (options) => {
|
|
3
5
|
const headers = { "content-type": "application/json" };
|
|
@@ -19,12 +21,12 @@ const fanOutRelation = async (options, body, label) => {
|
|
|
19
21
|
})
|
|
20
22
|
);
|
|
21
23
|
if (!response.ok) {
|
|
22
|
-
throw new
|
|
24
|
+
throw new LunoraError(`cross-shard relation ${label} failed: worker returned ${String(response.status)}`);
|
|
23
25
|
}
|
|
24
26
|
const result = await response.json();
|
|
25
27
|
if (typeof result.failed === "number" && result.failed > 0) {
|
|
26
28
|
const reached = (typeof result.ok === "number" ? result.ok : 0) + result.failed;
|
|
27
|
-
throw new
|
|
29
|
+
throw new LunoraError(
|
|
28
30
|
`cross-shard relation ${label} failed on ${String(result.failed)} of ${String(reached)} shard(s) — refusing to return a partial result`
|
|
29
31
|
);
|
|
30
32
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { LunoraError } from './LunoraError-
|
|
2
|
-
import { resolveShard } from './
|
|
1
|
+
import { LunoraError } from './LunoraError-Bpb9EFJ3.mjs';
|
|
2
|
+
import { resolveShard } from './applyJurisdiction-BkZtTkct.mjs';
|
|
3
3
|
|
|
4
4
|
const createStaticShardRegistry = (table_to_keys) => {
|
|
5
5
|
return {
|
|
@@ -547,7 +547,16 @@ const mergeTopK = (values, strategy) => {
|
|
|
547
547
|
}
|
|
548
548
|
}
|
|
549
549
|
const direction = strategy.direction ?? "desc";
|
|
550
|
-
|
|
550
|
+
const ascending = (x, y) => {
|
|
551
|
+
if (x < y) {
|
|
552
|
+
return -1;
|
|
553
|
+
}
|
|
554
|
+
if (x > y) {
|
|
555
|
+
return 1;
|
|
556
|
+
}
|
|
557
|
+
return 0;
|
|
558
|
+
};
|
|
559
|
+
collected.sort((a, b) => direction === "asc" ? ascending(a.score, b.score) : ascending(b.score, a.score));
|
|
551
560
|
return collected.slice(0, strategy.k).map((entry) => entry.row);
|
|
552
561
|
};
|
|
553
562
|
const mergeShardResults = (values, strategy) => {
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
+
import { LunoraError } from './LunoraError-Bpb9EFJ3.mjs';
|
|
2
|
+
|
|
1
3
|
const DEFAULT_CSP = "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'";
|
|
4
|
+
const htmlCspFor = (frameOptions) => {
|
|
5
|
+
const parts = ["base-uri 'none'", "object-src 'none'"];
|
|
6
|
+
if (frameOptions === "DENY") {
|
|
7
|
+
parts.push("frame-ancestors 'none'");
|
|
8
|
+
} else if (frameOptions === "SAMEORIGIN") {
|
|
9
|
+
parts.push("frame-ancestors 'self'");
|
|
10
|
+
}
|
|
11
|
+
return parts.join("; ");
|
|
12
|
+
};
|
|
2
13
|
const DEFAULT_PERMISSIONS_POLICY = "accelerometer=(), autoplay=(), camera=(), display-capture=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()";
|
|
3
14
|
const DEFAULT_CORS_HEADERS = ["Authorization", "Content-Type", "X-D1-Bookmark", "X-Lunora-Mutation-Id"];
|
|
4
15
|
const DEFAULT_CORS_METHODS = ["DELETE", "GET", "HEAD", "PATCH", "POST", "PUT"];
|
|
@@ -13,14 +24,14 @@ const resolveHstsHeader = (hsts) => {
|
|
|
13
24
|
const includeSubDomains = config.includeSubDomains ?? true;
|
|
14
25
|
return `max-age=${String(maxAge)}${includeSubDomains ? "; includeSubDomains" : ""}${config.preload ? "; preload" : ""}`;
|
|
15
26
|
};
|
|
16
|
-
const resolveCspHeader = (csp) => {
|
|
27
|
+
const resolveCspHeader = (csp, htmlDefault) => {
|
|
17
28
|
if (csp === false) {
|
|
18
29
|
return void 0;
|
|
19
30
|
}
|
|
20
31
|
if (typeof csp === "string") {
|
|
21
|
-
return {
|
|
32
|
+
return { htmlValue: csp, value: csp };
|
|
22
33
|
}
|
|
23
|
-
return {
|
|
34
|
+
return { htmlValue: htmlDefault, value: DEFAULT_CSP };
|
|
24
35
|
};
|
|
25
36
|
const resolveHeaders = (input) => {
|
|
26
37
|
if (input === false) {
|
|
@@ -35,11 +46,12 @@ const resolveHeaders = (input) => {
|
|
|
35
46
|
};
|
|
36
47
|
}
|
|
37
48
|
const options = input === void 0 || input === true ? {} : input;
|
|
49
|
+
const frameOptions = options.frameOptions === false ? void 0 : options.frameOptions ?? "SAMEORIGIN";
|
|
38
50
|
return {
|
|
39
51
|
coop: "same-origin",
|
|
40
|
-
csp: resolveCspHeader(options.csp),
|
|
52
|
+
csp: resolveCspHeader(options.csp, htmlCspFor(frameOptions)),
|
|
41
53
|
enabled: true,
|
|
42
|
-
frameOptions
|
|
54
|
+
frameOptions,
|
|
43
55
|
hsts: resolveHstsHeader(options.hsts),
|
|
44
56
|
permissionsPolicy: options.permissionsPolicy === false ? void 0 : options.permissionsPolicy ?? DEFAULT_PERMISSIONS_POLICY,
|
|
45
57
|
referrerPolicy: options.referrerPolicy === false ? void 0 : options.referrerPolicy ?? "strict-origin-when-cross-origin"
|
|
@@ -65,10 +77,14 @@ const resolveCors = (input) => {
|
|
|
65
77
|
if (typeof origins === "function") {
|
|
66
78
|
isAllowed = origins;
|
|
67
79
|
isExplicitlyAllowed = origins;
|
|
80
|
+
const credentialsNote = allowCredentials ? " AND reflects matching origins with credentials (`allowCredentials: true`)" : "";
|
|
81
|
+
console.warn(
|
|
82
|
+
`@lunora/runtime: security.cors uses a custom \`allowedOrigins\` predicate. It is trusted by the CSRF and WebSocket origin checks${credentialsNote} — ensure it matches ONLY trusted origins by exact equality; an over-broad predicate (e.g. \`() => true\`, or \`endsWith\`/\`includes\` checks) defeats the allowlist.`
|
|
83
|
+
);
|
|
68
84
|
} else {
|
|
69
85
|
const originsList = origins;
|
|
70
86
|
if (originsList.includes("*") && allowCredentials) {
|
|
71
|
-
throw new
|
|
87
|
+
throw new LunoraError(
|
|
72
88
|
'@lunora/runtime: security.cors cannot combine a wildcard origin ("*") with allowCredentials: true — browsers reject it and it defeats the allowlist.'
|
|
73
89
|
);
|
|
74
90
|
}
|
|
@@ -152,6 +168,20 @@ const enforceOrigin = (request, resolved) => {
|
|
|
152
168
|
{ headers: { "content-type": "application/json" }, status: 403 }
|
|
153
169
|
);
|
|
154
170
|
};
|
|
171
|
+
const enforceWebSocketOrigin = (request, resolved) => {
|
|
172
|
+
if (!resolved.csrf.enabled || !request.headers.get("cookie")) {
|
|
173
|
+
return void 0;
|
|
174
|
+
}
|
|
175
|
+
const selfOrigin = new URL(request.url).origin;
|
|
176
|
+
const source = originOf(request.headers.get("origin"));
|
|
177
|
+
if (source !== void 0 && isTrustedOrigin(source, selfOrigin, resolved)) {
|
|
178
|
+
return void 0;
|
|
179
|
+
}
|
|
180
|
+
return Response.json(
|
|
181
|
+
{ error: { code: "FORBIDDEN_ORIGIN", message: "cross-origin websocket upgrade rejected" } },
|
|
182
|
+
{ headers: { "content-type": "application/json" }, status: 403 }
|
|
183
|
+
);
|
|
184
|
+
};
|
|
155
185
|
const corsResponseHeaders = (origin, cors) => {
|
|
156
186
|
const headers = new Headers();
|
|
157
187
|
headers.set("access-control-allow-origin", origin);
|
|
@@ -199,8 +229,11 @@ const applyBaselineHeaders = (headers, request, response, config) => {
|
|
|
199
229
|
if (config.coop !== void 0) {
|
|
200
230
|
setIfAbsent(headers, "cross-origin-opener-policy", config.coop);
|
|
201
231
|
}
|
|
202
|
-
if (config.csp !== void 0
|
|
203
|
-
|
|
232
|
+
if (config.csp !== void 0) {
|
|
233
|
+
const cspValue = isHtmlResponse(response) ? config.csp.htmlValue : config.csp.value;
|
|
234
|
+
if (cspValue !== void 0) {
|
|
235
|
+
setIfAbsent(headers, "content-security-policy", cspValue);
|
|
236
|
+
}
|
|
204
237
|
}
|
|
205
238
|
};
|
|
206
239
|
const applyCorsHeaders = (headers, request, cors) => {
|
|
@@ -230,4 +263,4 @@ const decorateResponse = (response, request, resolved) => {
|
|
|
230
263
|
return new Response(response.body, { headers, status: response.status, statusText: response.statusText });
|
|
231
264
|
};
|
|
232
265
|
|
|
233
|
-
export { decorateResponse, enforceOrigin, handleCorsPreflight, resolveSecurity };
|
|
266
|
+
export { decorateResponse, enforceOrigin, enforceWebSocketOrigin, handleCorsPreflight, resolveSecurity };
|
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.21",
|
|
4
4
|
"description": "Lunora Worker runtime: the RPC router, shard resolver, and query coordinator",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"directory": "packages/runtime"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
|
-
"dist",
|
|
28
|
+
"./dist",
|
|
29
29
|
"README.md",
|
|
30
30
|
"LICENSE.md",
|
|
31
31
|
"__assets__"
|
|
@@ -45,6 +45,9 @@
|
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@lunora/errors": "1.0.0-alpha.2"
|
|
50
|
+
},
|
|
48
51
|
"engines": {
|
|
49
52
|
"node": "^22.15.0 || >=24.11.0"
|
|
50
53
|
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
class LunoraError extends Error {
|
|
2
|
-
code;
|
|
3
|
-
status;
|
|
4
|
-
constructor(message, options) {
|
|
5
|
-
super(message, { cause: options?.cause });
|
|
6
|
-
this.name = "LunoraError";
|
|
7
|
-
this.code = options?.code ?? "INTERNAL";
|
|
8
|
-
this.status = options?.status ?? 500;
|
|
9
|
-
}
|
|
10
|
-
toResponse() {
|
|
11
|
-
const body = { error: { code: this.code, message: this.message } };
|
|
12
|
-
return Response.json(body, {
|
|
13
|
-
headers: { "content-type": "application/json" },
|
|
14
|
-
status: this.status
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
const hasErrorShape = (error, name) => {
|
|
19
|
-
if (!error || typeof error !== "object") {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
const candidate = error;
|
|
23
|
-
return candidate.name === name && typeof candidate.code === "string" && typeof candidate.status === "number" && typeof candidate.message === "string";
|
|
24
|
-
};
|
|
25
|
-
const isStructuralConflictError = (error) => hasErrorShape(error, "ConflictError");
|
|
26
|
-
const isStructuralLunoraError = (error) => hasErrorShape(error, "LunoraError");
|
|
27
|
-
const toErrorResponse = (error) => {
|
|
28
|
-
if (error instanceof LunoraError) {
|
|
29
|
-
return error.toResponse();
|
|
30
|
-
}
|
|
31
|
-
if (isStructuralLunoraError(error) || isStructuralConflictError(error)) {
|
|
32
|
-
const body2 = { error: { code: error.code, message: error.message } };
|
|
33
|
-
return Response.json(body2, {
|
|
34
|
-
headers: { "content-type": "application/json" },
|
|
35
|
-
status: error.status
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
console.error("[lunora] unhandled error:", error);
|
|
39
|
-
const body = { error: { code: "INTERNAL", message: "Internal error" } };
|
|
40
|
-
return Response.json(body, {
|
|
41
|
-
headers: { "content-type": "application/json" },
|
|
42
|
-
status: 500
|
|
43
|
-
});
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export { LunoraError, isStructuralConflictError, isStructuralLunoraError, toErrorResponse };
|