@statoss/sdk 0.0.0-stage → 0.1.1

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.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +411 -2
  3. package/dist/cjs/client.d.ts +50 -0
  4. package/dist/cjs/client.js +462 -0
  5. package/dist/cjs/dsn.d.ts +25 -0
  6. package/dist/cjs/dsn.js +62 -0
  7. package/dist/cjs/envelope.d.ts +36 -0
  8. package/dist/cjs/envelope.js +55 -0
  9. package/dist/cjs/event.d.ts +9 -0
  10. package/dist/cjs/event.js +128 -0
  11. package/dist/cjs/hub.d.ts +37 -0
  12. package/dist/cjs/hub.js +119 -0
  13. package/dist/cjs/index.d.ts +38 -0
  14. package/dist/cjs/index.js +107 -0
  15. package/dist/cjs/integrations/browser.d.ts +7 -0
  16. package/dist/cjs/integrations/browser.js +124 -0
  17. package/dist/cjs/integrations/console.d.ts +3 -0
  18. package/dist/cjs/integrations/console.js +106 -0
  19. package/dist/cjs/integrations/dedupe.d.ts +7 -0
  20. package/dist/cjs/integrations/dedupe.js +32 -0
  21. package/dist/cjs/integrations/fetch.d.ts +3 -0
  22. package/dist/cjs/integrations/fetch.js +69 -0
  23. package/dist/cjs/integrations/handlers.d.ts +9 -0
  24. package/dist/cjs/integrations/handlers.js +192 -0
  25. package/dist/cjs/levels.d.ts +20 -0
  26. package/dist/cjs/levels.js +77 -0
  27. package/dist/cjs/logger.d.ts +18 -0
  28. package/dist/cjs/logger.js +38 -0
  29. package/dist/cjs/package.json +1 -0
  30. package/dist/cjs/ratelimit.d.ts +11 -0
  31. package/dist/cjs/ratelimit.js +50 -0
  32. package/dist/cjs/runtime.d.ts +42 -0
  33. package/dist/cjs/runtime.js +188 -0
  34. package/dist/cjs/scope.d.ts +30 -0
  35. package/dist/cjs/scope.js +127 -0
  36. package/dist/cjs/stack.d.ts +9 -0
  37. package/dist/cjs/stack.js +129 -0
  38. package/dist/cjs/transport.d.ts +40 -0
  39. package/dist/cjs/transport.js +218 -0
  40. package/dist/cjs/types.d.ts +180 -0
  41. package/dist/cjs/types.js +2 -0
  42. package/dist/cjs/utils.d.ts +27 -0
  43. package/dist/cjs/utils.js +227 -0
  44. package/dist/cjs/version.d.ts +3 -0
  45. package/dist/cjs/version.js +7 -0
  46. package/dist/esm/client.d.ts +50 -0
  47. package/dist/esm/client.js +458 -0
  48. package/dist/esm/dsn.d.ts +25 -0
  49. package/dist/esm/dsn.js +55 -0
  50. package/dist/esm/envelope.d.ts +36 -0
  51. package/dist/esm/envelope.js +49 -0
  52. package/dist/esm/event.d.ts +9 -0
  53. package/dist/esm/event.js +124 -0
  54. package/dist/esm/hub.d.ts +37 -0
  55. package/dist/esm/hub.js +111 -0
  56. package/dist/esm/index.d.ts +38 -0
  57. package/dist/esm/index.js +88 -0
  58. package/dist/esm/integrations/browser.d.ts +7 -0
  59. package/dist/esm/integrations/browser.js +119 -0
  60. package/dist/esm/integrations/console.d.ts +3 -0
  61. package/dist/esm/integrations/console.js +103 -0
  62. package/dist/esm/integrations/dedupe.d.ts +7 -0
  63. package/dist/esm/integrations/dedupe.js +29 -0
  64. package/dist/esm/integrations/fetch.d.ts +3 -0
  65. package/dist/esm/integrations/fetch.js +66 -0
  66. package/dist/esm/integrations/handlers.d.ts +9 -0
  67. package/dist/esm/integrations/handlers.js +188 -0
  68. package/dist/esm/levels.d.ts +20 -0
  69. package/dist/esm/levels.js +69 -0
  70. package/dist/esm/logger.d.ts +18 -0
  71. package/dist/esm/logger.js +33 -0
  72. package/dist/esm/ratelimit.d.ts +11 -0
  73. package/dist/esm/ratelimit.js +45 -0
  74. package/dist/esm/runtime.d.ts +42 -0
  75. package/dist/esm/runtime.js +181 -0
  76. package/dist/esm/scope.d.ts +30 -0
  77. package/dist/esm/scope.js +123 -0
  78. package/dist/esm/stack.d.ts +9 -0
  79. package/dist/esm/stack.js +125 -0
  80. package/dist/esm/transport.d.ts +40 -0
  81. package/dist/esm/transport.js +213 -0
  82. package/dist/esm/types.d.ts +180 -0
  83. package/dist/esm/types.js +1 -0
  84. package/dist/esm/utils.d.ts +27 -0
  85. package/dist/esm/utils.js +211 -0
  86. package/dist/esm/version.d.ts +3 -0
  87. package/dist/esm/version.js +4 -0
  88. package/package.json +48 -4
@@ -0,0 +1,66 @@
1
+ import { isObject, isThenable, safely, scrubUrl } from "../utils.js";
2
+ function describe(args) {
3
+ const [input, init] = args;
4
+ let method = "GET";
5
+ let url = "";
6
+ if (typeof input === "string") {
7
+ url = input;
8
+ }
9
+ else if (isObject(input)) {
10
+ if (typeof input.url === "string")
11
+ url = input.url;
12
+ else if (typeof input.href === "string")
13
+ url = input.href;
14
+ if (typeof input.method === "string")
15
+ method = input.method;
16
+ }
17
+ else if (input !== undefined) {
18
+ url = String(input);
19
+ }
20
+ if (isObject(init) && typeof init.method === "string")
21
+ method = init.method;
22
+ return { method: method.toUpperCase(), url };
23
+ }
24
+ function levelFor(status) {
25
+ if (status >= 500)
26
+ return "error";
27
+ if (status >= 400)
28
+ return "warning";
29
+ return "info";
30
+ }
31
+ /** Breadcrumbs for fetch calls: method, URL without secrets, status. */
32
+ export function installFetch(client) {
33
+ const g = globalThis;
34
+ const original = g.fetch;
35
+ if (typeof original !== "function")
36
+ return undefined;
37
+ const wrapper = function (...args) {
38
+ const request = safely(() => describe(args));
39
+ const result = original.apply(globalThis, args);
40
+ if (!request || !isThenable(result) || client.isOwnUrl(request.url))
41
+ return result;
42
+ const data = { method: request.method, url: scrubUrl(request.url) };
43
+ return result.then((response) => {
44
+ safely(() => {
45
+ const status = isObject(response) && typeof response.status === "number"
46
+ ? response.status
47
+ : 0;
48
+ client.addBreadcrumb({
49
+ type: "http",
50
+ category: "fetch",
51
+ level: levelFor(status),
52
+ data: { ...data, status_code: status },
53
+ }, { input: args, response });
54
+ });
55
+ return response;
56
+ }, (error) => {
57
+ safely(() => client.addBreadcrumb({ type: "http", category: "fetch", level: "error", data }, { input: args, error }));
58
+ throw error;
59
+ });
60
+ };
61
+ g.fetch = wrapper;
62
+ return () => {
63
+ if (g.fetch === wrapper)
64
+ g.fetch = original;
65
+ };
66
+ }
@@ -0,0 +1,9 @@
1
+ import type { Client } from "../client.js";
2
+ import { type ProcessLike } from "../runtime.js";
3
+ /**
4
+ * Uncaught errors and unhandled rejections, per runtime. Edge runtimes have
5
+ * no global hook, so there the app captures in its handlers.
6
+ */
7
+ export declare function installGlobalHandlers(client: Client): (() => void) | undefined;
8
+ /** Node's --unhandled-rejections mode from NODE_OPTIONS and the command line (which wins); "throw" by default. */
9
+ export declare function rejectionMode(proc: ProcessLike): string;
@@ -0,0 +1,188 @@
1
+ import { printError } from "../logger.js";
2
+ import { getProcess } from "../runtime.js";
3
+ import { frameAt } from "../stack.js";
4
+ import { isErrorLike, isObject, safely, safeString, truncate, } from "../utils.js";
5
+ const SHUTDOWN_TIMEOUT_MS = 2000;
6
+ /**
7
+ * Uncaught errors and unhandled rejections, per runtime. Edge runtimes have
8
+ * no global hook, so there the app captures in its handlers.
9
+ */
10
+ export function installGlobalHandlers(client) {
11
+ switch (client.runtime.name) {
12
+ case "node":
13
+ case "bun":
14
+ return processHandlers(client);
15
+ case "deno":
16
+ return eventHandlers(client, true);
17
+ case "browser":
18
+ case "worker":
19
+ return eventHandlers(client, false);
20
+ default:
21
+ return undefined;
22
+ }
23
+ }
24
+ /** Node's --unhandled-rejections mode from NODE_OPTIONS and the command line (which wins); "throw" by default. */
25
+ export function rejectionMode(proc) {
26
+ const flags = [
27
+ ...String(safely(() => proc.env?.NODE_OPTIONS) ?? "").split(/\s+/),
28
+ ...(safely(() => proc.execArgv) ?? []),
29
+ ];
30
+ let mode = "throw";
31
+ for (let i = 0; i < flags.length; i++) {
32
+ const flag = flags[i];
33
+ if (flag.startsWith("--unhandled-rejections=")) {
34
+ mode = flag.slice("--unhandled-rejections=".length);
35
+ }
36
+ else if (flag === "--unhandled-rejections" && i + 1 < flags.length) {
37
+ mode = flags[++i];
38
+ }
39
+ }
40
+ return ["strict", "warn", "none", "warn-with-error-code"].includes(mode)
41
+ ? mode
42
+ : "throw";
43
+ }
44
+ /** The error Node raises for a rejection with a value that is not an Error. */
45
+ function rejectionError(reason) {
46
+ const error = new Error(`This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "${truncate(safeString(reason), 200)}".`);
47
+ error.name = "UnhandledPromiseRejection";
48
+ error.code = "ERR_UNHANDLED_REJECTION";
49
+ return error;
50
+ }
51
+ /**
52
+ * Node and Bun. A listener on uncaughtException or unhandledRejection
53
+ * switches off the runtime's own handling, so the SDK does what the runtime
54
+ * would have done, after sending:
55
+ * - uncaught exception: print it and exit with 1, unless the app has its own
56
+ * listener;
57
+ * - unhandled rejection, by Node's --unhandled-rejections mode: "throw" (the
58
+ * default) raises it as an uncaught exception, so the app's listener gets
59
+ * it or the process exits with 1; "warn-with-error-code" prints it and sets
60
+ * the exit code to 1; "warn" and "none" keep running; "strict" is raised by
61
+ * Node itself. An app with its own unhandledRejection listener decides.
62
+ */
63
+ function processHandlers(client) {
64
+ const proc = getProcess();
65
+ if (!proc ||
66
+ typeof proc.on !== "function" ||
67
+ typeof proc.listeners !== "function") {
68
+ return undefined;
69
+ }
70
+ const mode = rejectionMode(proc);
71
+ let shuttingDown = false;
72
+ let reraised = undefined;
73
+ const others = (event, own) => (proc.listeners?.(event) ?? []).filter((l) => l !== own && l.name !== "domainUncaughtExceptionClear").length;
74
+ const shutdown = (error) => {
75
+ if (shuttingDown)
76
+ return;
77
+ shuttingDown = true;
78
+ // First: if nothing keeps the process alive while sending, it still ends with 1.
79
+ safely(() => {
80
+ proc.exitCode = 1;
81
+ });
82
+ printError(error);
83
+ const exit = () => {
84
+ safely(() => proc.exit?.(1));
85
+ };
86
+ client.flush(SHUTDOWN_TIMEOUT_MS).then(exit, exit);
87
+ };
88
+ const onUncaught = (error, origin) => {
89
+ // A rejection passed on by onRejection below was captured there.
90
+ if (reraised === undefined || error !== reraised) {
91
+ const fromRejection = origin === "unhandledRejection";
92
+ client.captureUnhandled(error, {
93
+ type: fromRejection ? "onunhandledrejection" : "onuncaughtexception",
94
+ handled: false,
95
+ }, "fatal", { rejection: fromRejection });
96
+ }
97
+ if (others("uncaughtException", onUncaught) > 0)
98
+ return;
99
+ shutdown(error);
100
+ };
101
+ const onRejection = (reason) => {
102
+ // In strict mode Node has already raised it as an uncaught exception.
103
+ if (mode === "strict")
104
+ return;
105
+ const ownListener = others("unhandledRejection", onRejection) > 0;
106
+ const crashes = mode === "throw" &&
107
+ !ownListener &&
108
+ others("uncaughtException", onUncaught) === 0;
109
+ client.captureUnhandled(reason, { type: "onunhandledrejection", handled: false }, crashes ? "fatal" : "error", { rejection: true });
110
+ if (ownListener)
111
+ return;
112
+ if (mode === "throw") {
113
+ const error = isErrorLike(reason) ? reason : rejectionError(reason);
114
+ reraised = error;
115
+ safely(() => proc.emit?.("uncaughtException", error, "unhandledRejection"));
116
+ reraised = undefined;
117
+ }
118
+ else if (mode === "warn-with-error-code") {
119
+ printError("Unhandled promise rejection:", reason);
120
+ safely(() => {
121
+ proc.exitCode = 1;
122
+ });
123
+ }
124
+ };
125
+ proc.on("uncaughtException", onUncaught);
126
+ proc.on("unhandledRejection", onRejection);
127
+ return () => {
128
+ const off = proc.off ?? proc.removeListener;
129
+ safely(() => off?.call(proc, "uncaughtException", onUncaught));
130
+ safely(() => off?.call(proc, "unhandledRejection", onRejection));
131
+ };
132
+ }
133
+ /**
134
+ * Browsers, web workers and Deno: the error and unhandledrejection events.
135
+ * Deno exits on them, so there the SDK prevents that, sends, and then throws
136
+ * the error again for Deno to print and exit as it would have, as Sentry's
137
+ * Deno SDK does. A listener of the app's that already called preventDefault
138
+ * keeps the process running.
139
+ */
140
+ function eventHandlers(client, deno) {
141
+ const target = globalThis;
142
+ if (typeof target.addEventListener !== "function")
143
+ return undefined;
144
+ let exiting = false;
145
+ const crash = (event, error) => {
146
+ if (!deno || event.defaultPrevented)
147
+ return;
148
+ safely(() => event.preventDefault?.());
149
+ exiting = true;
150
+ const rethrow = () => {
151
+ throw error;
152
+ };
153
+ void client.flush(SHUTDOWN_TIMEOUT_MS).then(rethrow, rethrow);
154
+ };
155
+ const onError = (event) => {
156
+ if (exiting || !isObject(event))
157
+ return;
158
+ const error = event.error;
159
+ const message = typeof event.message === "string" && event.message
160
+ ? event.message
161
+ : undefined;
162
+ const filename = typeof event.filename === "string" ? event.filename : undefined;
163
+ // Cross-origin scripts give only "Script error." with a null error.
164
+ client.captureUnhandled(error !== undefined && error !== null
165
+ ? error
166
+ : (message ?? "Unknown error"), { type: "onerror", handled: false }, deno ? "fatal" : "error", {
167
+ frame: filename && !isErrorLike(error)
168
+ ? frameAt(filename, typeof event.lineno === "number" ? event.lineno : undefined, typeof event.colno === "number" ? event.colno : undefined)
169
+ : undefined,
170
+ });
171
+ crash(event, error);
172
+ };
173
+ const onRejection = (event) => {
174
+ if (exiting || !isObject(event))
175
+ return;
176
+ const reason = "reason" in event
177
+ ? event.reason
178
+ : event.detail?.reason;
179
+ client.captureUnhandled(reason, { type: "onunhandledrejection", handled: false }, deno ? "fatal" : "error", { rejection: true });
180
+ crash(event, reason);
181
+ };
182
+ target.addEventListener("error", onError);
183
+ target.addEventListener("unhandledrejection", onRejection);
184
+ return () => {
185
+ safely(() => target.removeEventListener?.("error", onError));
186
+ safely(() => target.removeEventListener?.("unhandledrejection", onRejection));
187
+ };
188
+ }
@@ -0,0 +1,20 @@
1
+ import type { Logger } from "./logger.js";
2
+ import type { SentryLevel } from "./types.js";
3
+ export declare const SENTRY_LEVELS: readonly SentryLevel[];
4
+ export declare const LEVEL_TAG = "statoss.level";
5
+ export declare const CATEGORY_TAG = "statoss.category";
6
+ export declare function isSentryLevel(value: unknown): value is SentryLevel;
7
+ export declare function isValidName(value: unknown): value is string;
8
+ /** Keeps the entries of init's `levels` that are usable, logging the rest in debug mode. */
9
+ export declare function readLevels(input: unknown, logger: Logger): Record<string, SentryLevel>;
10
+ export interface LevelChoice {
11
+ level: SentryLevel;
12
+ /** The custom name, when the level was one. */
13
+ custom?: string;
14
+ }
15
+ /**
16
+ * Picks the first usable level from candidates in order of precedence. A
17
+ * custom name is sent as its mapped Sentry level (error when unmapped).
18
+ */
19
+ export declare function chooseLevel(candidates: unknown[], levels: Record<string, SentryLevel>, fallback: SentryLevel, logger: Logger): LevelChoice;
20
+ export declare function readCategory(value: unknown, logger: Logger): string | undefined;
@@ -0,0 +1,69 @@
1
+ import { safeString } from "./utils.js";
2
+ export const SENTRY_LEVELS = [
3
+ "fatal",
4
+ "error",
5
+ "warning",
6
+ "info",
7
+ "debug",
8
+ ];
9
+ /** Custom level and category names: what the server accepts as tag values to filter on. */
10
+ const NAME_RE = /^[a-z0-9._-]{1,64}$/;
11
+ export const LEVEL_TAG = "statoss.level";
12
+ export const CATEGORY_TAG = "statoss.category";
13
+ export function isSentryLevel(value) {
14
+ return (typeof value === "string" &&
15
+ SENTRY_LEVELS.includes(value));
16
+ }
17
+ export function isValidName(value) {
18
+ return typeof value === "string" && NAME_RE.test(value);
19
+ }
20
+ /** Keeps the entries of init's `levels` that are usable, logging the rest in debug mode. */
21
+ export function readLevels(input, logger) {
22
+ const out = {};
23
+ if (input === undefined || input === null)
24
+ return out;
25
+ if (typeof input !== "object") {
26
+ logger.warn('Ignored `levels`: it should be an object such as { "payment-failed": "error" }.');
27
+ return out;
28
+ }
29
+ for (const [name, level] of Object.entries(input)) {
30
+ if (isSentryLevel(name)) {
31
+ logger.warn(`Ignored level "${name}" in \`levels\`: it is already a Sentry level.`);
32
+ }
33
+ else if (!isValidName(name)) {
34
+ logger.warn(`Ignored level "${name}" in \`levels\`: use 1 to 64 lowercase letters, digits, dots, dashes or underscores.`);
35
+ }
36
+ else if (!isSentryLevel(level)) {
37
+ logger.warn(`Ignored level "${name}" in \`levels\`: map it to fatal, error, warning, info or debug.`);
38
+ }
39
+ else {
40
+ out[name] = level;
41
+ }
42
+ }
43
+ return out;
44
+ }
45
+ /**
46
+ * Picks the first usable level from candidates in order of precedence. A
47
+ * custom name is sent as its mapped Sentry level (error when unmapped).
48
+ */
49
+ export function chooseLevel(candidates, levels, fallback, logger) {
50
+ for (const candidate of candidates) {
51
+ if (candidate === undefined || candidate === null)
52
+ continue;
53
+ if (isSentryLevel(candidate))
54
+ return { level: candidate };
55
+ if (isValidName(candidate)) {
56
+ return { level: levels[candidate] ?? "error", custom: candidate };
57
+ }
58
+ logger.warn(`Ignored level "${safeString(candidate)}": use a Sentry level or 1 to 64 lowercase letters, digits, dots, dashes or underscores.`);
59
+ }
60
+ return { level: fallback };
61
+ }
62
+ export function readCategory(value, logger) {
63
+ if (value === undefined || value === null)
64
+ return undefined;
65
+ if (isValidName(value))
66
+ return value;
67
+ logger.warn(`Ignored category "${safeString(value)}": use 1 to 64 lowercase letters, digits, dots, dashes or underscores.`);
68
+ return undefined;
69
+ }
@@ -0,0 +1,18 @@
1
+ export type ConsoleMethod = "debug" | "info" | "warn" | "error" | "log" | "assert";
2
+ type ConsoleFn = (...args: unknown[]) => void;
3
+ /** The console methods as they were before the console integration wrapped them. */
4
+ export declare const originalConsole: Partial<Record<ConsoleMethod, ConsoleFn>>;
5
+ export interface Logger {
6
+ readonly enabled: boolean;
7
+ log(...args: unknown[]): void;
8
+ warn(...args: unknown[]): void;
9
+ error(...args: unknown[]): void;
10
+ }
11
+ /** Writes only when debug is on, and never through the wrapped console. */
12
+ export declare function makeLogger(enabled: boolean): Logger;
13
+ /**
14
+ * Prints an error the way the runtime would have if the SDK had not taken
15
+ * over its crash handling. Not a debug message, so it ignores `debug`.
16
+ */
17
+ export declare function printError(...args: unknown[]): void;
18
+ export {};
@@ -0,0 +1,33 @@
1
+ /** The console methods as they were before the console integration wrapped them. */
2
+ export const originalConsole = {};
3
+ function call(method, args) {
4
+ try {
5
+ const c = globalThis
6
+ .console;
7
+ const fn = originalConsole[method] ?? c?.[method];
8
+ if (typeof fn === "function")
9
+ fn.apply(c, args);
10
+ }
11
+ catch {
12
+ // nowhere left to report to
13
+ }
14
+ }
15
+ /** Writes only when debug is on, and never through the wrapped console. */
16
+ export function makeLogger(enabled) {
17
+ const write = (method) => enabled
18
+ ? (...args) => call(method, ["[StatOSS]", ...args])
19
+ : () => undefined;
20
+ return {
21
+ enabled,
22
+ log: write("log"),
23
+ warn: write("warn"),
24
+ error: write("error"),
25
+ };
26
+ }
27
+ /**
28
+ * Prints an error the way the runtime would have if the SDK had not taken
29
+ * over its crash handling. Not a debug message, so it ignores `debug`.
30
+ */
31
+ export function printError(...args) {
32
+ call("error", args);
33
+ }
@@ -0,0 +1,11 @@
1
+ /** Per category (or "all"), the time in ms until which nothing is sent. */
2
+ export type RateLimits = Record<string, number>;
3
+ export declare function parseRetryAfter(header: string, now: number): number;
4
+ /**
5
+ * Applies a response's limits as Sentry's SDKs do. X-Sentry-Rate-Limits
6
+ * entries are "seconds:categories:scope:reason", categories split by ";" and
7
+ * empty for all. Its scope (key, project, organization) and reason (such as
8
+ * usage_exceeded) do not change what the client does: wait, then try again.
9
+ */
10
+ export declare function updateRateLimits(limits: RateLimits, status: number, rateLimitHeader: string | null | undefined, retryAfterHeader: string | null | undefined, now: number): RateLimits;
11
+ export declare function isRateLimited(limits: RateLimits, category: string, now: number): boolean;
@@ -0,0 +1,45 @@
1
+ const DEFAULT_RETRY_MS = 60000;
2
+ export function parseRetryAfter(header, now) {
3
+ const seconds = parseInt(header, 10);
4
+ if (!Number.isNaN(seconds))
5
+ return seconds * 1000;
6
+ const date = Date.parse(header);
7
+ if (!Number.isNaN(date))
8
+ return date - now;
9
+ return DEFAULT_RETRY_MS;
10
+ }
11
+ /**
12
+ * Applies a response's limits as Sentry's SDKs do. X-Sentry-Rate-Limits
13
+ * entries are "seconds:categories:scope:reason", categories split by ";" and
14
+ * empty for all. Its scope (key, project, organization) and reason (such as
15
+ * usage_exceeded) do not change what the client does: wait, then try again.
16
+ */
17
+ export function updateRateLimits(limits, status, rateLimitHeader, retryAfterHeader, now) {
18
+ const next = { ...limits };
19
+ if (rateLimitHeader) {
20
+ for (const entry of rateLimitHeader.trim().split(",")) {
21
+ const [retryAfter, categories] = entry.trim().split(":", 2);
22
+ const seconds = parseInt(retryAfter, 10);
23
+ const until = now + (Number.isNaN(seconds) ? 60 : seconds) * 1000;
24
+ if (!categories) {
25
+ next.all = until;
26
+ }
27
+ else {
28
+ for (const category of categories.split(";")) {
29
+ if (category)
30
+ next[category] = until;
31
+ }
32
+ }
33
+ }
34
+ }
35
+ else if (retryAfterHeader) {
36
+ next.all = now + parseRetryAfter(retryAfterHeader, now);
37
+ }
38
+ else if (status === 429) {
39
+ next.all = now + DEFAULT_RETRY_MS;
40
+ }
41
+ return next;
42
+ }
43
+ export function isRateLimited(limits, category, now) {
44
+ return (limits[category] || limits.all || 0) > now;
45
+ }
@@ -0,0 +1,42 @@
1
+ export type RuntimeName = "browser" | "worker" | "node" | "bun" | "deno" | "cloudflare" | "vercel-edge" | "unknown";
2
+ export interface RuntimeInfo {
3
+ name: RuntimeName;
4
+ platform: "javascript" | "node";
5
+ /** Browsers and web workers: key in the query, keepalive, page context. */
6
+ browserLike: boolean;
7
+ serverName?: string;
8
+ contexts: Record<string, Record<string, unknown>>;
9
+ }
10
+ /** The parts of Node's process the SDK reads, all optional. */
11
+ export interface ProcessLike {
12
+ version?: string;
13
+ versions?: Record<string, string | undefined>;
14
+ platform?: string;
15
+ arch?: string;
16
+ getBuiltinModule?: (id: string) => unknown;
17
+ on?: (event: string, listener: (...args: never[]) => void) => unknown;
18
+ off?: (event: string, listener: (...args: never[]) => void) => unknown;
19
+ removeListener?: (event: string, listener: (...args: never[]) => void) => unknown;
20
+ listeners?: (event: string) => ((...args: never[]) => void)[];
21
+ emit?: (event: string, ...args: unknown[]) => boolean;
22
+ exit?: (code?: number) => never;
23
+ exitCode?: number | string;
24
+ execArgv?: string[];
25
+ env?: Record<string, string | undefined>;
26
+ }
27
+ export declare function getProcess(): ProcessLike | undefined;
28
+ /** A Node built-in without a static import, so bundlers for the browser never see one. */
29
+ export declare function getBuiltin<T>(id: string): T | undefined;
30
+ export declare function detectRuntime(): RuntimeName;
31
+ /** Browser and OS from a user agent, for the common browsers only. */
32
+ export declare function parseUserAgent(ua: string): {
33
+ browser?: {
34
+ name: string;
35
+ version: string;
36
+ };
37
+ os?: {
38
+ name: string;
39
+ version?: string;
40
+ };
41
+ };
42
+ export declare function describeRuntime(name?: RuntimeName): RuntimeInfo;