theokit 0.48.8 → 0.48.10
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/README.md +86 -0
- package/dist/{actions-virtual-module-IICWQK43.js → actions-virtual-module-LB4NGONI.js} +2 -2
- package/dist/{agent-WZLCEXD4.js → agent-QMVAEQ7I.js} +2 -2
- package/dist/{app-typed-client-4GDSR5T3.js → app-typed-client-G3J5G7E5.js} +2 -2
- package/dist/{build-5TKAROT4.js → build-GYRWWCLA.js} +4 -4
- package/dist/{chunk-XSCZWSE3.js → chunk-7BCC4BCR.js} +95 -6
- package/dist/chunk-7BCC4BCR.js.map +1 -0
- package/dist/{chunk-ZGNA66JK.js → chunk-7UMA5DY4.js} +91 -11
- package/dist/{chunk-ZGNA66JK.js.map → chunk-7UMA5DY4.js.map} +1 -1
- package/dist/{chunk-ABUVJU3P.js → chunk-BIVCP6X5.js} +2 -1
- package/dist/{chunk-UDPJP42U.js → chunk-GPX7I5ZY.js} +31 -4
- package/dist/chunk-GPX7I5ZY.js.map +1 -0
- package/dist/{chunk-RPIL7VRZ.js → chunk-GRI4XHBJ.js} +2 -2
- package/dist/{chunk-A4ZEP3FE.js → chunk-GVW5EKEU.js} +11 -2
- package/dist/chunk-GVW5EKEU.js.map +1 -0
- package/dist/{chunk-HENNMZUH.js → chunk-NGE76BEX.js} +4 -4
- package/dist/{chunk-GH32R55S.js → chunk-S433N3CT.js} +2 -2
- package/dist/chunk-V7MTRM6Q.js +90 -0
- package/dist/chunk-V7MTRM6Q.js.map +1 -0
- package/dist/cli/index.js +19 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/cookies-QWVS7OGS.js +23 -0
- package/dist/{dev-XGAJQ7DH.js → dev-K27SUPJD.js} +6 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/{info-MSBQ72EM.js → info-XW7HGUPH.js} +3 -3
- package/dist/{internal-api-AKYIRMT2.js → internal-api-V5L65YSJ.js} +2 -2
- package/dist/{load-config-2LP2OEKD.js → load-config-VKD6LKWU.js} +3 -3
- package/dist/{mcp-D7K6NJMW.js → mcp-3JORKAFI.js} +3 -3
- package/dist/{openapi-FWNYHB6T.js → openapi-HZXR335W.js} +3 -3
- package/dist/{routes-6AMNVFMI.js → routes-OLNQX7CT.js} +3 -3
- package/dist/{schema-Z3Y7SFLE.js → schema-ZO3OSWVG.js} +2 -2
- package/dist/server/index.js +1 -1
- package/dist/server/rate-limit/index.d.ts +49 -1
- package/dist/server/rate-limit/index.js +1 -1
- package/dist/{start-YR4TQQP2.js → start-KR2JJR7K.js} +142 -15
- package/dist/start-KR2JJR7K.js.map +1 -0
- package/dist/vite-plugin/index.js +1 -1
- package/dist/{vite-plugin-PUUCR6LR.js → vite-plugin-SM5MHOJZ.js} +6 -6
- package/dist/vite-plugin-SM5MHOJZ.js.map +1 -0
- package/package.json +13 -3
- package/dist/chunk-A4ZEP3FE.js.map +0 -1
- package/dist/chunk-UDPJP42U.js.map +0 -1
- package/dist/chunk-XSCZWSE3.js.map +0 -1
- package/dist/start-YR4TQQP2.js.map +0 -1
- /package/dist/{actions-virtual-module-IICWQK43.js.map → actions-virtual-module-LB4NGONI.js.map} +0 -0
- /package/dist/{agent-WZLCEXD4.js.map → agent-QMVAEQ7I.js.map} +0 -0
- /package/dist/{app-typed-client-4GDSR5T3.js.map → app-typed-client-G3J5G7E5.js.map} +0 -0
- /package/dist/{build-5TKAROT4.js.map → build-GYRWWCLA.js.map} +0 -0
- /package/dist/{chunk-ABUVJU3P.js.map → chunk-BIVCP6X5.js.map} +0 -0
- /package/dist/{chunk-RPIL7VRZ.js.map → chunk-GRI4XHBJ.js.map} +0 -0
- /package/dist/{chunk-HENNMZUH.js.map → chunk-NGE76BEX.js.map} +0 -0
- /package/dist/{chunk-GH32R55S.js.map → chunk-S433N3CT.js.map} +0 -0
- /package/dist/{internal-api-AKYIRMT2.js.map → cookies-QWVS7OGS.js.map} +0 -0
- /package/dist/{dev-XGAJQ7DH.js.map → dev-K27SUPJD.js.map} +0 -0
- /package/dist/{info-MSBQ72EM.js.map → info-XW7HGUPH.js.map} +0 -0
- /package/dist/{load-config-2LP2OEKD.js.map → internal-api-V5L65YSJ.js.map} +0 -0
- /package/dist/{schema-Z3Y7SFLE.js.map → load-config-VKD6LKWU.js.map} +0 -0
- /package/dist/{mcp-D7K6NJMW.js.map → mcp-3JORKAFI.js.map} +0 -0
- /package/dist/{openapi-FWNYHB6T.js.map → openapi-HZXR335W.js.map} +0 -0
- /package/dist/{routes-6AMNVFMI.js.map → routes-OLNQX7CT.js.map} +0 -0
- /package/dist/{vite-plugin-PUUCR6LR.js.map → schema-ZO3OSWVG.js.map} +0 -0
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
} from "./chunk-IEES3CHD.js";
|
|
6
6
|
import {
|
|
7
7
|
loadConfig
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-S433N3CT.js";
|
|
9
9
|
import "./chunk-HBU2GIMZ.js";
|
|
10
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-GVW5EKEU.js";
|
|
11
11
|
import "./chunk-7VPQHEAJ.js";
|
|
12
12
|
|
|
13
13
|
// src/cli/commands/info.ts
|
|
@@ -101,4 +101,4 @@ export {
|
|
|
101
101
|
buildInfo,
|
|
102
102
|
infoCommand
|
|
103
103
|
};
|
|
104
|
-
//# sourceMappingURL=info-
|
|
104
|
+
//# sourceMappingURL=info-XW7HGUPH.js.map
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
handleCspReport,
|
|
20
20
|
handleCsrfReadiness,
|
|
21
21
|
mountAgent
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-BIVCP6X5.js";
|
|
23
23
|
import {
|
|
24
24
|
resolveProvider
|
|
25
25
|
} from "./chunk-6GGWS7J3.js";
|
|
@@ -76,4 +76,4 @@ export {
|
|
|
76
76
|
scanWebSocketRoutes,
|
|
77
77
|
sendError
|
|
78
78
|
};
|
|
79
|
-
//# sourceMappingURL=internal-api-
|
|
79
|
+
//# sourceMappingURL=internal-api-V5L65YSJ.js.map
|
|
@@ -3,12 +3,12 @@ import "tsx/esm";
|
|
|
3
3
|
import {
|
|
4
4
|
deepMerge,
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-S433N3CT.js";
|
|
7
7
|
import "./chunk-HBU2GIMZ.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-GVW5EKEU.js";
|
|
9
9
|
import "./chunk-7VPQHEAJ.js";
|
|
10
10
|
export {
|
|
11
11
|
deepMerge,
|
|
12
12
|
loadConfig
|
|
13
13
|
};
|
|
14
|
-
//# sourceMappingURL=load-config-
|
|
14
|
+
//# sourceMappingURL=load-config-VKD6LKWU.js.map
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import "tsx/esm";
|
|
3
3
|
import {
|
|
4
4
|
createAgentSsrLoader
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-NGE76BEX.js";
|
|
6
6
|
import {
|
|
7
7
|
extractAppResources,
|
|
8
8
|
handleMcpJsonRpc
|
|
@@ -46,7 +46,7 @@ async function serveMcpStdio(mod, name, appResources, streams) {
|
|
|
46
46
|
// src/cli/commands/mcp.ts
|
|
47
47
|
async function mcpCommand(name, deps = {}) {
|
|
48
48
|
const projectRoot = deps.projectRoot ?? process.cwd();
|
|
49
|
-
const agentsDir = deps.agentsDir ?? (await (await import("./load-config-
|
|
49
|
+
const agentsDir = deps.agentsDir ?? (await (await import("./load-config-VKD6LKWU.js")).loadConfig(projectRoot)).agentsDir;
|
|
50
50
|
const agent = scanAgents(projectRoot, agentsDir).find((a) => a.name === name);
|
|
51
51
|
if (!agent) {
|
|
52
52
|
const names = scanAgents(projectRoot, agentsDir).map((a) => a.name).join(", ");
|
|
@@ -75,4 +75,4 @@ async function mcpCommand(name, deps = {}) {
|
|
|
75
75
|
export {
|
|
76
76
|
mcpCommand
|
|
77
77
|
};
|
|
78
|
-
//# sourceMappingURL=mcp-
|
|
78
|
+
//# sourceMappingURL=mcp-3JORKAFI.js.map
|
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
} from "./chunk-47VNWTL4.js";
|
|
7
7
|
import {
|
|
8
8
|
loadConfig
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-S433N3CT.js";
|
|
10
10
|
import "./chunk-HBU2GIMZ.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-GVW5EKEU.js";
|
|
12
12
|
import "./chunk-7VPQHEAJ.js";
|
|
13
13
|
import {
|
|
14
14
|
generateManifest
|
|
@@ -82,4 +82,4 @@ function emitOpenApiInMemory(manifest, config) {
|
|
|
82
82
|
export {
|
|
83
83
|
openapiCommand
|
|
84
84
|
};
|
|
85
|
-
//# sourceMappingURL=openapi-
|
|
85
|
+
//# sourceMappingURL=openapi-HZXR335W.js.map
|
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
} from "./chunk-GEQJAFSV.js";
|
|
6
6
|
import {
|
|
7
7
|
loadConfig
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-S433N3CT.js";
|
|
9
9
|
import "./chunk-HBU2GIMZ.js";
|
|
10
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-GVW5EKEU.js";
|
|
11
11
|
import "./chunk-7VPQHEAJ.js";
|
|
12
12
|
import {
|
|
13
13
|
scanServerActions,
|
|
@@ -62,4 +62,4 @@ async function routesCommand() {
|
|
|
62
62
|
export {
|
|
63
63
|
routesCommand
|
|
64
64
|
};
|
|
65
|
-
//# sourceMappingURL=routes-
|
|
65
|
+
//# sourceMappingURL=routes-OLNQX7CT.js.map
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
securityHeadersSchema,
|
|
7
7
|
storageSchema,
|
|
8
8
|
theoConfigSchema
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-GVW5EKEU.js";
|
|
10
10
|
import "./chunk-7VPQHEAJ.js";
|
|
11
11
|
export {
|
|
12
12
|
cacheSchema,
|
|
@@ -15,4 +15,4 @@ export {
|
|
|
15
15
|
storageSchema,
|
|
16
16
|
theoConfigSchema
|
|
17
17
|
};
|
|
18
|
-
//# sourceMappingURL=schema-
|
|
18
|
+
//# sourceMappingURL=schema-ZO3OSWVG.js.map
|
package/dist/server/index.js
CHANGED
|
@@ -4,6 +4,41 @@ import { R as RateLimitStore } from '../../rate-limit-store-BEJnhWdw.js';
|
|
|
4
4
|
export { I as InMemoryStore, a as RateLimitState } from '../../rate-limit-store-BEJnhWdw.js';
|
|
5
5
|
import { IncomingMessage } from 'node:http';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Resolves the address a rate-limit bucket should be keyed on.
|
|
9
|
+
*
|
|
10
|
+
* ## Why this is not just `req.socket.remoteAddress`
|
|
11
|
+
*
|
|
12
|
+
* Behind any reverse proxy — Caddy, nginx, a load balancer, an ingress controller — the socket
|
|
13
|
+
* address is the PROXY's, identical for every visitor on the internet. Keying buckets on it gives
|
|
14
|
+
* the whole world one shared budget, so the first few requests each minute exhaust it and everyone
|
|
15
|
+
* else is refused. That is worse than no limiting: a limit meant to stop one abusive client becomes
|
|
16
|
+
* a denial of service any single client can trigger for everybody.
|
|
17
|
+
*
|
|
18
|
+
* ## Why it is opt-in
|
|
19
|
+
*
|
|
20
|
+
* `x-forwarded-for` is a request header, and a request header is whatever the client typed. Reading
|
|
21
|
+
* it without being told to would let anyone bypass the limiter by rotating a forged value — a
|
|
22
|
+
* one-line `curl -H`. So the default is to trust nothing and use the socket.
|
|
23
|
+
*
|
|
24
|
+
* ## Why the RIGHTMOST entry
|
|
25
|
+
*
|
|
26
|
+
* Each proxy APPENDS the address that connected to it. A client that sends `x-forwarded-for: 1.2.3.4`
|
|
27
|
+
* through one proxy produces `1.2.3.4, <real client>` — the forgery lands on the left, and the entry
|
|
28
|
+
* the trusted proxy wrote is last. Counting from the right therefore skips exactly the hops the
|
|
29
|
+
* operator vouched for, and everything a client can influence stays to the left of where we look.
|
|
30
|
+
*
|
|
31
|
+
* This mirrors the `'trusted-proxy'` policy in `adapters/web-shim.ts`, which documents the same rule
|
|
32
|
+
* for the Web-Request runtimes.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* How many proxies sit in front of the app.
|
|
36
|
+
*
|
|
37
|
+
* `false` (the default) trusts nothing and uses the socket address. `true` means one trusted proxy.
|
|
38
|
+
* A number names the hop count for a longer chain — a CDN in front of your own proxy is `2`.
|
|
39
|
+
*/
|
|
40
|
+
type TrustProxy = boolean | number;
|
|
41
|
+
|
|
7
42
|
/**
|
|
8
43
|
* T2.2 — Per-route + per-user rate limiting.
|
|
9
44
|
*
|
|
@@ -29,6 +64,19 @@ interface RouteRateLimitConfig {
|
|
|
29
64
|
cookieName?: string;
|
|
30
65
|
/** Optional shared store (for multi-route correlation). Default per-limiter InMemoryStore. */
|
|
31
66
|
store?: RateLimitStore;
|
|
67
|
+
/**
|
|
68
|
+
* How many reverse proxies sit in front of the app, for `keyBy: 'ip'`. Default `false` — trust
|
|
69
|
+
* none and key on the socket address.
|
|
70
|
+
*
|
|
71
|
+
* Set this whenever the app is behind Caddy, nginx, a load balancer or an ingress controller:
|
|
72
|
+
* without it every visitor keys on the proxy's address and shares one bucket, so a handful of
|
|
73
|
+
* requests exhausts the budget for the entire internet. With it, the client address is read from
|
|
74
|
+
* `x-forwarded-for` counting in from the right, past exactly the hops declared here.
|
|
75
|
+
*
|
|
76
|
+
* Leaving it off by default is deliberate — `x-forwarded-for` is client-writable, and honouring it
|
|
77
|
+
* uninvited turns the limiter into a one-header bypass. See `client-ip.ts`.
|
|
78
|
+
*/
|
|
79
|
+
trustProxy?: TrustProxy;
|
|
32
80
|
}
|
|
33
81
|
/**
|
|
34
82
|
* Test whether `path` matches `pattern`. String patterns are compared
|
|
@@ -44,7 +92,7 @@ declare function matchRoutePattern(path: string, pattern: string | RegExp): bool
|
|
|
44
92
|
* we fall back to IP so anonymous users still get rate-limited rather
|
|
45
93
|
* than sharing an empty bucket.
|
|
46
94
|
*/
|
|
47
|
-
declare function deriveKey(req: IncomingMessage, keyBy: KeyByMode, cookieName: string): Promise<string>;
|
|
95
|
+
declare function deriveKey(req: IncomingMessage, keyBy: KeyByMode, cookieName: string, trustProxy?: TrustProxy): Promise<string>;
|
|
48
96
|
/**
|
|
49
97
|
* Per-route rate limiter factory. Returns a sync checker compatible with
|
|
50
98
|
* the existing api-middleware shape.
|
|
@@ -3,6 +3,9 @@ import "tsx/esm";
|
|
|
3
3
|
import {
|
|
4
4
|
CONTROLLER_MANIFEST_FILE
|
|
5
5
|
} from "./chunk-47CINLWF.js";
|
|
6
|
+
import {
|
|
7
|
+
parseCookieHeader
|
|
8
|
+
} from "./chunk-V7MTRM6Q.js";
|
|
6
9
|
import {
|
|
7
10
|
preflightNodeAndBindings
|
|
8
11
|
} from "./chunk-KTMQUYED.js";
|
|
@@ -15,30 +18,30 @@ import {
|
|
|
15
18
|
resolveTransformer,
|
|
16
19
|
serveAgentAuxRoute,
|
|
17
20
|
writeWebResponseToServerResponse
|
|
18
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-GRI4XHBJ.js";
|
|
19
22
|
import "./chunk-2CVV6CNN.js";
|
|
20
23
|
import {
|
|
21
24
|
findRootDiv
|
|
22
25
|
} from "./chunk-3PWQQWT6.js";
|
|
23
26
|
import {
|
|
24
27
|
loadConfig
|
|
25
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-S433N3CT.js";
|
|
26
29
|
import {
|
|
27
30
|
loadEnv
|
|
28
31
|
} from "./chunk-HBU2GIMZ.js";
|
|
29
|
-
import "./chunk-
|
|
32
|
+
import "./chunk-GVW5EKEU.js";
|
|
30
33
|
import "./chunk-7VPQHEAJ.js";
|
|
31
34
|
import {
|
|
35
|
+
InMemoryStore,
|
|
32
36
|
buildSecurityHeaders,
|
|
33
37
|
createPluginRunnerFromConfig,
|
|
34
38
|
createProductionLoader,
|
|
35
|
-
createRateLimiter,
|
|
36
39
|
executeAction,
|
|
37
40
|
executeRoute,
|
|
38
41
|
findSuggestion,
|
|
39
42
|
generateNonce,
|
|
40
43
|
mountAgent
|
|
41
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-BIVCP6X5.js";
|
|
42
45
|
import {
|
|
43
46
|
getApprovalRegistry,
|
|
44
47
|
resolveProvider
|
|
@@ -97,6 +100,131 @@ async function serveReservedRoute(pathname, routes = {}) {
|
|
|
97
100
|
return null;
|
|
98
101
|
}
|
|
99
102
|
|
|
103
|
+
// src/server/rate-limit/client-ip.ts
|
|
104
|
+
function hopCount(trustProxy) {
|
|
105
|
+
if (trustProxy === true) return 1;
|
|
106
|
+
if (trustProxy === false) return 0;
|
|
107
|
+
return trustProxy;
|
|
108
|
+
}
|
|
109
|
+
function headerValue(req, name) {
|
|
110
|
+
const raw = req.headers[name];
|
|
111
|
+
if (Array.isArray(raw)) return raw[raw.length - 1];
|
|
112
|
+
return raw;
|
|
113
|
+
}
|
|
114
|
+
function resolveClientIp(req, trustProxy = false) {
|
|
115
|
+
const socketAddress = req.socket?.remoteAddress ?? "unknown";
|
|
116
|
+
const hops = hopCount(trustProxy);
|
|
117
|
+
if (hops < 1) return socketAddress;
|
|
118
|
+
const forwarded = headerValue(req, "x-forwarded-for");
|
|
119
|
+
if (forwarded) {
|
|
120
|
+
const entries = forwarded.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
121
|
+
const index = entries.length - hops;
|
|
122
|
+
if (index >= 0 && index < entries.length) return entries[index];
|
|
123
|
+
}
|
|
124
|
+
const realIp = headerValue(req, "x-real-ip");
|
|
125
|
+
if (realIp) return realIp.trim();
|
|
126
|
+
return socketAddress;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/server/rate-limit/rate-limit-per-route.ts
|
|
130
|
+
function normalizePath(input) {
|
|
131
|
+
const noQuery = input.split("?")[0];
|
|
132
|
+
if (noQuery.length > 1 && noQuery.endsWith("/")) return noQuery.slice(0, -1);
|
|
133
|
+
return noQuery;
|
|
134
|
+
}
|
|
135
|
+
function matchRoutePattern(path, pattern) {
|
|
136
|
+
const canonical = normalizePath(path);
|
|
137
|
+
if (typeof pattern === "string") {
|
|
138
|
+
return canonical === normalizePath(pattern);
|
|
139
|
+
}
|
|
140
|
+
pattern.lastIndex = 0;
|
|
141
|
+
return pattern.test(canonical);
|
|
142
|
+
}
|
|
143
|
+
async function hashFragment(input) {
|
|
144
|
+
const buf = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
|
|
145
|
+
const bytes = new Uint8Array(buf);
|
|
146
|
+
let bin = "";
|
|
147
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
148
|
+
const b64url = btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
149
|
+
return b64url.slice(0, 16);
|
|
150
|
+
}
|
|
151
|
+
function readCookie(req, name) {
|
|
152
|
+
return parseCookieHeader(req.headers.cookie ?? void 0).get(name);
|
|
153
|
+
}
|
|
154
|
+
async function deriveKey(req, keyBy, cookieName, trustProxy = false) {
|
|
155
|
+
if (typeof keyBy === "function") return keyBy(req);
|
|
156
|
+
const ip = resolveClientIp(req, trustProxy);
|
|
157
|
+
switch (keyBy) {
|
|
158
|
+
case "session": {
|
|
159
|
+
const cookie = readCookie(req, cookieName);
|
|
160
|
+
return cookie ? `session:${await hashFragment(cookie)}` : `ip:${ip}`;
|
|
161
|
+
}
|
|
162
|
+
case "user": {
|
|
163
|
+
const userId = req.user?.id;
|
|
164
|
+
return userId ? `user:${userId}` : `ip:${ip}`;
|
|
165
|
+
}
|
|
166
|
+
case "ip":
|
|
167
|
+
default:
|
|
168
|
+
return `ip:${ip}`;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function createRouteRateLimiter(config) {
|
|
172
|
+
const isFlat = "windowMs" in config && "max" in config && !("default" in config) && !("routes" in config);
|
|
173
|
+
const cfg = isFlat ? { default: config } : config;
|
|
174
|
+
const store = cfg.store ?? new InMemoryStore();
|
|
175
|
+
if (!(store instanceof InMemoryStore)) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
"createRouteRateLimiter: async RateLimitStore implementations require a dedicated async middleware path. Use the InMemoryStore default for the sync facade."
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
const inMemoryStore = store;
|
|
181
|
+
const keyBy = cfg.keyBy ?? "ip";
|
|
182
|
+
const cookieName = cfg.cookieName ?? "theo_session";
|
|
183
|
+
const trustProxy = cfg.trustProxy ?? false;
|
|
184
|
+
const patternList = [];
|
|
185
|
+
if (cfg.routes) {
|
|
186
|
+
for (const [pattern, c] of Object.entries(cfg.routes)) patternList.push([pattern, c]);
|
|
187
|
+
}
|
|
188
|
+
if (cfg.routePatterns) {
|
|
189
|
+
for (const tuple of cfg.routePatterns) patternList.push(tuple);
|
|
190
|
+
}
|
|
191
|
+
return async function checkRouteRateLimit(req) {
|
|
192
|
+
const url = req.url ?? "";
|
|
193
|
+
let matched;
|
|
194
|
+
for (const [pattern, c] of patternList) {
|
|
195
|
+
if (matchRoutePattern(url, pattern)) {
|
|
196
|
+
matched = c;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const effective = matched ?? cfg.default;
|
|
201
|
+
if (!effective) {
|
|
202
|
+
return { limited: false, headers: {} };
|
|
203
|
+
}
|
|
204
|
+
const bucketSuffix = typeof matched === "undefined" ? "*default*" : normalizePath(url);
|
|
205
|
+
const key = `${await deriveKey(req, keyBy, cookieName, trustProxy)}|${bucketSuffix}`;
|
|
206
|
+
const state = inMemoryStore.incrSync(key, effective.windowMs);
|
|
207
|
+
if (state.count > effective.max) {
|
|
208
|
+
const retryAfter = Math.ceil((state.resetAt - Date.now()) / 1e3);
|
|
209
|
+
return {
|
|
210
|
+
limited: true,
|
|
211
|
+
headers: {
|
|
212
|
+
"X-RateLimit-Limit": String(effective.max),
|
|
213
|
+
"X-RateLimit-Remaining": "0",
|
|
214
|
+
"Retry-After": String(retryAfter)
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
limited: false,
|
|
220
|
+
headers: {
|
|
221
|
+
"X-RateLimit-Limit": String(effective.max),
|
|
222
|
+
"X-RateLimit-Remaining": String(Math.max(0, effective.max - state.count))
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
100
228
|
// src/cli/commands/start/assert-sdk-compatible.ts
|
|
101
229
|
import { readFileSync } from "fs";
|
|
102
230
|
import { createRequire } from "module";
|
|
@@ -202,7 +330,7 @@ async function configureStorageManagerFromConfig(storageConfig) {
|
|
|
202
330
|
if (storageConfig === void 0 || storageConfig === null) return;
|
|
203
331
|
try {
|
|
204
332
|
const { getStorageManager } = await import("./storage-manager-3O5PDO6O.js");
|
|
205
|
-
const { storageSchema } = await import("./schema-
|
|
333
|
+
const { storageSchema } = await import("./schema-ZO3OSWVG.js");
|
|
206
334
|
const parsed = storageSchema.parse(storageConfig);
|
|
207
335
|
getStorageManager().configure(parsed);
|
|
208
336
|
} catch (err) {
|
|
@@ -359,9 +487,9 @@ async function loadCompiledController(absPath) {
|
|
|
359
487
|
return await import(pathToFileURL(absPath).href);
|
|
360
488
|
}
|
|
361
489
|
var X_REQUEST_ID = "x-request-id";
|
|
362
|
-
function applyRateLimit(c, method) {
|
|
490
|
+
async function applyRateLimit(c, method) {
|
|
363
491
|
if (!c.rateLimiter) return false;
|
|
364
|
-
const check = c.rateLimiter(c.req);
|
|
492
|
+
const check = await c.rateLimiter(c.req);
|
|
365
493
|
for (const [k, v] of Object.entries(check.headers)) c.res.setHeader(k, v);
|
|
366
494
|
if (check.limited) {
|
|
367
495
|
sendError(c.res, "RATE_LIMITED", "Too many requests", 429, void 0, c.requestId);
|
|
@@ -379,7 +507,7 @@ function applyRateLimit(c, method) {
|
|
|
379
507
|
async function tryServeAction(c) {
|
|
380
508
|
if (!c.url.startsWith("/api/__actions/")) return false;
|
|
381
509
|
c.res.setHeader(X_REQUEST_ID, c.requestId);
|
|
382
|
-
if (applyRateLimit(c, c.req.method ?? "POST")) return true;
|
|
510
|
+
if (await applyRateLimit(c, c.req.method ?? "POST")) return true;
|
|
383
511
|
const pathAfterPrefix = c.url.slice("/api/__actions/".length).split("?")[0];
|
|
384
512
|
const segments = pathAfterPrefix.split("/").filter(Boolean);
|
|
385
513
|
if (segments.length < 2) {
|
|
@@ -468,7 +596,7 @@ async function tryServeAgent(c) {
|
|
|
468
596
|
const urlPath = c.url.split("?")[0];
|
|
469
597
|
if (isApprovalPath(urlPath)) {
|
|
470
598
|
c.res.setHeader(X_REQUEST_ID, c.requestId);
|
|
471
|
-
if (applyRateLimit(c, c.req.method ?? "POST")) return true;
|
|
599
|
+
if (await applyRateLimit(c, c.req.method ?? "POST")) return true;
|
|
472
600
|
const method2 = (c.req.method ?? "POST").toUpperCase();
|
|
473
601
|
if (method2 !== "POST") {
|
|
474
602
|
sendError(
|
|
@@ -519,7 +647,7 @@ async function tryServeAgent(c) {
|
|
|
519
647
|
const agent = c.cachedAgents.find((a) => a.agentPath === urlPath);
|
|
520
648
|
if (!agent) return false;
|
|
521
649
|
c.res.setHeader(X_REQUEST_ID, c.requestId);
|
|
522
|
-
if (applyRateLimit(c, c.req.method ?? "POST")) return true;
|
|
650
|
+
if (await applyRateLimit(c, c.req.method ?? "POST")) return true;
|
|
523
651
|
const method = (c.req.method ?? "POST").toUpperCase();
|
|
524
652
|
if (method !== "POST") {
|
|
525
653
|
sendError(
|
|
@@ -571,7 +699,7 @@ async function tryServeAgent(c) {
|
|
|
571
699
|
async function tryServeApiRoute(c) {
|
|
572
700
|
if (!c.url.startsWith("/api/")) return false;
|
|
573
701
|
c.res.setHeader(X_REQUEST_ID, c.requestId);
|
|
574
|
-
if (applyRateLimit(c, c.req.method ?? "GET")) return true;
|
|
702
|
+
if (await applyRateLimit(c, c.req.method ?? "GET")) return true;
|
|
575
703
|
const match = matchRoute(c.url, c.cachedRoutes);
|
|
576
704
|
if (!match) {
|
|
577
705
|
if (c.controllersDistDir !== void 0) {
|
|
@@ -882,8 +1010,7 @@ async function startCommand(options) {
|
|
|
882
1010
|
wsRoutes: cachedWsRoutes,
|
|
883
1011
|
agents: cachedAgents
|
|
884
1012
|
} = loadRoutesAndActions(distDir, serverDir, config.agentsDir);
|
|
885
|
-
const
|
|
886
|
-
const rateLimiter = flatRateLimit ? createRateLimiter(flatRateLimit) : null;
|
|
1013
|
+
const rateLimiter = config.rateLimit ? createRouteRateLimiter(config.rateLimit) : null;
|
|
887
1014
|
const ssr = await setupSsr({
|
|
888
1015
|
distDir,
|
|
889
1016
|
indexHtml,
|
|
@@ -940,4 +1067,4 @@ export {
|
|
|
940
1067
|
resolveSsrEntry,
|
|
941
1068
|
startCommand
|
|
942
1069
|
};
|
|
943
|
-
//# sourceMappingURL=start-
|
|
1070
|
+
//# sourceMappingURL=start-KR2JJR7K.js.map
|