@graphoria/server 0.2.3 → 0.4.0
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 +29 -20
- package/dist/src/ai/mcp/index.d.ts +9 -2
- package/dist/src/ai/mcp/index.d.ts.map +1 -1
- package/dist/src/ai/tools/core.d.ts +6 -1
- package/dist/src/ai/tools/core.d.ts.map +1 -1
- package/dist/src/ai/tools/core.test.d.ts +2 -0
- package/dist/src/ai/tools/core.test.d.ts.map +1 -0
- package/dist/src/analyzeQuery/analyzers/selectionAnalyzer.d.ts.map +1 -1
- package/dist/src/analyzeQuery/costLimit.d.ts +30 -0
- package/dist/src/analyzeQuery/costLimit.d.ts.map +1 -0
- package/dist/src/analyzeQuery/costLimit.test.d.ts +2 -0
- package/dist/src/analyzeQuery/costLimit.test.d.ts.map +1 -0
- package/dist/src/analyzeQuery/depthLimit.d.ts.map +1 -1
- package/dist/src/analyzeQuery/fragments.d.ts +6 -0
- package/dist/src/analyzeQuery/fragments.d.ts.map +1 -0
- package/dist/src/authentication/capabilities.d.ts +18 -0
- package/dist/src/authentication/capabilities.d.ts.map +1 -0
- package/dist/src/authentication/capabilities.test.d.ts +2 -0
- package/dist/src/authentication/capabilities.test.d.ts.map +1 -0
- package/dist/src/authentication/jwt.d.ts.map +1 -1
- package/dist/src/authentication/paseto.d.ts.map +1 -1
- package/dist/src/authentication/secrets.d.ts +9 -0
- package/dist/src/authentication/secrets.d.ts.map +1 -0
- package/dist/src/authentication/secrets.test.d.ts +2 -0
- package/dist/src/authentication/secrets.test.d.ts.map +1 -0
- package/dist/src/config/types/auth.d.ts +8 -0
- package/dist/src/config/types/auth.d.ts.map +1 -1
- package/dist/src/config/types/db.d.ts +2 -0
- package/dist/src/config/types/db.d.ts.map +1 -1
- package/dist/src/config/types/operation.d.ts +4 -0
- package/dist/src/config/types/operation.d.ts.map +1 -1
- package/dist/src/config/types/operation.test.d.ts +2 -0
- package/dist/src/config/types/operation.test.d.ts.map +1 -0
- package/dist/src/configuration/crossReferences.d.ts +27 -0
- package/dist/src/configuration/crossReferences.d.ts.map +1 -0
- package/dist/src/configuration/crossReferences.test.d.ts +2 -0
- package/dist/src/configuration/crossReferences.test.d.ts.map +1 -0
- package/dist/src/configuration/getSchemas/index.d.ts +70 -6
- package/dist/src/configuration/getSchemas/index.d.ts.map +1 -1
- package/dist/src/configuration/getSchemas/mergeEntities/index.d.ts +3 -1
- package/dist/src/configuration/getSchemas/mergeEntities/index.d.ts.map +1 -1
- package/dist/src/configuration/gql/gqlAuthOperations.d.ts.map +1 -1
- package/dist/src/configuration/gql/handleGraphQLRequestFactory.d.ts +32 -2
- package/dist/src/configuration/gql/handleGraphQLRequestFactory.d.ts.map +1 -1
- package/dist/src/configuration/gql/handleGraphQLSubscriptionFactory.d.ts.map +1 -1
- package/dist/src/configuration/index.d.ts +41 -3
- package/dist/src/configuration/index.d.ts.map +1 -1
- package/dist/src/configuration/rest/generateOpenAPI.d.ts.map +1 -1
- package/dist/src/configuration/rest/generateOpenAPI.test.d.ts +2 -0
- package/dist/src/configuration/rest/generateOpenAPI.test.d.ts.map +1 -0
- package/dist/src/configuration/rest/handleRESTRequestFactory.d.ts.map +1 -1
- package/dist/src/configuration/rest/index.d.ts.map +1 -1
- package/dist/src/console/api.d.ts +7 -5
- package/dist/src/console/api.d.ts.map +1 -1
- package/dist/src/console/session.d.ts +29 -0
- package/dist/src/console/session.d.ts.map +1 -0
- package/dist/src/console/session.test.d.ts +2 -0
- package/dist/src/console/session.test.d.ts.map +1 -0
- package/dist/src/databases/common.d.ts +12 -4
- package/dist/src/databases/common.d.ts.map +1 -1
- package/dist/src/databases/core/executor.d.ts +7 -3
- package/dist/src/databases/core/executor.d.ts.map +1 -1
- package/dist/src/databases/core/function-mapping.d.ts +5 -4
- package/dist/src/databases/core/function-mapping.d.ts.map +1 -1
- package/dist/src/databases/core/procedure-arguments.d.ts +17 -0
- package/dist/src/databases/core/procedure-arguments.d.ts.map +1 -0
- package/dist/src/databases/core/procedure-arguments.test.d.ts +2 -0
- package/dist/src/databases/core/procedure-arguments.test.d.ts.map +1 -0
- package/dist/src/databases/core/query-builder.d.ts +7 -1
- package/dist/src/databases/core/query-builder.d.ts.map +1 -1
- package/dist/src/databases/engines/mssql/connection.d.ts +30 -7
- package/dist/src/databases/engines/mssql/connection.d.ts.map +1 -1
- package/dist/src/databases/engines/mssql/connection.test.d.ts +2 -0
- package/dist/src/databases/engines/mssql/connection.test.d.ts.map +1 -0
- package/dist/src/databases/engines/mssql/format.d.ts +2 -1
- package/dist/src/databases/engines/mssql/format.d.ts.map +1 -1
- package/dist/src/databases/engines/mssql/query/index.d.ts +3 -2
- package/dist/src/databases/engines/mssql/query/index.d.ts.map +1 -1
- package/dist/src/databases/engines/mysql/connection.d.ts +17 -1
- package/dist/src/databases/engines/mysql/connection.d.ts.map +1 -1
- package/dist/src/databases/engines/mysql/connection.test.d.ts +2 -0
- package/dist/src/databases/engines/mysql/connection.test.d.ts.map +1 -0
- package/dist/src/databases/engines/mysql/format.d.ts +2 -1
- package/dist/src/databases/engines/mysql/format.d.ts.map +1 -1
- package/dist/src/databases/engines/mysql/query/index.d.ts +3 -2
- package/dist/src/databases/engines/mysql/query/index.d.ts.map +1 -1
- package/dist/src/databases/engines/postgresql/connection.d.ts +26 -7
- package/dist/src/databases/engines/postgresql/connection.d.ts.map +1 -1
- package/dist/src/databases/engines/postgresql/connection.test.d.ts +2 -0
- package/dist/src/databases/engines/postgresql/connection.test.d.ts.map +1 -0
- package/dist/src/databases/engines/postgresql/format.d.ts +2 -1
- package/dist/src/databases/engines/postgresql/format.d.ts.map +1 -1
- package/dist/src/databases/engines/postgresql/query/index.d.ts +3 -2
- package/dist/src/databases/engines/postgresql/query/index.d.ts.map +1 -1
- package/dist/src/databases/high-level-operations.d.ts +2 -0
- package/dist/src/databases/high-level-operations.d.ts.map +1 -1
- package/dist/src/databases/transformers/data-transformers.d.ts +2 -0
- package/dist/src/databases/transformers/data-transformers.d.ts.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/logging/audit.d.ts +27 -0
- package/dist/src/logging/audit.d.ts.map +1 -0
- package/dist/src/logging/audit.test.d.ts +2 -0
- package/dist/src/logging/audit.test.d.ts.map +1 -0
- package/dist/src/singletons/env.d.ts +27 -4
- package/dist/src/singletons/env.d.ts.map +1 -1
- package/dist/src/singletons/queryTimeout.d.ts +3 -0
- package/dist/src/singletons/queryTimeout.d.ts.map +1 -0
- package/dist/src/subscriptions/strategies/database.d.ts.map +1 -1
- package/dist/src/subscriptions/strategies/database.test.d.ts +2 -0
- package/dist/src/subscriptions/strategies/database.test.d.ts.map +1 -0
- package/dist/src/subscriptions/types.d.ts +4 -1
- package/dist/src/subscriptions/types.d.ts.map +1 -1
- package/dist/src/subscriptions/utils/polling.d.ts.map +1 -1
- package/dist/src/types/env.d.ts +97 -12
- package/dist/src/types/env.d.ts.map +1 -1
- package/dist/src/types/zod/auth.d.ts +12 -0
- package/dist/src/types/zod/auth.d.ts.map +1 -1
- package/dist/src/types/zod/configuration.d.ts +28 -0
- package/dist/src/types/zod/configuration.d.ts.map +1 -1
- package/dist/src/utils/rateLimit.d.ts +65 -0
- package/dist/src/utils/rateLimit.d.ts.map +1 -0
- package/dist/src/utils/rateLimit.test.d.ts +2 -0
- package/dist/src/utils/rateLimit.test.d.ts.map +1 -0
- package/dist/src/utils/responses.d.ts +6 -0
- package/dist/src/utils/responses.d.ts.map +1 -1
- package/dist/src/utils/sessionVariables.d.ts +2 -0
- package/dist/src/utils/sessionVariables.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/playgrounds/console/index.html +7 -7
- package/playgrounds/graphiql/index.html +548 -685
- package/src/ai/mcp/index.ts +19 -14
- package/src/ai/tools/core.ts +30 -6
- package/src/analyzeQuery/analyzers/selectionAnalyzer.ts +2 -1
- package/src/analyzeQuery/costLimit.ts +236 -0
- package/src/analyzeQuery/depthLimit.ts +4 -23
- package/src/analyzeQuery/fragments.ts +18 -0
- package/src/authentication/capabilities.ts +54 -0
- package/src/authentication/index.ts +3 -3
- package/src/authentication/jwt.ts +30 -25
- package/src/authentication/paseto.ts +36 -26
- package/src/authentication/secrets.ts +30 -0
- package/src/config/types/auth.ts +7 -0
- package/src/config/types/db.ts +7 -0
- package/src/config/types/operation.ts +4 -0
- package/src/configuration/crossReferences.ts +340 -0
- package/src/configuration/getSchemas/mergeEntities/index.ts +0 -26
- package/src/configuration/getSchemas/type-definition-generator/index.ts +1 -1
- package/src/configuration/gql/buildExecute.ts +1 -1
- package/src/configuration/gql/gqlAuthOperations.ts +26 -0
- package/src/configuration/gql/handleGraphQLRequestFactory.ts +97 -15
- package/src/configuration/gql/handleGraphQLSubscriptionFactory.ts +31 -2
- package/src/configuration/index.ts +24 -6
- package/src/configuration/rest/generateOpenAPI.ts +61 -11
- package/src/configuration/rest/handleRESTRequestFactory.ts +32 -4
- package/src/configuration/rest/index.ts +11 -2
- package/src/console/api.ts +102 -16
- package/src/console/session.ts +129 -0
- package/src/databases/common.ts +55 -10
- package/src/databases/core/executor.ts +13 -4
- package/src/databases/core/function-mapping.ts +5 -1
- package/src/databases/core/procedure-arguments.ts +25 -0
- package/src/databases/core/query-builder.ts +7 -0
- package/src/databases/engines/mssql/connection.ts +66 -17
- package/src/databases/engines/mssql/format.ts +3 -1
- package/src/databases/engines/mssql/query/index.ts +12 -2
- package/src/databases/engines/mysql/connection.ts +40 -20
- package/src/databases/engines/mysql/format.ts +3 -1
- package/src/databases/engines/mysql/query/index.ts +25 -4
- package/src/databases/engines/postgresql/connection.ts +86 -22
- package/src/databases/engines/postgresql/format.ts +3 -1
- package/src/databases/engines/postgresql/query/index.ts +12 -3
- package/src/databases/high-level-operations.ts +13 -2
- package/src/index.ts +127 -19
- package/src/logging/audit.ts +86 -0
- package/src/singletons/queryTimeout.ts +20 -0
- package/src/subscriptions/strategies/database.ts +19 -3
- package/src/subscriptions/types.ts +4 -1
- package/src/subscriptions/utils/polling.ts +6 -3
- package/src/types/env.ts +47 -9
- package/src/utils/rateLimit.ts +209 -0
- package/src/utils/responses.ts +16 -0
- package/src/utils/sessionVariables.ts +2 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { LRUCache } from "lru-cache";
|
|
2
|
+
|
|
3
|
+
import { logger } from "../logging";
|
|
4
|
+
|
|
5
|
+
export type ConsumeResult = { allowed: boolean; retryAfterMs: number };
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* One method, everything per call: a role with its own limit shares the store
|
|
9
|
+
* with every other role rather than needing an instance of its own.
|
|
10
|
+
*/
|
|
11
|
+
export interface RateLimitStore {
|
|
12
|
+
consume(
|
|
13
|
+
key: string,
|
|
14
|
+
capacity: number,
|
|
15
|
+
refillPerMs: number,
|
|
16
|
+
ttlMs: number,
|
|
17
|
+
): Promise<ConsumeResult>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type Bucket = { tokens: number; ts: number };
|
|
21
|
+
|
|
22
|
+
export type MemoryRateLimitStoreOptions = {
|
|
23
|
+
now?: () => number;
|
|
24
|
+
maxKeys?: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const DEFAULT_MAX_KEYS = 10_000;
|
|
28
|
+
|
|
29
|
+
export const createMemoryRateLimitStore = ({
|
|
30
|
+
now = Date.now,
|
|
31
|
+
maxKeys = DEFAULT_MAX_KEYS,
|
|
32
|
+
}: MemoryRateLimitStoreOptions = {}): RateLimitStore => {
|
|
33
|
+
// The bound is load-bearing: an unbounded map keyed by client address is
|
|
34
|
+
// itself a memory-exhaustion vector. Evicting a bucket only ever gives its
|
|
35
|
+
// owner a full one, which is what an idle caller would have had anyway.
|
|
36
|
+
const buckets = new LRUCache<string, Bucket>({ max: maxKeys });
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
consume: async (key, capacity, refillPerMs, ttlMs) => {
|
|
40
|
+
const nowMs = now();
|
|
41
|
+
const bucket = buckets.get(key);
|
|
42
|
+
const tokens = bucket
|
|
43
|
+
? Math.min(capacity, bucket.tokens + (nowMs - bucket.ts) * refillPerMs)
|
|
44
|
+
: capacity;
|
|
45
|
+
|
|
46
|
+
if (tokens < 1) {
|
|
47
|
+
buckets.set(key, { tokens, ts: nowMs }, { ttl: ttlMs });
|
|
48
|
+
return { allowed: false, retryAfterMs: Math.ceil((1 - tokens) / refillPerMs) };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
buckets.set(key, { tokens: tokens - 1, ts: nowMs }, { ttl: ttlMs });
|
|
52
|
+
return { allowed: true, retryAfterMs: 0 };
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type RateLimitRedisClient = {
|
|
58
|
+
send(command: string, args: string[]): Promise<unknown>;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
type RateLimitLogger = { warn(obj: object, msg: string): void };
|
|
62
|
+
|
|
63
|
+
export type RedisRateLimitStoreOptions = {
|
|
64
|
+
now?: () => number;
|
|
65
|
+
log?: RateLimitLogger;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// KEYS[1] = bucket key, ARGV = capacity, refillPerMs, nowMs, ttlMs. Refill,
|
|
69
|
+
// read and write have to happen inside Redis: doing them from here would race
|
|
70
|
+
// between workers, which is the whole reason for reaching for Redis at all.
|
|
71
|
+
const CONSUME_SCRIPT = `
|
|
72
|
+
local b = redis.call('HMGET', KEYS[1], 'tokens', 'ts')
|
|
73
|
+
local tokens = tonumber(b[1]) or tonumber(ARGV[1])
|
|
74
|
+
local ts = tonumber(b[2]) or tonumber(ARGV[3])
|
|
75
|
+
tokens = math.min(tonumber(ARGV[1]), tokens + (tonumber(ARGV[3]) - ts) * tonumber(ARGV[2]))
|
|
76
|
+
local allowed = 0
|
|
77
|
+
if tokens >= 1 then tokens = tokens - 1; allowed = 1 end
|
|
78
|
+
redis.call('HMSET', KEYS[1], 'tokens', tokens, 'ts', ARGV[3])
|
|
79
|
+
redis.call('PEXPIRE', KEYS[1], ARGV[4])
|
|
80
|
+
return { allowed, tostring(tokens) }
|
|
81
|
+
`;
|
|
82
|
+
|
|
83
|
+
export const createRedisRateLimitStore = (
|
|
84
|
+
client: RateLimitRedisClient,
|
|
85
|
+
{ now = Date.now, log = logger("rate-limit") }: RedisRateLimitStoreOptions = {},
|
|
86
|
+
): RateLimitStore => {
|
|
87
|
+
let lastWarnMs: number | undefined;
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
consume: async (key, capacity, refillPerMs, ttlMs) => {
|
|
91
|
+
const nowMs = now();
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const reply = (await client.send("EVAL", [
|
|
95
|
+
CONSUME_SCRIPT,
|
|
96
|
+
"1",
|
|
97
|
+
key,
|
|
98
|
+
String(capacity),
|
|
99
|
+
String(refillPerMs),
|
|
100
|
+
String(nowMs),
|
|
101
|
+
String(ttlMs),
|
|
102
|
+
])) as [number | string, string];
|
|
103
|
+
|
|
104
|
+
const allowed = Number(reply[0]) === 1;
|
|
105
|
+
const tokens = Number(reply[1]);
|
|
106
|
+
|
|
107
|
+
return allowed
|
|
108
|
+
? { allowed: true, retryAfterMs: 0 }
|
|
109
|
+
: { allowed: false, retryAfterMs: Math.ceil((1 - tokens) / refillPerMs) };
|
|
110
|
+
} catch (error) {
|
|
111
|
+
// A dead cache must not take the API down, and a dead cache must not
|
|
112
|
+
// produce a log line per request either — the flood would outlast the
|
|
113
|
+
// outage.
|
|
114
|
+
if (lastWarnMs === undefined || nowMs - lastWarnMs >= ttlMs) {
|
|
115
|
+
lastWarnMs = nowMs;
|
|
116
|
+
log.warn({ err: error }, "rate limit store unavailable, allowing the request");
|
|
117
|
+
}
|
|
118
|
+
return { allowed: true, retryAfterMs: 0 };
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export type RateLimitSettings = {
|
|
125
|
+
max: number;
|
|
126
|
+
anonymousMax: number;
|
|
127
|
+
windowMs: number;
|
|
128
|
+
trustProxy: boolean;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
type RoleRateLimit = { rateLimit?: { max: number; windowMs?: number } | undefined };
|
|
132
|
+
|
|
133
|
+
export type RateLimiter = {
|
|
134
|
+
check(
|
|
135
|
+
session: { role?: string | undefined; sub?: string | undefined } | null,
|
|
136
|
+
address: string | undefined,
|
|
137
|
+
): Promise<ConsumeResult>;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export type CreateRateLimiterOptions = {
|
|
141
|
+
settings: RateLimitSettings;
|
|
142
|
+
anonymousRole: string;
|
|
143
|
+
permissions?: Record<string, RoleRateLimit | undefined>;
|
|
144
|
+
/** A factory is only called once a limit is configured, so a disabled
|
|
145
|
+
* limiter never constructs a store — or a Redis client. */
|
|
146
|
+
store?: RateLimitStore | (() => RateLimitStore);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Attacker-controlled once RATE_LIMIT_TRUST_PROXY is on, and it ends up in a
|
|
150
|
+
// store key, so it is capped before it gets there.
|
|
151
|
+
const MAX_ADDRESS_LENGTH = 64;
|
|
152
|
+
|
|
153
|
+
export const resolveClientAddress = (
|
|
154
|
+
req: Request,
|
|
155
|
+
server: { requestIP(req: Request): { address: string } | null } | undefined,
|
|
156
|
+
trustProxy: boolean,
|
|
157
|
+
): string | undefined => {
|
|
158
|
+
if (trustProxy) {
|
|
159
|
+
const forwarded = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim();
|
|
160
|
+
if (forwarded) return forwarded.slice(0, MAX_ADDRESS_LENGTH);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return server?.requestIP(req)?.address;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* `undefined` when nothing is configured, so the caller can skip the wrapper
|
|
168
|
+
* entirely and the shipped default costs nothing per request.
|
|
169
|
+
*/
|
|
170
|
+
export const createRateLimiter = ({
|
|
171
|
+
settings,
|
|
172
|
+
anonymousRole,
|
|
173
|
+
permissions = {},
|
|
174
|
+
store,
|
|
175
|
+
}: CreateRateLimiterOptions): RateLimiter | undefined => {
|
|
176
|
+
const configured =
|
|
177
|
+
settings.max > 0 ||
|
|
178
|
+
settings.anonymousMax > 0 ||
|
|
179
|
+
Object.values(permissions).some((permission) => (permission?.rateLimit?.max ?? 0) > 0);
|
|
180
|
+
|
|
181
|
+
if (!configured) return undefined;
|
|
182
|
+
|
|
183
|
+
const resolvedStore =
|
|
184
|
+
typeof store === "function" ? store() : (store ?? createMemoryRateLimitStore());
|
|
185
|
+
|
|
186
|
+
const limitFor = (role: string) => {
|
|
187
|
+
const roleLimit = permissions[role]?.rateLimit;
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
max: roleLimit?.max ?? (role === anonymousRole ? settings.anonymousMax : settings.max),
|
|
191
|
+
windowMs: roleLimit?.windowMs ?? settings.windowMs,
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
check: async (session, address) => {
|
|
197
|
+
const role = session?.role ?? anonymousRole;
|
|
198
|
+
const { max, windowMs } = limitFor(role);
|
|
199
|
+
|
|
200
|
+
if (max === 0) return { allowed: true, retryAfterMs: 0 };
|
|
201
|
+
|
|
202
|
+
const sub = session?.sub;
|
|
203
|
+
const key =
|
|
204
|
+
sub && sub !== "anonymous" ? `rl:${role}:${sub}` : `rl:${role}:ip:${address ?? "unknown"}`;
|
|
205
|
+
|
|
206
|
+
return resolvedStore.consume(key, max, max / windowMs, windowMs);
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
};
|
package/src/utils/responses.ts
CHANGED
|
@@ -29,12 +29,28 @@ export class S401 extends ClientResponse {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export class S403 extends ClientResponse {
|
|
33
|
+
constructor(body?: object | null, init?: ResponseInit) {
|
|
34
|
+
super(body, { ...init, status: 403 });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
export class S404 extends ClientResponse {
|
|
33
39
|
constructor(body?: object | null, init?: ResponseInit) {
|
|
34
40
|
super(body, { ...init, status: 404 });
|
|
35
41
|
}
|
|
36
42
|
}
|
|
37
43
|
|
|
44
|
+
export class S429 extends ClientResponse {
|
|
45
|
+
constructor(retryAfterMs: number) {
|
|
46
|
+
super({ errors: [{ message: "Rate limit exceeded" }] }, { status: 429 });
|
|
47
|
+
|
|
48
|
+
// Seconds, and never 0: a caller told to retry immediately retries
|
|
49
|
+
// immediately, which is the traffic the limit is there to stop.
|
|
50
|
+
this.headers.set("Retry-After", String(Math.max(1, Math.ceil(retryAfterMs / 1000))));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
38
54
|
export class S500 extends ClientResponse {
|
|
39
55
|
constructor(body?: object | null, init?: ResponseInit) {
|
|
40
56
|
super(body, { ...init, status: 500 });
|