theokit 0.48.7 → 0.48.8
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/{actions-virtual-module-GB5FWUPS.js → actions-virtual-module-IICWQK43.js} +2 -3
- package/dist/{actions-virtual-module-GB5FWUPS.js.map → actions-virtual-module-IICWQK43.js.map} +1 -1
- package/dist/{agent-47XWDGIR.js → agent-WZLCEXD4.js} +2 -2
- package/dist/{app-typed-client-5QDNHANF.js → app-typed-client-4GDSR5T3.js} +2 -3
- package/dist/{app-typed-client-5QDNHANF.js.map → app-typed-client-4GDSR5T3.js.map} +1 -1
- package/dist/{build-4CAGKYYT.js → build-5TKAROT4.js} +2 -2
- package/dist/{chunk-H6YBKIUN.js → chunk-ABUVJU3P.js} +574 -161
- package/dist/chunk-ABUVJU3P.js.map +1 -0
- package/dist/{chunk-JZ25BBLP.js → chunk-HENNMZUH.js} +2 -2
- package/dist/{chunk-3GUXSNHY.js → chunk-RPIL7VRZ.js} +86 -3
- package/dist/chunk-RPIL7VRZ.js.map +1 -0
- package/dist/{chunk-AXQF3I4L.js → chunk-ZGNA66JK.js} +39 -122
- package/dist/chunk-ZGNA66JK.js.map +1 -0
- package/dist/cli/index.js +5 -5
- package/dist/{dev-F67X4F5E.js → dev-XGAJQ7DH.js} +4 -5
- package/dist/{dev-F67X4F5E.js.map → dev-XGAJQ7DH.js.map} +1 -1
- package/dist/{internal-api-ICU7ITJD.js → internal-api-AKYIRMT2.js} +7 -9
- package/dist/{mcp-AJHKPAU4.js → mcp-D7K6NJMW.js} +2 -2
- package/dist/{start-IITIEJTY.js → start-YR4TQQP2.js} +10 -8
- package/dist/start-YR4TQQP2.js.map +1 -0
- package/dist/{vite-plugin-MXHY6MSV.js → vite-plugin-PUUCR6LR.js} +4 -5
- package/package.json +1 -1
- package/dist/chunk-3GUXSNHY.js.map +0 -1
- package/dist/chunk-7CQ6DEWZ.js +0 -421
- package/dist/chunk-7CQ6DEWZ.js.map +0 -1
- package/dist/chunk-AXQF3I4L.js.map +0 -1
- package/dist/chunk-H6YBKIUN.js.map +0 -1
- package/dist/start-IITIEJTY.js.map +0 -1
- /package/dist/{agent-47XWDGIR.js.map → agent-WZLCEXD4.js.map} +0 -0
- /package/dist/{build-4CAGKYYT.js.map → build-5TKAROT4.js.map} +0 -0
- /package/dist/{chunk-JZ25BBLP.js.map → chunk-HENNMZUH.js.map} +0 -0
- /package/dist/{internal-api-ICU7ITJD.js.map → internal-api-AKYIRMT2.js.map} +0 -0
- /package/dist/{mcp-AJHKPAU4.js.map → mcp-D7K6NJMW.js.map} +0 -0
- /package/dist/{vite-plugin-MXHY6MSV.js.map → vite-plugin-PUUCR6LR.js.map} +0 -0
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
enforceCsrf,
|
|
9
9
|
incomingMessageToHandlerRequest,
|
|
10
10
|
parseRequestBody,
|
|
11
|
+
safeAudit,
|
|
11
12
|
sendError,
|
|
12
13
|
sendJson,
|
|
13
14
|
validateCsrfRequest,
|
|
@@ -18,6 +19,166 @@ import {
|
|
|
18
19
|
serverErrorToEnvelope
|
|
19
20
|
} from "./chunk-VQIUTCK2.js";
|
|
20
21
|
|
|
22
|
+
// src/core/contracts/action-protocol.ts
|
|
23
|
+
var CODE_TO_STATUS = {
|
|
24
|
+
VALIDATION_ERROR: 422,
|
|
25
|
+
BAD_REQUEST: 400,
|
|
26
|
+
UNAUTHORIZED: 401,
|
|
27
|
+
FORBIDDEN: 403,
|
|
28
|
+
NOT_FOUND: 404,
|
|
29
|
+
METHOD_NOT_ALLOWED: 405,
|
|
30
|
+
CONFLICT: 409,
|
|
31
|
+
CONTENT_TOO_LARGE: 413,
|
|
32
|
+
PAYLOAD_TOO_LARGE: 413,
|
|
33
|
+
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
34
|
+
TOO_MANY_REQUESTS: 429,
|
|
35
|
+
INTERNAL_SERVER_ERROR: 500
|
|
36
|
+
};
|
|
37
|
+
var STATUS_TO_CODE = {
|
|
38
|
+
400: "BAD_REQUEST",
|
|
39
|
+
401: "UNAUTHORIZED",
|
|
40
|
+
403: "FORBIDDEN",
|
|
41
|
+
404: "NOT_FOUND",
|
|
42
|
+
405: "METHOD_NOT_ALLOWED",
|
|
43
|
+
409: "CONFLICT",
|
|
44
|
+
413: "PAYLOAD_TOO_LARGE",
|
|
45
|
+
415: "UNSUPPORTED_MEDIA_TYPE",
|
|
46
|
+
422: "VALIDATION_ERROR",
|
|
47
|
+
429: "TOO_MANY_REQUESTS",
|
|
48
|
+
500: "INTERNAL_SERVER_ERROR"
|
|
49
|
+
};
|
|
50
|
+
var ActionError = class _ActionError extends Error {
|
|
51
|
+
// Discriminator widened to the full union so subclasses can narrow to their
|
|
52
|
+
// specific literal (TypeScript would otherwise reject the override). Concrete
|
|
53
|
+
// values are still always exact literals at runtime.
|
|
54
|
+
type = "TheoActionError";
|
|
55
|
+
code;
|
|
56
|
+
status;
|
|
57
|
+
constructor(params) {
|
|
58
|
+
super(params.message ?? params.code);
|
|
59
|
+
this.code = params.code;
|
|
60
|
+
this.status = _ActionError.codeToStatus(params.code);
|
|
61
|
+
if (params.stack) {
|
|
62
|
+
this.stack = params.stack;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* G5 T2.4 — canonical envelope view of the action error. Maps the G3
|
|
67
|
+
* ActionErrorCode to a canonical TheoErrorCode (VALIDATION_ERROR ↔
|
|
68
|
+
* UNPROCESSABLE_ENTITY, CONTENT_TOO_LARGE ↔ PAYLOAD_TOO_LARGE) so consumer
|
|
69
|
+
* UI / SDK code can switch on the unified envelope.
|
|
70
|
+
*
|
|
71
|
+
* Subclasses override to populate `ext` (see `ActionInputError.envelope`).
|
|
72
|
+
*/
|
|
73
|
+
get envelope() {
|
|
74
|
+
return {
|
|
75
|
+
code: _ActionError.toTheoErrorCode(this.code),
|
|
76
|
+
message: this.message
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Translate G3 ActionErrorCode → canonical TheoErrorCode (blueprint
|
|
81
|
+
* Recommendations § "G3 ActionError becomes inaugural envelope user").
|
|
82
|
+
*/
|
|
83
|
+
static toTheoErrorCode(code) {
|
|
84
|
+
if (code === "VALIDATION_ERROR") return "UNPROCESSABLE_ENTITY";
|
|
85
|
+
if (code === "CONTENT_TOO_LARGE") return "PAYLOAD_TOO_LARGE";
|
|
86
|
+
return code;
|
|
87
|
+
}
|
|
88
|
+
static codeToStatus(code) {
|
|
89
|
+
return CODE_TO_STATUS[code];
|
|
90
|
+
}
|
|
91
|
+
static statusToCode(status) {
|
|
92
|
+
return STATUS_TO_CODE[status] ?? "INTERNAL_SERVER_ERROR";
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Parse a serialized error JSON back into the typed class hierarchy.
|
|
96
|
+
* Distinguishes `TheoActionInputError` (with `issues` array) from
|
|
97
|
+
* `TheoActionError` via the `type` discriminator. Falls back to
|
|
98
|
+
* `INTERNAL_SERVER_ERROR` for malformed bodies (non-object, missing
|
|
99
|
+
* `type`, unknown `code`).
|
|
100
|
+
*/
|
|
101
|
+
static fromJson(body) {
|
|
102
|
+
if (typeof body !== "object" || body === null) {
|
|
103
|
+
return new _ActionError({ code: "INTERNAL_SERVER_ERROR" });
|
|
104
|
+
}
|
|
105
|
+
const obj = body;
|
|
106
|
+
if (obj.type === "TheoActionInputError" && Array.isArray(obj.issues)) {
|
|
107
|
+
return new ActionInputError(obj.issues);
|
|
108
|
+
}
|
|
109
|
+
if (obj.type === "TheoActionError" && typeof obj.code === "string" && obj.code in CODE_TO_STATUS) {
|
|
110
|
+
return new _ActionError({
|
|
111
|
+
code: obj.code,
|
|
112
|
+
message: typeof obj.message === "string" ? obj.message : void 0
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return new _ActionError({ code: "INTERNAL_SERVER_ERROR" });
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var ActionInputError = class extends ActionError {
|
|
119
|
+
type = "TheoActionInputError";
|
|
120
|
+
issues;
|
|
121
|
+
fields;
|
|
122
|
+
constructor(rawIssues) {
|
|
123
|
+
super({ code: "VALIDATION_ERROR", message: "Validation failed" });
|
|
124
|
+
this.issues = extractUniversalIssues(rawIssues);
|
|
125
|
+
this.fields = buildFieldsMap(this.issues);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* G5 T2.4 — envelope view with ValidationFieldsExt populated from .fields.
|
|
129
|
+
* UI consumers can `switch (env.code)` on `UNPROCESSABLE_ENTITY` and read
|
|
130
|
+
* `(env.ext as ValidationFieldsExt).fields` for field-level rendering.
|
|
131
|
+
*/
|
|
132
|
+
get envelope() {
|
|
133
|
+
return {
|
|
134
|
+
code: "UNPROCESSABLE_ENTITY",
|
|
135
|
+
message: this.message,
|
|
136
|
+
ext: { fields: this.fields }
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
function buildFieldsMap(issues) {
|
|
141
|
+
const fields = {};
|
|
142
|
+
const seen = /* @__PURE__ */ new Set();
|
|
143
|
+
for (const issue of issues) {
|
|
144
|
+
const key = issue.path.length === 0 ? "" : issue.path.join(".");
|
|
145
|
+
const dedupeKey = `${key}\0${issue.message}`;
|
|
146
|
+
if (seen.has(dedupeKey)) continue;
|
|
147
|
+
seen.add(dedupeKey);
|
|
148
|
+
const bucket = fields[key] ?? [];
|
|
149
|
+
bucket.push(issue.message);
|
|
150
|
+
fields[key] = bucket;
|
|
151
|
+
}
|
|
152
|
+
return fields;
|
|
153
|
+
}
|
|
154
|
+
function extractUniversalIssues(raw) {
|
|
155
|
+
if (!Array.isArray(raw)) return [];
|
|
156
|
+
const out = [];
|
|
157
|
+
for (const entry of raw) {
|
|
158
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
159
|
+
const obj = entry;
|
|
160
|
+
if (!Array.isArray(obj.path)) continue;
|
|
161
|
+
if (typeof obj.message !== "string") continue;
|
|
162
|
+
const path = [];
|
|
163
|
+
let pathValid = true;
|
|
164
|
+
for (const seg of obj.path) {
|
|
165
|
+
if (typeof seg === "string" || typeof seg === "number") {
|
|
166
|
+
path.push(seg);
|
|
167
|
+
} else {
|
|
168
|
+
pathValid = false;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (!pathValid) continue;
|
|
173
|
+
out.push({
|
|
174
|
+
path,
|
|
175
|
+
message: obj.message,
|
|
176
|
+
code: typeof obj.code === "string" ? obj.code : void 0
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
|
|
21
182
|
// src/core/contracts/auth-error-guard.ts
|
|
22
183
|
function isAuthRequiredError(err) {
|
|
23
184
|
if (err == null || typeof err !== "object") return false;
|
|
@@ -514,166 +675,6 @@ async function executeRoute(ctx) {
|
|
|
514
675
|
}
|
|
515
676
|
}
|
|
516
677
|
|
|
517
|
-
// src/core/contracts/action-protocol.ts
|
|
518
|
-
var CODE_TO_STATUS = {
|
|
519
|
-
VALIDATION_ERROR: 422,
|
|
520
|
-
BAD_REQUEST: 400,
|
|
521
|
-
UNAUTHORIZED: 401,
|
|
522
|
-
FORBIDDEN: 403,
|
|
523
|
-
NOT_FOUND: 404,
|
|
524
|
-
METHOD_NOT_ALLOWED: 405,
|
|
525
|
-
CONFLICT: 409,
|
|
526
|
-
CONTENT_TOO_LARGE: 413,
|
|
527
|
-
PAYLOAD_TOO_LARGE: 413,
|
|
528
|
-
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
529
|
-
TOO_MANY_REQUESTS: 429,
|
|
530
|
-
INTERNAL_SERVER_ERROR: 500
|
|
531
|
-
};
|
|
532
|
-
var STATUS_TO_CODE = {
|
|
533
|
-
400: "BAD_REQUEST",
|
|
534
|
-
401: "UNAUTHORIZED",
|
|
535
|
-
403: "FORBIDDEN",
|
|
536
|
-
404: "NOT_FOUND",
|
|
537
|
-
405: "METHOD_NOT_ALLOWED",
|
|
538
|
-
409: "CONFLICT",
|
|
539
|
-
413: "PAYLOAD_TOO_LARGE",
|
|
540
|
-
415: "UNSUPPORTED_MEDIA_TYPE",
|
|
541
|
-
422: "VALIDATION_ERROR",
|
|
542
|
-
429: "TOO_MANY_REQUESTS",
|
|
543
|
-
500: "INTERNAL_SERVER_ERROR"
|
|
544
|
-
};
|
|
545
|
-
var ActionError = class _ActionError extends Error {
|
|
546
|
-
// Discriminator widened to the full union so subclasses can narrow to their
|
|
547
|
-
// specific literal (TypeScript would otherwise reject the override). Concrete
|
|
548
|
-
// values are still always exact literals at runtime.
|
|
549
|
-
type = "TheoActionError";
|
|
550
|
-
code;
|
|
551
|
-
status;
|
|
552
|
-
constructor(params) {
|
|
553
|
-
super(params.message ?? params.code);
|
|
554
|
-
this.code = params.code;
|
|
555
|
-
this.status = _ActionError.codeToStatus(params.code);
|
|
556
|
-
if (params.stack) {
|
|
557
|
-
this.stack = params.stack;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
/**
|
|
561
|
-
* G5 T2.4 — canonical envelope view of the action error. Maps the G3
|
|
562
|
-
* ActionErrorCode to a canonical TheoErrorCode (VALIDATION_ERROR ↔
|
|
563
|
-
* UNPROCESSABLE_ENTITY, CONTENT_TOO_LARGE ↔ PAYLOAD_TOO_LARGE) so consumer
|
|
564
|
-
* UI / SDK code can switch on the unified envelope.
|
|
565
|
-
*
|
|
566
|
-
* Subclasses override to populate `ext` (see `ActionInputError.envelope`).
|
|
567
|
-
*/
|
|
568
|
-
get envelope() {
|
|
569
|
-
return {
|
|
570
|
-
code: _ActionError.toTheoErrorCode(this.code),
|
|
571
|
-
message: this.message
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
/**
|
|
575
|
-
* Translate G3 ActionErrorCode → canonical TheoErrorCode (blueprint
|
|
576
|
-
* Recommendations § "G3 ActionError becomes inaugural envelope user").
|
|
577
|
-
*/
|
|
578
|
-
static toTheoErrorCode(code) {
|
|
579
|
-
if (code === "VALIDATION_ERROR") return "UNPROCESSABLE_ENTITY";
|
|
580
|
-
if (code === "CONTENT_TOO_LARGE") return "PAYLOAD_TOO_LARGE";
|
|
581
|
-
return code;
|
|
582
|
-
}
|
|
583
|
-
static codeToStatus(code) {
|
|
584
|
-
return CODE_TO_STATUS[code];
|
|
585
|
-
}
|
|
586
|
-
static statusToCode(status) {
|
|
587
|
-
return STATUS_TO_CODE[status] ?? "INTERNAL_SERVER_ERROR";
|
|
588
|
-
}
|
|
589
|
-
/**
|
|
590
|
-
* Parse a serialized error JSON back into the typed class hierarchy.
|
|
591
|
-
* Distinguishes `TheoActionInputError` (with `issues` array) from
|
|
592
|
-
* `TheoActionError` via the `type` discriminator. Falls back to
|
|
593
|
-
* `INTERNAL_SERVER_ERROR` for malformed bodies (non-object, missing
|
|
594
|
-
* `type`, unknown `code`).
|
|
595
|
-
*/
|
|
596
|
-
static fromJson(body) {
|
|
597
|
-
if (typeof body !== "object" || body === null) {
|
|
598
|
-
return new _ActionError({ code: "INTERNAL_SERVER_ERROR" });
|
|
599
|
-
}
|
|
600
|
-
const obj = body;
|
|
601
|
-
if (obj.type === "TheoActionInputError" && Array.isArray(obj.issues)) {
|
|
602
|
-
return new ActionInputError(obj.issues);
|
|
603
|
-
}
|
|
604
|
-
if (obj.type === "TheoActionError" && typeof obj.code === "string" && obj.code in CODE_TO_STATUS) {
|
|
605
|
-
return new _ActionError({
|
|
606
|
-
code: obj.code,
|
|
607
|
-
message: typeof obj.message === "string" ? obj.message : void 0
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
return new _ActionError({ code: "INTERNAL_SERVER_ERROR" });
|
|
611
|
-
}
|
|
612
|
-
};
|
|
613
|
-
var ActionInputError = class extends ActionError {
|
|
614
|
-
type = "TheoActionInputError";
|
|
615
|
-
issues;
|
|
616
|
-
fields;
|
|
617
|
-
constructor(rawIssues) {
|
|
618
|
-
super({ code: "VALIDATION_ERROR", message: "Validation failed" });
|
|
619
|
-
this.issues = extractUniversalIssues(rawIssues);
|
|
620
|
-
this.fields = buildFieldsMap(this.issues);
|
|
621
|
-
}
|
|
622
|
-
/**
|
|
623
|
-
* G5 T2.4 — envelope view with ValidationFieldsExt populated from .fields.
|
|
624
|
-
* UI consumers can `switch (env.code)` on `UNPROCESSABLE_ENTITY` and read
|
|
625
|
-
* `(env.ext as ValidationFieldsExt).fields` for field-level rendering.
|
|
626
|
-
*/
|
|
627
|
-
get envelope() {
|
|
628
|
-
return {
|
|
629
|
-
code: "UNPROCESSABLE_ENTITY",
|
|
630
|
-
message: this.message,
|
|
631
|
-
ext: { fields: this.fields }
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
};
|
|
635
|
-
function buildFieldsMap(issues) {
|
|
636
|
-
const fields = {};
|
|
637
|
-
const seen = /* @__PURE__ */ new Set();
|
|
638
|
-
for (const issue of issues) {
|
|
639
|
-
const key = issue.path.length === 0 ? "" : issue.path.join(".");
|
|
640
|
-
const dedupeKey = `${key}\0${issue.message}`;
|
|
641
|
-
if (seen.has(dedupeKey)) continue;
|
|
642
|
-
seen.add(dedupeKey);
|
|
643
|
-
const bucket = fields[key] ?? [];
|
|
644
|
-
bucket.push(issue.message);
|
|
645
|
-
fields[key] = bucket;
|
|
646
|
-
}
|
|
647
|
-
return fields;
|
|
648
|
-
}
|
|
649
|
-
function extractUniversalIssues(raw) {
|
|
650
|
-
if (!Array.isArray(raw)) return [];
|
|
651
|
-
const out = [];
|
|
652
|
-
for (const entry of raw) {
|
|
653
|
-
if (typeof entry !== "object" || entry === null) continue;
|
|
654
|
-
const obj = entry;
|
|
655
|
-
if (!Array.isArray(obj.path)) continue;
|
|
656
|
-
if (typeof obj.message !== "string") continue;
|
|
657
|
-
const path = [];
|
|
658
|
-
let pathValid = true;
|
|
659
|
-
for (const seg of obj.path) {
|
|
660
|
-
if (typeof seg === "string" || typeof seg === "number") {
|
|
661
|
-
path.push(seg);
|
|
662
|
-
} else {
|
|
663
|
-
pathValid = false;
|
|
664
|
-
break;
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
if (!pathValid) continue;
|
|
668
|
-
out.push({
|
|
669
|
-
path,
|
|
670
|
-
message: obj.message,
|
|
671
|
-
code: typeof obj.code === "string" ? obj.code : void 0
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
return out;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
678
|
// src/server/http/form-data-to-object.ts
|
|
678
679
|
import { z } from "zod";
|
|
679
680
|
function formDataToObject(formData, schema, prefix = "") {
|
|
@@ -1183,6 +1184,188 @@ async function handleActionError(err, c) {
|
|
|
1183
1184
|
});
|
|
1184
1185
|
}
|
|
1185
1186
|
|
|
1187
|
+
// src/server/http/batch-handler.ts
|
|
1188
|
+
import { z as z2 } from "zod";
|
|
1189
|
+
var STRIPPED_HEADERS = [
|
|
1190
|
+
"authorization",
|
|
1191
|
+
"cookie",
|
|
1192
|
+
"x-forwarded-for",
|
|
1193
|
+
"x-forwarded-host",
|
|
1194
|
+
"x-forwarded-proto",
|
|
1195
|
+
"x-real-ip",
|
|
1196
|
+
"host"
|
|
1197
|
+
];
|
|
1198
|
+
var BATCH_PATH = "/api/__theo_batch__";
|
|
1199
|
+
var DEFAULT_MAX_BATCH = 32;
|
|
1200
|
+
var batchRequestSchema = z2.object({
|
|
1201
|
+
path: z2.string().min(1),
|
|
1202
|
+
method: z2.string().min(1),
|
|
1203
|
+
query: z2.record(z2.string(), z2.unknown()).optional(),
|
|
1204
|
+
body: z2.unknown().optional(),
|
|
1205
|
+
headers: z2.record(z2.string(), z2.string()).optional()
|
|
1206
|
+
});
|
|
1207
|
+
var batchPayloadSchema = z2.object({
|
|
1208
|
+
requests: z2.array(batchRequestSchema).min(1)
|
|
1209
|
+
});
|
|
1210
|
+
function sanitizeItemHeaders(itemHeaders, outerHeaders) {
|
|
1211
|
+
const out = {};
|
|
1212
|
+
if (itemHeaders) {
|
|
1213
|
+
for (const [k, v] of Object.entries(itemHeaders)) {
|
|
1214
|
+
const lower = k.toLowerCase();
|
|
1215
|
+
if (!STRIPPED_HEADERS.includes(lower)) {
|
|
1216
|
+
out[lower] = v;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
if (outerHeaders) {
|
|
1221
|
+
for (const stripped of STRIPPED_HEADERS) {
|
|
1222
|
+
if (Object.hasOwn(outerHeaders, stripped)) {
|
|
1223
|
+
out[stripped] = outerHeaders[stripped];
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
return out;
|
|
1228
|
+
}
|
|
1229
|
+
async function handleBatchRequest(payload, options) {
|
|
1230
|
+
const parsed = batchPayloadSchema.parse(payload);
|
|
1231
|
+
const max = options.max ?? DEFAULT_MAX_BATCH;
|
|
1232
|
+
if (parsed.requests.length > max) {
|
|
1233
|
+
throw new Error(`Batch size ${parsed.requests.length} exceeds max ${max}`);
|
|
1234
|
+
}
|
|
1235
|
+
const results = [];
|
|
1236
|
+
for (const item of parsed.requests) {
|
|
1237
|
+
const sanitized = {
|
|
1238
|
+
...item,
|
|
1239
|
+
headers: sanitizeItemHeaders(item.headers, options.outerHeaders)
|
|
1240
|
+
};
|
|
1241
|
+
try {
|
|
1242
|
+
const r = await options.execute(sanitized);
|
|
1243
|
+
results.push(r);
|
|
1244
|
+
} catch (err) {
|
|
1245
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1246
|
+
results.push({ error: { message } });
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
return { results };
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// src/server/http/cors.ts
|
|
1253
|
+
var DEFAULT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
|
|
1254
|
+
var DEFAULT_ALLOWED_HEADERS = ["Content-Type", "X-Theo-Action", "Authorization"];
|
|
1255
|
+
var DEFAULT_MAX_AGE = 600;
|
|
1256
|
+
function readOrigin(req) {
|
|
1257
|
+
const raw = req.headers.origin;
|
|
1258
|
+
if (raw === void 0) return void 0;
|
|
1259
|
+
if (Array.isArray(raw)) {
|
|
1260
|
+
return raw.find((v) => typeof v === "string" && v.length > 0);
|
|
1261
|
+
}
|
|
1262
|
+
return raw;
|
|
1263
|
+
}
|
|
1264
|
+
function matchesOrigin(origin, allowed) {
|
|
1265
|
+
if (allowed === "*") return true;
|
|
1266
|
+
if (typeof allowed === "string") return origin === allowed;
|
|
1267
|
+
if (allowed instanceof RegExp) {
|
|
1268
|
+
allowed.lastIndex = 0;
|
|
1269
|
+
return allowed.test(origin);
|
|
1270
|
+
}
|
|
1271
|
+
if (Array.isArray(allowed)) {
|
|
1272
|
+
for (const entry of allowed) {
|
|
1273
|
+
if (typeof entry === "string" && origin === entry) return true;
|
|
1274
|
+
if (entry instanceof RegExp) {
|
|
1275
|
+
entry.lastIndex = 0;
|
|
1276
|
+
if (entry.test(origin)) return true;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
return false;
|
|
1280
|
+
}
|
|
1281
|
+
if (typeof allowed === "function") {
|
|
1282
|
+
try {
|
|
1283
|
+
return allowed(origin);
|
|
1284
|
+
} catch {
|
|
1285
|
+
return false;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
return false;
|
|
1289
|
+
}
|
|
1290
|
+
function createCorsHandler(config) {
|
|
1291
|
+
const methods = (config.methods ?? DEFAULT_METHODS).slice();
|
|
1292
|
+
const allowedHeaders = (config.allowedHeaders ?? DEFAULT_ALLOWED_HEADERS).slice();
|
|
1293
|
+
const maxAge = String(config.maxAge ?? DEFAULT_MAX_AGE);
|
|
1294
|
+
const credentials = config.credentials === true;
|
|
1295
|
+
return {
|
|
1296
|
+
handlePreflight(req, res) {
|
|
1297
|
+
if (req.method !== "OPTIONS") return false;
|
|
1298
|
+
const acMethod = req.headers["access-control-request-method"];
|
|
1299
|
+
if (!acMethod) return false;
|
|
1300
|
+
const origin = readOrigin(req);
|
|
1301
|
+
if (!origin) return false;
|
|
1302
|
+
if (!matchesOrigin(origin, config.origins)) {
|
|
1303
|
+
res.statusCode = 403;
|
|
1304
|
+
res.end();
|
|
1305
|
+
return true;
|
|
1306
|
+
}
|
|
1307
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
1308
|
+
res.setHeader("Access-Control-Allow-Methods", methods.join(", "));
|
|
1309
|
+
res.setHeader("Access-Control-Allow-Headers", allowedHeaders.join(", "));
|
|
1310
|
+
res.setHeader("Access-Control-Max-Age", maxAge);
|
|
1311
|
+
res.setHeader("Vary", "Origin");
|
|
1312
|
+
if (credentials) res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
1313
|
+
res.statusCode = 204;
|
|
1314
|
+
res.end();
|
|
1315
|
+
return true;
|
|
1316
|
+
},
|
|
1317
|
+
applyHeaders(req, res) {
|
|
1318
|
+
const origin = readOrigin(req);
|
|
1319
|
+
if (!origin) return;
|
|
1320
|
+
if (!matchesOrigin(origin, config.origins)) return;
|
|
1321
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
1322
|
+
res.setHeader("Vary", "Origin");
|
|
1323
|
+
if (credentials) res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
1324
|
+
if (config.exposedHeaders && config.exposedHeaders.length > 0) {
|
|
1325
|
+
res.setHeader("Access-Control-Expose-Headers", config.exposedHeaders.join(", "));
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// src/server/http/trace-context.ts
|
|
1332
|
+
var TRACE_HEADER = "x-trace-id";
|
|
1333
|
+
var TRACE_PARENT_HEADER = "traceparent";
|
|
1334
|
+
var REQUEST_ID_HEADER = "x-request-id";
|
|
1335
|
+
var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/;
|
|
1336
|
+
function parseTraceparent(value) {
|
|
1337
|
+
if (!value) return null;
|
|
1338
|
+
const m = TRACEPARENT_RE.exec(value);
|
|
1339
|
+
if (!m) return null;
|
|
1340
|
+
const traceId = m[1];
|
|
1341
|
+
if (/^0+$/.test(traceId)) return null;
|
|
1342
|
+
return traceId;
|
|
1343
|
+
}
|
|
1344
|
+
function pickHeader(value) {
|
|
1345
|
+
if (Array.isArray(value)) {
|
|
1346
|
+
for (const v of value) {
|
|
1347
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
1348
|
+
}
|
|
1349
|
+
return null;
|
|
1350
|
+
}
|
|
1351
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
1352
|
+
return null;
|
|
1353
|
+
}
|
|
1354
|
+
function resolveTraceIdFromHeaders(traceparent, requestId) {
|
|
1355
|
+
if (traceparent !== null) {
|
|
1356
|
+
const parsed = parseTraceparent(traceparent);
|
|
1357
|
+
if (parsed !== null) return parsed;
|
|
1358
|
+
}
|
|
1359
|
+
if (requestId !== null) return requestId;
|
|
1360
|
+
return globalThis.crypto.randomUUID();
|
|
1361
|
+
}
|
|
1362
|
+
function extractTraceId(req) {
|
|
1363
|
+
return resolveTraceIdFromHeaders(
|
|
1364
|
+
pickHeader(req.headers[TRACE_PARENT_HEADER]),
|
|
1365
|
+
pickHeader(req.headers[REQUEST_ID_HEADER])
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1186
1369
|
// src/server/observability/suggest.ts
|
|
1187
1370
|
function levenshtein(a, b) {
|
|
1188
1371
|
const m = a.length;
|
|
@@ -1818,6 +2001,227 @@ function createProductionLoader() {
|
|
|
1818
2001
|
};
|
|
1819
2002
|
}
|
|
1820
2003
|
|
|
2004
|
+
// src/server/security/csp-report.ts
|
|
2005
|
+
var CSP_REPORT_PATH = "/__theo/csp-report";
|
|
2006
|
+
var MAX_BODY = 16 * 1024;
|
|
2007
|
+
function pickHeader2(value) {
|
|
2008
|
+
if (typeof value === "string") return value;
|
|
2009
|
+
if (Array.isArray(value) && value.length > 0) return value[0];
|
|
2010
|
+
return "";
|
|
2011
|
+
}
|
|
2012
|
+
function toStringSafe(value, fallback = "(missing)") {
|
|
2013
|
+
if (typeof value === "string") return value;
|
|
2014
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
2015
|
+
return fallback;
|
|
2016
|
+
}
|
|
2017
|
+
function normalizeLegacy(raw) {
|
|
2018
|
+
return {
|
|
2019
|
+
blockedUrl: toStringSafe(raw["blocked-uri"]),
|
|
2020
|
+
documentUrl: toStringSafe(raw["document-uri"]),
|
|
2021
|
+
violatedDirective: toStringSafe(raw["violated-directive"]),
|
|
2022
|
+
effectiveDirective: typeof raw["effective-directive"] === "string" ? raw["effective-directive"] : void 0,
|
|
2023
|
+
originalPolicy: typeof raw["original-policy"] === "string" ? raw["original-policy"] : void 0,
|
|
2024
|
+
disposition: raw.disposition === "enforce" || raw.disposition === "report" ? raw.disposition : void 0,
|
|
2025
|
+
statusCode: typeof raw["status-code"] === "number" ? raw["status-code"] : void 0,
|
|
2026
|
+
sourceFile: typeof raw["source-file"] === "string" ? raw["source-file"] : void 0,
|
|
2027
|
+
lineNumber: typeof raw["line-number"] === "number" ? raw["line-number"] : void 0,
|
|
2028
|
+
columnNumber: typeof raw["column-number"] === "number" ? raw["column-number"] : void 0
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
function normalizeNew(entry) {
|
|
2032
|
+
if (!entry || typeof entry !== "object") return null;
|
|
2033
|
+
const body = entry.body;
|
|
2034
|
+
if (!body || typeof body !== "object") return null;
|
|
2035
|
+
const b = body;
|
|
2036
|
+
return {
|
|
2037
|
+
blockedUrl: toStringSafe(b.blockedURL),
|
|
2038
|
+
documentUrl: toStringSafe(b.documentURL),
|
|
2039
|
+
violatedDirective: toStringSafe(b.violatedDirective),
|
|
2040
|
+
effectiveDirective: typeof b.effectiveDirective === "string" ? b.effectiveDirective : void 0,
|
|
2041
|
+
originalPolicy: typeof b.originalPolicy === "string" ? b.originalPolicy : void 0,
|
|
2042
|
+
disposition: b.disposition === "enforce" || b.disposition === "report" ? b.disposition : void 0,
|
|
2043
|
+
statusCode: typeof b.statusCode === "number" ? b.statusCode : void 0,
|
|
2044
|
+
sourceFile: typeof b.sourceFile === "string" ? b.sourceFile : void 0,
|
|
2045
|
+
lineNumber: typeof b.lineNumber === "number" ? b.lineNumber : void 0,
|
|
2046
|
+
columnNumber: typeof b.columnNumber === "number" ? b.columnNumber : void 0
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
async function readBody(req, maxBytes) {
|
|
2050
|
+
return await new Promise((resolve, reject) => {
|
|
2051
|
+
const chunks = [];
|
|
2052
|
+
let total = 0;
|
|
2053
|
+
req.on("data", (chunk) => {
|
|
2054
|
+
total += chunk.length;
|
|
2055
|
+
if (total > maxBytes) {
|
|
2056
|
+
reject(new Error("body too large"));
|
|
2057
|
+
req.destroy();
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
chunks.push(chunk);
|
|
2061
|
+
});
|
|
2062
|
+
req.on("end", () => {
|
|
2063
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
2064
|
+
});
|
|
2065
|
+
req.on("error", reject);
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
2068
|
+
async function handleCspReport(req, res, opts) {
|
|
2069
|
+
const ctHeader = req.headers["content-type"];
|
|
2070
|
+
const ct = pickHeader2(ctHeader);
|
|
2071
|
+
let raw;
|
|
2072
|
+
try {
|
|
2073
|
+
raw = await readBody(req, MAX_BODY);
|
|
2074
|
+
} catch {
|
|
2075
|
+
res.statusCode = 413;
|
|
2076
|
+
res.end();
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
let violations;
|
|
2080
|
+
try {
|
|
2081
|
+
if (ct.startsWith("application/csp-report")) {
|
|
2082
|
+
const parsed = JSON.parse(raw);
|
|
2083
|
+
const inner = parsed["csp-report"];
|
|
2084
|
+
if (!inner || typeof inner !== "object") {
|
|
2085
|
+
res.statusCode = 204;
|
|
2086
|
+
res.end();
|
|
2087
|
+
return;
|
|
2088
|
+
}
|
|
2089
|
+
violations = [normalizeLegacy(inner)];
|
|
2090
|
+
} else if (ct.startsWith("application/reports+json")) {
|
|
2091
|
+
const parsed = JSON.parse(raw);
|
|
2092
|
+
const entries = Array.isArray(parsed) ? parsed : [];
|
|
2093
|
+
violations = entries.map((e) => normalizeNew(e)).filter((v) => v !== null);
|
|
2094
|
+
} else {
|
|
2095
|
+
res.statusCode = 415;
|
|
2096
|
+
res.end();
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
} catch {
|
|
2100
|
+
res.statusCode = 400;
|
|
2101
|
+
res.end();
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
for (const v of violations) {
|
|
2105
|
+
safeAudit(opts.auditLogger, {
|
|
2106
|
+
action: "csp.violation",
|
|
2107
|
+
metadata: v
|
|
2108
|
+
});
|
|
2109
|
+
try {
|
|
2110
|
+
opts.devtoolsDispatcher?.onCspViolation?.(v);
|
|
2111
|
+
} catch {
|
|
2112
|
+
}
|
|
2113
|
+
try {
|
|
2114
|
+
opts.onViolation?.(v);
|
|
2115
|
+
} catch {
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
res.statusCode = 204;
|
|
2119
|
+
res.end();
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
// src/server/security/csrf-readiness-endpoint.ts
|
|
2123
|
+
var CSRF_READINESS_PATH = "/__theo/csrf-readiness";
|
|
2124
|
+
var CSRF_READINESS_RESET_PATH = "/__theo/csrf-readiness/reset";
|
|
2125
|
+
function originMatchesHost(req) {
|
|
2126
|
+
const origin = req.headers.origin;
|
|
2127
|
+
if (typeof origin !== "string") return false;
|
|
2128
|
+
const host = req.headers.host;
|
|
2129
|
+
if (typeof host !== "string") return false;
|
|
2130
|
+
try {
|
|
2131
|
+
const parsed = new URL(origin);
|
|
2132
|
+
return parsed.host === host;
|
|
2133
|
+
} catch {
|
|
2134
|
+
return false;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
function sendJson2(res, status, body) {
|
|
2138
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
2139
|
+
res.end(JSON.stringify(body));
|
|
2140
|
+
}
|
|
2141
|
+
function sendNoContent(res) {
|
|
2142
|
+
res.writeHead(204);
|
|
2143
|
+
res.end();
|
|
2144
|
+
}
|
|
2145
|
+
function sendError2(res, status, code, message) {
|
|
2146
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
2147
|
+
res.end(JSON.stringify({ error: { code, message } }));
|
|
2148
|
+
}
|
|
2149
|
+
async function handleCsrfReadiness(req, res, store) {
|
|
2150
|
+
const url = (req.url ?? "").split("?")[0];
|
|
2151
|
+
const method = req.method ?? "GET";
|
|
2152
|
+
if (url === CSRF_READINESS_PATH) {
|
|
2153
|
+
if (method === "GET") {
|
|
2154
|
+
sendJson2(res, 200, store.summary());
|
|
2155
|
+
return true;
|
|
2156
|
+
}
|
|
2157
|
+
sendError2(res, 405, "METHOD_NOT_ALLOWED", `Use GET on ${CSRF_READINESS_PATH}`);
|
|
2158
|
+
return true;
|
|
2159
|
+
}
|
|
2160
|
+
if (url === CSRF_READINESS_RESET_PATH) {
|
|
2161
|
+
if (method !== "POST") {
|
|
2162
|
+
sendError2(res, 405, "METHOD_NOT_ALLOWED", `Use POST on ${CSRF_READINESS_RESET_PATH}`);
|
|
2163
|
+
return true;
|
|
2164
|
+
}
|
|
2165
|
+
const hasHeader = req.headers["x-theo-action"] === "1";
|
|
2166
|
+
if (!hasHeader || !originMatchesHost(req)) {
|
|
2167
|
+
sendError2(res, 403, "CSRF_INVALID", "Reset requires X-Theo-Action: 1 + same-origin");
|
|
2168
|
+
return true;
|
|
2169
|
+
}
|
|
2170
|
+
store.reset();
|
|
2171
|
+
sendNoContent(res);
|
|
2172
|
+
return true;
|
|
2173
|
+
}
|
|
2174
|
+
return false;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
// src/server/security/csrf-readiness-store.ts
|
|
2178
|
+
var CsrfReadinessStore = class _CsrfReadinessStore {
|
|
2179
|
+
static MAX_ENTRIES = 1e3;
|
|
2180
|
+
entries = /* @__PURE__ */ new Map();
|
|
2181
|
+
keyFor(event) {
|
|
2182
|
+
return `${event.method}\0${event.path}\0${event.reason}`;
|
|
2183
|
+
}
|
|
2184
|
+
record(event) {
|
|
2185
|
+
const key = this.keyFor(event);
|
|
2186
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2187
|
+
const existing = this.entries.get(key);
|
|
2188
|
+
if (existing) {
|
|
2189
|
+
existing.count++;
|
|
2190
|
+
existing.lastSeen = now;
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
if (this.entries.size >= _CsrfReadinessStore.MAX_ENTRIES) {
|
|
2194
|
+
const firstKey = this.entries.keys().next().value;
|
|
2195
|
+
if (firstKey !== void 0) this.entries.delete(firstKey);
|
|
2196
|
+
}
|
|
2197
|
+
this.entries.set(key, { count: 1, firstSeen: now, lastSeen: now });
|
|
2198
|
+
}
|
|
2199
|
+
summary() {
|
|
2200
|
+
let total = 0;
|
|
2201
|
+
const routes = [];
|
|
2202
|
+
for (const [key, entry] of this.entries) {
|
|
2203
|
+
const [method, path, reason] = key.split("\0");
|
|
2204
|
+
routes.push({
|
|
2205
|
+
method,
|
|
2206
|
+
path,
|
|
2207
|
+
reason,
|
|
2208
|
+
count: entry.count,
|
|
2209
|
+
firstSeen: entry.firstSeen,
|
|
2210
|
+
lastSeen: entry.lastSeen
|
|
2211
|
+
});
|
|
2212
|
+
total += entry.count;
|
|
2213
|
+
}
|
|
2214
|
+
return {
|
|
2215
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2216
|
+
totalEvents: total,
|
|
2217
|
+
routes
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
reset() {
|
|
2221
|
+
this.entries.clear();
|
|
2222
|
+
}
|
|
2223
|
+
};
|
|
2224
|
+
|
|
1821
2225
|
// src/server/security/security-headers.ts
|
|
1822
2226
|
var DEFAULT_CSP = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' ws: wss:; frame-ancestors 'none'; report-uri /__theo/csp-report";
|
|
1823
2227
|
var DEFAULT_PERMISSIONS_POLICY = "geolocation=(), camera=(), microphone=(), payment=(), usb=(), accelerometer=(), gyroscope=()";
|
|
@@ -1903,6 +2307,11 @@ function generateNonce() {
|
|
|
1903
2307
|
export {
|
|
1904
2308
|
executeRoute,
|
|
1905
2309
|
executeAction,
|
|
2310
|
+
BATCH_PATH,
|
|
2311
|
+
handleBatchRequest,
|
|
2312
|
+
createCorsHandler,
|
|
2313
|
+
TRACE_HEADER,
|
|
2314
|
+
extractTraceId,
|
|
1906
2315
|
findSuggestion,
|
|
1907
2316
|
createPluginRunnerFromConfig,
|
|
1908
2317
|
createRateLimiter,
|
|
@@ -1918,8 +2327,12 @@ export {
|
|
|
1918
2327
|
mountAgent,
|
|
1919
2328
|
createViteLoader,
|
|
1920
2329
|
createProductionLoader,
|
|
2330
|
+
CSP_REPORT_PATH,
|
|
2331
|
+
handleCspReport,
|
|
2332
|
+
handleCsrfReadiness,
|
|
2333
|
+
CsrfReadinessStore,
|
|
1921
2334
|
buildSecurityHeaders,
|
|
1922
2335
|
applySecurityHeaders,
|
|
1923
2336
|
generateNonce
|
|
1924
2337
|
};
|
|
1925
|
-
//# sourceMappingURL=chunk-
|
|
2338
|
+
//# sourceMappingURL=chunk-ABUVJU3P.js.map
|