@rasputin-ai/core 0.1.2
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/announce-deploy/announce-deploy.d.ts +19 -0
- package/dist/announce-deploy/announce-deploy.d.ts.map +1 -0
- package/dist/create-client/create-client-types.d.ts +110 -0
- package/dist/create-client/create-client-types.d.ts.map +1 -0
- package/dist/create-client/create-client.d.ts +9 -0
- package/dist/create-client/create-client.d.ts.map +1 -0
- package/dist/create-client/is-client-enabled.d.ts +5 -0
- package/dist/create-client/is-client-enabled.d.ts.map +1 -0
- package/dist/create-client/resolve-endpoint-urls.d.ts +7 -0
- package/dist/create-client/resolve-endpoint-urls.d.ts.map +1 -0
- package/dist/create-ingest-event/create-ingest-event-types.d.ts +47 -0
- package/dist/create-ingest-event/create-ingest-event-types.d.ts.map +1 -0
- package/dist/create-ingest-event/create-ingest-event.d.ts +9 -0
- package/dist/create-ingest-event/create-ingest-event.d.ts.map +1 -0
- package/dist/create-ingest-event/finalize-resolution.d.ts +4 -0
- package/dist/create-ingest-event/finalize-resolution.d.ts.map +1 -0
- package/dist/detect-release/detect-release.d.ts +13 -0
- package/dist/detect-release/detect-release.d.ts.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1013 -0
- package/dist/normalize-frames/normalize-frames.d.ts +24 -0
- package/dist/normalize-frames/normalize-frames.d.ts.map +1 -0
- package/dist/parse-stack/parse-stack.d.ts +4 -0
- package/dist/parse-stack/parse-stack.d.ts.map +1 -0
- package/dist/parse-stack/raw-frame.d.ts +8 -0
- package/dist/parse-stack/raw-frame.d.ts.map +1 -0
- package/dist/resolve-source-maps/resolve-source-maps.d.ts +7 -0
- package/dist/resolve-source-maps/resolve-source-maps.d.ts.map +1 -0
- package/dist/resolve-source-maps/resolved-frame.d.ts +25 -0
- package/dist/resolve-source-maps/resolved-frame.d.ts.map +1 -0
- package/dist/sdk-meta.d.ts +4 -0
- package/dist/sdk-meta.d.ts.map +1 -0
- package/dist/suppression/compute-burst-key.d.ts +7 -0
- package/dist/suppression/compute-burst-key.d.ts.map +1 -0
- package/dist/suppression/create-suppression.d.ts +12 -0
- package/dist/suppression/create-suppression.d.ts.map +1 -0
- package/dist/suppression/identity-needs-message-template.d.ts +6 -0
- package/dist/suppression/identity-needs-message-template.d.ts.map +1 -0
- package/dist/suppression/parameterize-message.d.ts +2 -0
- package/dist/suppression/parameterize-message.d.ts.map +1 -0
- package/dist/suppression/suppression-types.d.ts +46 -0
- package/dist/suppression/suppression-types.d.ts.map +1 -0
- package/dist/transport/create-transport.d.ts +3 -0
- package/dist/transport/create-transport.d.ts.map +1 -0
- package/dist/transport/send-batch.d.ts +19 -0
- package/dist/transport/send-batch.d.ts.map +1 -0
- package/dist/transport/transport-queue.d.ts +9 -0
- package/dist/transport/transport-queue.d.ts.map +1 -0
- package/dist/transport/transport-types.d.ts +53 -0
- package/dist/transport/transport-types.d.ts.map +1 -0
- package/package.json +40 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1013 @@
|
|
|
1
|
+
// src/announce-deploy/announce-deploy.ts
|
|
2
|
+
var announceDeploy = async (options) => {
|
|
3
|
+
if (options.enabled === false) return "skipped";
|
|
4
|
+
const release = options.release?.trim();
|
|
5
|
+
const environment = options.environment.trim();
|
|
6
|
+
if (!release || !environment) return "skipped";
|
|
7
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
8
|
+
try {
|
|
9
|
+
const response = await fetchFn(options.deployUrl, {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers: {
|
|
12
|
+
Authorization: `Bearer ${options.projectApiKey}`,
|
|
13
|
+
"Content-Type": "application/json",
|
|
14
|
+
Accept: "application/json"
|
|
15
|
+
},
|
|
16
|
+
body: JSON.stringify({ release, environment })
|
|
17
|
+
});
|
|
18
|
+
if (response.ok) return "ok";
|
|
19
|
+
return "failed";
|
|
20
|
+
} catch {
|
|
21
|
+
return "failed";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// src/create-ingest-event/create-ingest-event.ts
|
|
26
|
+
var MAX_MESSAGE_BYTES = 8 * 1024;
|
|
27
|
+
var MAX_STACK_FRAMES = 50;
|
|
28
|
+
var truncateUtf8 = (value, maxBytes) => {
|
|
29
|
+
if (Buffer.byteLength(value, "utf8") <= maxBytes) return value;
|
|
30
|
+
let end = Math.min(value.length, maxBytes);
|
|
31
|
+
while (end > 0 && Buffer.byteLength(value.slice(0, end), "utf8") > maxBytes) end--;
|
|
32
|
+
return value.slice(0, end);
|
|
33
|
+
};
|
|
34
|
+
var errorFields = (error) => {
|
|
35
|
+
if (error instanceof Error) {
|
|
36
|
+
const code = "code" in error && (typeof error.code === "string" || typeof error.code === "number") ? String(error.code) : void 0;
|
|
37
|
+
return {
|
|
38
|
+
type: error.name || "Error",
|
|
39
|
+
message: error.message || String(error),
|
|
40
|
+
code
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (typeof error === "string") {
|
|
44
|
+
return { type: "string", message: error };
|
|
45
|
+
}
|
|
46
|
+
return { type: "Error", message: String(error) };
|
|
47
|
+
};
|
|
48
|
+
var toStackFrame = (frame) => {
|
|
49
|
+
const preferred = frame.original ?? frame.generated;
|
|
50
|
+
const filename = (preferred.file || frame.generated.file).trim();
|
|
51
|
+
if (!filename) return null;
|
|
52
|
+
const fn = frame.function ?? frame.original?.name;
|
|
53
|
+
return {
|
|
54
|
+
filename,
|
|
55
|
+
...fn ? { function: fn } : {},
|
|
56
|
+
...preferred.line !== void 0 ? { lineno: preferred.line } : {},
|
|
57
|
+
...preferred.column !== void 0 ? { colno: preferred.column } : {},
|
|
58
|
+
in_app: frame.in_app
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
var sanitizeRequest = (request) => {
|
|
62
|
+
if (!request) return void 0;
|
|
63
|
+
const { method, route, status } = request;
|
|
64
|
+
if (method == null && route == null && status == null) return void 0;
|
|
65
|
+
return {
|
|
66
|
+
...method != null ? { method } : {},
|
|
67
|
+
...route != null ? { route } : {},
|
|
68
|
+
...status != null ? { status } : {}
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
var framesToStackFrames = (frames) => frames.slice(0, MAX_STACK_FRAMES).map(toStackFrame).filter((frame) => frame !== null);
|
|
72
|
+
var withResolvedFrames = (event, frames, resolution) => ({
|
|
73
|
+
...event,
|
|
74
|
+
stack_frames: framesToStackFrames(frames),
|
|
75
|
+
resolution
|
|
76
|
+
});
|
|
77
|
+
var createIngestEvent = (input) => {
|
|
78
|
+
const extracted = errorFields(input.error);
|
|
79
|
+
const occurred = input.occurredAt === void 0 ? (/* @__PURE__ */ new Date()).toISOString() : typeof input.occurredAt === "string" ? input.occurredAt : input.occurredAt.toISOString();
|
|
80
|
+
const event = {
|
|
81
|
+
type: extracted.type,
|
|
82
|
+
message: truncateUtf8(extracted.message, MAX_MESSAGE_BYTES),
|
|
83
|
+
stack_frames: framesToStackFrames(input.frames),
|
|
84
|
+
occurred_at: occurred,
|
|
85
|
+
environment: input.environment,
|
|
86
|
+
origin: input.origin,
|
|
87
|
+
resolution: input.resolution
|
|
88
|
+
};
|
|
89
|
+
const code = input.code !== void 0 ? input.code : extracted.code;
|
|
90
|
+
if (code !== void 0) event.code = code;
|
|
91
|
+
if (input.release !== void 0) event.release = input.release;
|
|
92
|
+
const request = sanitizeRequest(input.request);
|
|
93
|
+
if (request) event.request = request;
|
|
94
|
+
return event;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/create-ingest-event/finalize-resolution.ts
|
|
98
|
+
var SOURCE_EXT = /\.(ts|tsx|mts|cts)$/i;
|
|
99
|
+
var isAbsolutePath = (file) => file.startsWith("/") || /^[A-Za-z]:[\\/]/.test(file);
|
|
100
|
+
var isRuntimeFrame = (file) => !file || file === "native" || /^(node:|bun:)/.test(file);
|
|
101
|
+
var finalizeResolution = (resolution, stackFrames) => {
|
|
102
|
+
if (resolution !== "exact") return resolution;
|
|
103
|
+
const mapped = stackFrames.filter((frame) => !isRuntimeFrame(frame.filename));
|
|
104
|
+
if (mapped.some((frame) => frame.filename.includes("..") || isAbsolutePath(frame.filename))) {
|
|
105
|
+
return "partial";
|
|
106
|
+
}
|
|
107
|
+
const resolvedSource = mapped.some((frame) => SOURCE_EXT.test(frame.filename));
|
|
108
|
+
const hasInApp = stackFrames.some((frame) => frame.in_app);
|
|
109
|
+
if (resolvedSource && !hasInApp) return "partial";
|
|
110
|
+
return "exact";
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// src/detect-release/detect-release.ts
|
|
114
|
+
var ENV_KEYS = [
|
|
115
|
+
"VERCEL_GIT_COMMIT_SHA",
|
|
116
|
+
"RAILWAY_GIT_COMMIT_SHA",
|
|
117
|
+
"RENDER_GIT_COMMIT",
|
|
118
|
+
"SOURCE_VERSION",
|
|
119
|
+
"GIT_COMMIT",
|
|
120
|
+
"COMMIT_SHA",
|
|
121
|
+
"GIT_SHA"
|
|
122
|
+
];
|
|
123
|
+
var detectRelease = (options = {}) => {
|
|
124
|
+
const explicit = options.release?.trim();
|
|
125
|
+
if (explicit) return explicit;
|
|
126
|
+
const env = options.env ?? process.env;
|
|
127
|
+
for (const key of ENV_KEYS) {
|
|
128
|
+
const value = env[key]?.trim();
|
|
129
|
+
if (value) return value;
|
|
130
|
+
}
|
|
131
|
+
console.warn(
|
|
132
|
+
"[rasputin] No release detected. Set `release` in RasputinInit or a commit SHA env var (e.g. GIT_COMMIT, VERCEL_GIT_COMMIT_SHA). Without a release, deploy correlation and diagnosis confidence are degraded."
|
|
133
|
+
);
|
|
134
|
+
return void 0;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// src/normalize-frames/normalize-frames.ts
|
|
138
|
+
import { isAbsolute, resolve } from "node:path";
|
|
139
|
+
import { fileURLToPath } from "node:url";
|
|
140
|
+
var NON_SOURCE = /* @__PURE__ */ new Set(["", "native", "<anonymous>", "unknown location"]);
|
|
141
|
+
var toPosix = (path) => path.replaceAll("\\", "/");
|
|
142
|
+
var stripFileUrl = (file) => {
|
|
143
|
+
if (!file.startsWith("file://")) return file;
|
|
144
|
+
try {
|
|
145
|
+
return fileURLToPath(file);
|
|
146
|
+
} catch {
|
|
147
|
+
return file.replace(/^file:\/\//, "");
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
var isSchemePath = (file) => NON_SOURCE.has(file) || /^(node:|bun:|https?:|webpack:|blob:|data:|eval\b)/i.test(file);
|
|
151
|
+
var normalizeFile = (file, appRoot) => {
|
|
152
|
+
if (!file || isSchemePath(file)) return { file, underRoot: false };
|
|
153
|
+
const cleaned = toPosix(stripFileUrl(file)).split("?")[0]?.trim() ?? "";
|
|
154
|
+
if (!cleaned || isSchemePath(cleaned)) return { file: cleaned, underRoot: false };
|
|
155
|
+
const root = toPosix(resolve(appRoot));
|
|
156
|
+
const absolute = toPosix(
|
|
157
|
+
isAbsolute(cleaned) || /^[A-Za-z]:\//.test(cleaned) ? resolve(cleaned) : resolve(root, cleaned)
|
|
158
|
+
);
|
|
159
|
+
if (absolute === root) return { file: ".", underRoot: true };
|
|
160
|
+
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
161
|
+
if (absolute.startsWith(prefix)) {
|
|
162
|
+
return { file: absolute.slice(prefix.length), underRoot: true };
|
|
163
|
+
}
|
|
164
|
+
return { file: absolute, underRoot: false };
|
|
165
|
+
};
|
|
166
|
+
var isInApp = (file, underRoot) => {
|
|
167
|
+
if (!underRoot || isSchemePath(file)) return false;
|
|
168
|
+
if (/(^|\/)node_modules\//.test(file)) return false;
|
|
169
|
+
return true;
|
|
170
|
+
};
|
|
171
|
+
var normalizeFrames = (frames, options = {}) => {
|
|
172
|
+
const appRoot = options.appRoot ?? process.cwd();
|
|
173
|
+
return frames.map((frame) => {
|
|
174
|
+
const generated = normalizeFile(frame.generated.file, appRoot);
|
|
175
|
+
const original = frame.original ? normalizeFile(frame.original.file, appRoot) : void 0;
|
|
176
|
+
const preferred = original ?? generated;
|
|
177
|
+
return {
|
|
178
|
+
generated: {
|
|
179
|
+
file: generated.file,
|
|
180
|
+
line: frame.generated.line,
|
|
181
|
+
column: frame.generated.column
|
|
182
|
+
},
|
|
183
|
+
original: frame.original ? {
|
|
184
|
+
file: original?.file ?? frame.original.file,
|
|
185
|
+
line: frame.original.line,
|
|
186
|
+
column: frame.original.column,
|
|
187
|
+
name: frame.original.name
|
|
188
|
+
} : void 0,
|
|
189
|
+
function: frame.function,
|
|
190
|
+
in_app: isInApp(preferred.file, preferred.underRoot)
|
|
191
|
+
};
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// src/parse-stack/parse-stack.ts
|
|
196
|
+
var V8 = /^\s*at (?<fn>.*?) ?\((?<loc>(?:file|https?|blob|native|eval|webpack|rsc|node|bun|data|<anonymous>|\/|[a-z]:\\|\\\\).*?)(?::(?<line>\d+))?(?::(?<col>\d+))?\)?\s*$/i;
|
|
197
|
+
var V8_EVAL = /\((?<loc>\S+)(?::(?<line>\d+))(?::(?<col>\d+))\)/;
|
|
198
|
+
var NODE = /^\s*at (?:(?<fn>(?:\[object object\])?[^\\/]+(?: \[as \S+\])?) )?\(?(?<loc>.+?):(?<line>\d+)(?::(?<col>\d+))?\)?\s*$/i;
|
|
199
|
+
var JSC = /^\s*(?:(?<fn>[^@]*)(?:\(.*?\))?@)?(?<loc>\S.*?):(?<line>\d+)(?::(?<col>\d+))?\s*$/i;
|
|
200
|
+
var convertToNumber = (value) => {
|
|
201
|
+
if (value === void 0 || value === "") return void 0;
|
|
202
|
+
const n = Number(value);
|
|
203
|
+
return Number.isFinite(n) ? n : void 0;
|
|
204
|
+
};
|
|
205
|
+
var cleanFunctionName = (fn) => {
|
|
206
|
+
if (!fn) return void 0;
|
|
207
|
+
const trimmed = fn.trim();
|
|
208
|
+
if (!trimmed || trimmed === "<anonymous>" || trimmed === "<unknown>") return void 0;
|
|
209
|
+
return trimmed.startsWith("async ") ? trimmed.slice(6) || void 0 : trimmed;
|
|
210
|
+
};
|
|
211
|
+
var cleanFilePath = (loc) => {
|
|
212
|
+
if (loc.startsWith("file:///")) {
|
|
213
|
+
const rest = loc.slice("file://".length);
|
|
214
|
+
return /^\/[A-Za-z]:/.test(rest) ? rest.slice(1) : rest;
|
|
215
|
+
}
|
|
216
|
+
if (loc.startsWith("file://")) return loc.slice("file://".length);
|
|
217
|
+
return loc.startsWith("native") ? "native" : loc;
|
|
218
|
+
};
|
|
219
|
+
var createFrame = (fn, loc, line, col) => ({
|
|
220
|
+
file: cleanFilePath(loc),
|
|
221
|
+
function: cleanFunctionName(fn),
|
|
222
|
+
line: convertToNumber(line),
|
|
223
|
+
column: convertToNumber(col)
|
|
224
|
+
});
|
|
225
|
+
var parseV8 = (line) => {
|
|
226
|
+
const g = V8.exec(line)?.groups;
|
|
227
|
+
if (!g?.loc) return null;
|
|
228
|
+
if (g.loc.startsWith("eval")) {
|
|
229
|
+
const inner = V8_EVAL.exec(g.loc)?.groups;
|
|
230
|
+
if (inner?.loc) return createFrame(g.fn, inner.loc, inner.line, inner.col);
|
|
231
|
+
}
|
|
232
|
+
return createFrame(g.fn, g.loc, g.line, g.col);
|
|
233
|
+
};
|
|
234
|
+
var parseNode = (line) => {
|
|
235
|
+
const g = NODE.exec(line)?.groups;
|
|
236
|
+
return g?.loc ? createFrame(g.fn, g.loc, g.line, g.col) : null;
|
|
237
|
+
};
|
|
238
|
+
var parseJsc = (line) => {
|
|
239
|
+
if (!line.includes("@")) return null;
|
|
240
|
+
const g = JSC.exec(line)?.groups;
|
|
241
|
+
return g?.loc ? createFrame(g.fn, g.loc, g.line, g.col) : null;
|
|
242
|
+
};
|
|
243
|
+
var parseLine = (line) => parseV8(line) ?? parseNode(line) ?? parseJsc(line);
|
|
244
|
+
var parseStack = (stack) => {
|
|
245
|
+
const frames = [];
|
|
246
|
+
for (const line of stack.split(/\r?\n/)) {
|
|
247
|
+
const parsed = parseLine(line);
|
|
248
|
+
if (parsed) frames.push(parsed);
|
|
249
|
+
}
|
|
250
|
+
return frames;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// src/resolve-source-maps/resolve-source-maps.ts
|
|
254
|
+
import { readFile } from "node:fs/promises";
|
|
255
|
+
import { dirname, isAbsolute as isAbsolute2, join } from "node:path";
|
|
256
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
257
|
+
import { LEAST_UPPER_BOUND, originalPositionFor, TraceMap } from "@jridgewell/trace-mapping";
|
|
258
|
+
var SOURCE_EXT2 = /\.(ts|tsx|mts|cts)$/i;
|
|
259
|
+
var SOURCEMAP_COMMENT = /(?:\/\/[#@][ \t]*sourceMappingURL=([^\s'"]+)|\/\*[#@][ \t]*sourceMappingURL=([^\s*'"]+)[ \t]*\*\/)\s*$/;
|
|
260
|
+
var mapCache = /* @__PURE__ */ new Map();
|
|
261
|
+
var toPath = (file) => {
|
|
262
|
+
if (file.startsWith("file://")) {
|
|
263
|
+
try {
|
|
264
|
+
return fileURLToPath2(file);
|
|
265
|
+
} catch {
|
|
266
|
+
return file;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return file;
|
|
270
|
+
};
|
|
271
|
+
var isSourcePath = (file) => SOURCE_EXT2.test(file);
|
|
272
|
+
var isResolvableFile = (file) => {
|
|
273
|
+
if (isSourcePath(file) || file === "<anonymous>" || file === "native") return false;
|
|
274
|
+
return !/^(node:|bun:|https?:|webpack:|blob:|data:|eval\b)/i.test(file);
|
|
275
|
+
};
|
|
276
|
+
var decodeDataUrl = (url) => {
|
|
277
|
+
const base64 = /^data:application\/json(?:;charset=[^;,]+)?;base64,(.+)$/i.exec(url);
|
|
278
|
+
if (base64?.[1]) {
|
|
279
|
+
try {
|
|
280
|
+
return Buffer.from(base64[1], "base64").toString("utf8");
|
|
281
|
+
} catch {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const raw = /^data:application\/json(?:;charset=[^;,]+)?,(.+)$/i.exec(url);
|
|
286
|
+
if (!raw?.[1]) return null;
|
|
287
|
+
try {
|
|
288
|
+
return decodeURIComponent(raw[1]);
|
|
289
|
+
} catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
var readMapPayload = async (generatedPath) => {
|
|
294
|
+
let source;
|
|
295
|
+
try {
|
|
296
|
+
source = await readFile(generatedPath, "utf8");
|
|
297
|
+
} catch {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
const match = SOURCEMAP_COMMENT.exec(source);
|
|
301
|
+
const comment = match?.[1] ?? match?.[2];
|
|
302
|
+
if (comment?.startsWith("data:")) return decodeDataUrl(comment);
|
|
303
|
+
if (comment) {
|
|
304
|
+
try {
|
|
305
|
+
return await readFile(join(dirname(generatedPath), comment), "utf8");
|
|
306
|
+
} catch {
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
return await readFile(`${generatedPath}.map`, "utf8");
|
|
311
|
+
} catch {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
var loadTraceMap = async (generatedPath) => {
|
|
316
|
+
const cached = mapCache.get(generatedPath);
|
|
317
|
+
if (cached !== void 0) return cached;
|
|
318
|
+
const payload = await readMapPayload(generatedPath);
|
|
319
|
+
if (!payload) {
|
|
320
|
+
mapCache.set(generatedPath, null);
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
const map = new TraceMap(payload);
|
|
325
|
+
mapCache.set(generatedPath, map);
|
|
326
|
+
return map;
|
|
327
|
+
} catch {
|
|
328
|
+
mapCache.set(generatedPath, null);
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
var isMapCandidate = (raw) => raw.line !== void 0 && isResolvableFile(raw.file) && !isSourcePath(raw.file);
|
|
333
|
+
var resolveFrame = async (raw) => {
|
|
334
|
+
const generated = { file: raw.file, line: raw.line, column: raw.column };
|
|
335
|
+
const base = { generated, function: raw.function };
|
|
336
|
+
if (isSourcePath(raw.file)) {
|
|
337
|
+
return { ...base, original: { ...generated } };
|
|
338
|
+
}
|
|
339
|
+
if (raw.line === void 0 || !isResolvableFile(raw.file)) return base;
|
|
340
|
+
const generatedPath = toPath(raw.file);
|
|
341
|
+
const map = await loadTraceMap(generatedPath);
|
|
342
|
+
if (!map) return base;
|
|
343
|
+
const column = raw.column ?? 0;
|
|
344
|
+
let pos = originalPositionFor(map, { line: raw.line, column });
|
|
345
|
+
if ((!pos.source || pos.line == null) && column === 0) {
|
|
346
|
+
pos = originalPositionFor(map, { line: raw.line, column: 0, bias: LEAST_UPPER_BOUND });
|
|
347
|
+
}
|
|
348
|
+
if (!pos.source || pos.line == null) return base;
|
|
349
|
+
return {
|
|
350
|
+
...base,
|
|
351
|
+
original: {
|
|
352
|
+
file: isAbsolute2(pos.source) ? pos.source : join(dirname(generatedPath), pos.source),
|
|
353
|
+
line: pos.line,
|
|
354
|
+
column: pos.column ?? void 0,
|
|
355
|
+
name: pos.name ?? void 0
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
};
|
|
359
|
+
var resolveSourceMaps = async (frames) => {
|
|
360
|
+
const resolved = await Promise.all(frames.map(resolveFrame));
|
|
361
|
+
const candidateIndexes = frames.flatMap((raw, index) => isMapCandidate(raw) ? [index] : []);
|
|
362
|
+
const unresolvedGenerated = candidateIndexes.length > 0 && candidateIndexes.every((index) => resolved[index]?.original === void 0);
|
|
363
|
+
return {
|
|
364
|
+
frames: resolved,
|
|
365
|
+
resolution: resolved.some((f) => f.original) ? "exact" : "none",
|
|
366
|
+
unresolvedGenerated
|
|
367
|
+
};
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// src/sdk-meta.ts
|
|
371
|
+
var SDK_NAME = "@rasputin-ai/core";
|
|
372
|
+
var SDK_VERSION = "0.1.2";
|
|
373
|
+
|
|
374
|
+
// src/suppression/identity-needs-message-template.ts
|
|
375
|
+
var identityNeedsMessageTemplate = (type, code) => {
|
|
376
|
+
if (code != null && code !== "") return false;
|
|
377
|
+
return type === "Error";
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// src/suppression/parameterize-message.ts
|
|
381
|
+
import { isIP } from "node:net";
|
|
382
|
+
var MAX_INPUT_LENGTH = 8192;
|
|
383
|
+
var RULES = [
|
|
384
|
+
{
|
|
385
|
+
token: "email",
|
|
386
|
+
pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}\b/giu
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
token: "url",
|
|
390
|
+
pattern: /\b(?:https?|wss?):\/\/[^\s"'<>]+/giu
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
token: "traceparent",
|
|
394
|
+
pattern: /\b00-[0-9a-f]{32}-[0-9a-f]{16}-0[01]\b/giu
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
token: "aws_trace_id",
|
|
398
|
+
pattern: /\b1-[0-9a-f]{8}-[0-9a-f]{24}\b/giu
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
token: "uuid",
|
|
402
|
+
pattern: /\b[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/giu
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
token: "timestamp",
|
|
406
|
+
pattern: /\b\d{4}-\d{2}-\d{2}[T ][0-2]\d:[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?\b/gu
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
token: "mac",
|
|
410
|
+
pattern: /\b(?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2}\b/giu
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
token: "ip",
|
|
414
|
+
pattern: /\b(?:\d{1,3}\.){3}\d{1,3}\b|(?<![\w:])(?:[0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}(?![\w:])/giu,
|
|
415
|
+
validate: (candidate) => isIP(candidate) !== 0
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
token: "hash",
|
|
419
|
+
pattern: /\b[0-9a-f]{16,128}\b/giu
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
token: "hex",
|
|
423
|
+
pattern: /\b0x[0-9a-f]+\b/giu
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
token: "duration",
|
|
427
|
+
pattern: /\b\d+(?:\.\d+)?\s?(?:ns|µs|us|ms|secs?|seconds?|mins?|minutes?|hrs?|hours?|days?)\b/giu
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
token: "id",
|
|
431
|
+
pattern: /\b(?=[a-z0-9]*[a-z])(?=[a-z0-9]*\d)[a-z0-9]{8,}\b/giu
|
|
432
|
+
}
|
|
433
|
+
];
|
|
434
|
+
var parameterizeMessage = (rawMessage) => {
|
|
435
|
+
if (rawMessage.length > MAX_INPUT_LENGTH) return rawMessage;
|
|
436
|
+
const normalized = rawMessage.normalize("NFKC").replace(/\r\n?/g, "\n").trim();
|
|
437
|
+
const lines = normalized.split("\n").filter((line) => line.trim());
|
|
438
|
+
let result = lines.slice(0, 2).join("\n");
|
|
439
|
+
if (result !== normalized) {
|
|
440
|
+
result += "...";
|
|
441
|
+
}
|
|
442
|
+
result = result.replace(/\b([A-Za-z_][\w.-]*)=(["'])(?:\\.|(?!\2).)*\2/gu, "$1=<string>");
|
|
443
|
+
result = result.replace(/\b([A-Za-z_][\w.-]*)=(?:true|false)\b/giu, "$1=<bool>");
|
|
444
|
+
for (const rule of RULES) {
|
|
445
|
+
result = result.replace(rule.pattern, (match) => {
|
|
446
|
+
if (rule.validate && !rule.validate(match)) {
|
|
447
|
+
return match;
|
|
448
|
+
}
|
|
449
|
+
return `<${rule.token}>`;
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
result = result.replace(/(?<![\w.])-?\d+\.\d+(?:e[+-]?\d+)?(?![\w.])/giu, "<float>").replace(/(?<![\w.])-?\d+(?![\w.])/gu, "<int>");
|
|
453
|
+
return result;
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// src/suppression/compute-burst-key.ts
|
|
457
|
+
var preferredFile = (frame) => (frame.original ?? frame.generated).file;
|
|
458
|
+
var frameBurstParts = (frames) => {
|
|
459
|
+
return frames.filter((frame) => frame.in_app).slice(0, 3).map((frame) => {
|
|
460
|
+
const file = preferredFile(frame);
|
|
461
|
+
const fn = frame.function ?? frame.original?.name ?? "";
|
|
462
|
+
if (!frame.original && frame.generated.line !== void 0) {
|
|
463
|
+
return `${file}:${fn}:${frame.generated.line}:${frame.generated.column ?? 0}`;
|
|
464
|
+
}
|
|
465
|
+
return `${file}:${fn}`;
|
|
466
|
+
});
|
|
467
|
+
};
|
|
468
|
+
var computeBurstKey = (errorType, message, code, frames) => {
|
|
469
|
+
const parts = frameBurstParts(frames);
|
|
470
|
+
let key = `${errorType}\0${parts.join("\0")}`;
|
|
471
|
+
if (identityNeedsMessageTemplate(errorType, code)) {
|
|
472
|
+
key += `\0${parameterizeMessage(message)}`;
|
|
473
|
+
}
|
|
474
|
+
return key;
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// src/suppression/create-suppression.ts
|
|
478
|
+
var DEFAULTS = {
|
|
479
|
+
windowMs: 6e4,
|
|
480
|
+
capPerWindow: 5,
|
|
481
|
+
maxBurstBuckets: 1e3
|
|
482
|
+
};
|
|
483
|
+
var sameDim = (a, b) => (a ?? null) === (b ?? null);
|
|
484
|
+
var captureIdentity = (input) => ({
|
|
485
|
+
type: input.errorType,
|
|
486
|
+
message: input.message,
|
|
487
|
+
stack_frames: framesToStackFrames(input.frames),
|
|
488
|
+
origin: input.origin,
|
|
489
|
+
...input.code !== void 0 ? { code: input.code } : {},
|
|
490
|
+
resolution: "none"
|
|
491
|
+
});
|
|
492
|
+
var createSuppression = (options = {}) => {
|
|
493
|
+
const windowMs = options.windowMs ?? DEFAULTS.windowMs;
|
|
494
|
+
const capPerWindow = options.capPerWindow ?? DEFAULTS.capPerWindow;
|
|
495
|
+
const maxBurstBuckets = options.maxBurstBuckets ?? DEFAULTS.maxBurstBuckets;
|
|
496
|
+
const burstBuckets = /* @__PURE__ */ new Map();
|
|
497
|
+
const pending = [];
|
|
498
|
+
const touch = (burstKey, bucket) => {
|
|
499
|
+
burstBuckets.delete(burstKey);
|
|
500
|
+
burstBuckets.set(burstKey, bucket);
|
|
501
|
+
};
|
|
502
|
+
const evictIfNeeded = () => {
|
|
503
|
+
while (burstBuckets.size > maxBurstBuckets) {
|
|
504
|
+
const oldest = burstBuckets.keys().next().value;
|
|
505
|
+
if (oldest === void 0) break;
|
|
506
|
+
const bucket = burstBuckets.get(oldest);
|
|
507
|
+
if (bucket && bucket.suppressed > 0) {
|
|
508
|
+
pending.push(toDraft(bucket, Date.now()));
|
|
509
|
+
}
|
|
510
|
+
burstBuckets.delete(oldest);
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
const toDraft = (bucket, windowEndMs) => ({
|
|
514
|
+
count: bucket.suppressed,
|
|
515
|
+
windowStart: bucket.windowStart,
|
|
516
|
+
windowEndMs,
|
|
517
|
+
lastRelease: bucket.lastRelease,
|
|
518
|
+
lastEnvironment: bucket.lastEnvironment,
|
|
519
|
+
identity: bucket.identity,
|
|
520
|
+
captureRawFrames: bucket.captureRawFrames
|
|
521
|
+
});
|
|
522
|
+
const toSummary = (draft, identity) => ({
|
|
523
|
+
count: draft.count,
|
|
524
|
+
window_start: new Date(draft.windowStart).toISOString(),
|
|
525
|
+
window_end: new Date(draft.windowEndMs).toISOString(),
|
|
526
|
+
release: draft.lastRelease,
|
|
527
|
+
environment: draft.lastEnvironment,
|
|
528
|
+
...identity
|
|
529
|
+
});
|
|
530
|
+
const flushSuppressed = (bucket, windowEndMs) => {
|
|
531
|
+
if (bucket.suppressed <= 0) return;
|
|
532
|
+
pending.push(toDraft(bucket, windowEndMs));
|
|
533
|
+
bucket.suppressed = 0;
|
|
534
|
+
};
|
|
535
|
+
const decide = (input) => {
|
|
536
|
+
const now = input.now ?? Date.now();
|
|
537
|
+
const identity = captureIdentity(input);
|
|
538
|
+
const captureRawFrames = (input.rawFrames ?? []).slice(0, MAX_STACK_FRAMES);
|
|
539
|
+
const burstKey = computeBurstKey(input.errorType, input.message, input.code, input.frames);
|
|
540
|
+
const release = input.release ?? null;
|
|
541
|
+
const route = input.route ?? null;
|
|
542
|
+
let bucket = burstBuckets.get(burstKey);
|
|
543
|
+
if (!bucket) {
|
|
544
|
+
bucket = {
|
|
545
|
+
sentInWindow: 1,
|
|
546
|
+
suppressed: 0,
|
|
547
|
+
windowStart: now,
|
|
548
|
+
lastRelease: release,
|
|
549
|
+
lastRoute: route,
|
|
550
|
+
lastEnvironment: input.environment,
|
|
551
|
+
identity,
|
|
552
|
+
captureRawFrames
|
|
553
|
+
};
|
|
554
|
+
burstBuckets.set(burstKey, bucket);
|
|
555
|
+
evictIfNeeded();
|
|
556
|
+
return { action: "send", priority: "first" };
|
|
557
|
+
}
|
|
558
|
+
touch(burstKey, bucket);
|
|
559
|
+
const dimensionChanged = !sameDim(release, bucket.lastRelease) || !sameDim(route, bucket.lastRoute) || bucket.lastEnvironment !== input.environment;
|
|
560
|
+
const windowExpired = now - bucket.windowStart >= windowMs;
|
|
561
|
+
if (dimensionChanged || windowExpired) {
|
|
562
|
+
flushSuppressed(bucket, now);
|
|
563
|
+
bucket.sentInWindow = 1;
|
|
564
|
+
bucket.windowStart = now;
|
|
565
|
+
bucket.lastRelease = release;
|
|
566
|
+
bucket.lastRoute = route;
|
|
567
|
+
bucket.lastEnvironment = input.environment;
|
|
568
|
+
bucket.identity = identity;
|
|
569
|
+
bucket.captureRawFrames = captureRawFrames;
|
|
570
|
+
return { action: "send", priority: "first" };
|
|
571
|
+
}
|
|
572
|
+
if (bucket.sentInWindow < capPerWindow) {
|
|
573
|
+
bucket.sentInWindow += 1;
|
|
574
|
+
bucket.lastRelease = release;
|
|
575
|
+
bucket.lastRoute = route;
|
|
576
|
+
bucket.lastEnvironment = input.environment;
|
|
577
|
+
return { action: "send", priority: "repeat" };
|
|
578
|
+
}
|
|
579
|
+
bucket.suppressed += 1;
|
|
580
|
+
return { action: "suppress" };
|
|
581
|
+
};
|
|
582
|
+
const takeSummaries = async (now = Date.now(), prepareSummary) => {
|
|
583
|
+
const drafts = pending.splice(0, pending.length);
|
|
584
|
+
for (const bucket of burstBuckets.values()) {
|
|
585
|
+
if (bucket.suppressed > 0) {
|
|
586
|
+
drafts.push(toDraft(bucket, now));
|
|
587
|
+
bucket.suppressed = 0;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
const out = [];
|
|
591
|
+
for (const draft of drafts) {
|
|
592
|
+
let identity = draft.identity;
|
|
593
|
+
if (prepareSummary && draft.captureRawFrames.length > 0) {
|
|
594
|
+
try {
|
|
595
|
+
const resolved = await prepareSummary(draft.captureRawFrames);
|
|
596
|
+
identity = { ...identity, ...resolved };
|
|
597
|
+
} catch {
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
out.push(toSummary(draft, identity));
|
|
601
|
+
}
|
|
602
|
+
return out;
|
|
603
|
+
};
|
|
604
|
+
return {
|
|
605
|
+
decide,
|
|
606
|
+
takeSummaries,
|
|
607
|
+
/** Test helper — number of active burst buckets. */
|
|
608
|
+
size: () => burstBuckets.size
|
|
609
|
+
};
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
// src/transport/send-batch.ts
|
|
613
|
+
import { gzipSync } from "node:zlib";
|
|
614
|
+
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
615
|
+
var backoffMs = (attempt) => {
|
|
616
|
+
const base = Math.min(3e4, 500 * 2 ** attempt);
|
|
617
|
+
return Math.floor(base * (0.5 + Math.random()));
|
|
618
|
+
};
|
|
619
|
+
var retryAfterMs = (response) => {
|
|
620
|
+
const header = response.headers.get("retry-after");
|
|
621
|
+
if (!header) return null;
|
|
622
|
+
const seconds = Number(header);
|
|
623
|
+
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
|
|
624
|
+
const date = Date.parse(header);
|
|
625
|
+
return Number.isFinite(date) ? Math.max(0, date - Date.now()) : null;
|
|
626
|
+
};
|
|
627
|
+
var sendBatch = async (options) => {
|
|
628
|
+
const summaries = options.summaries ?? [];
|
|
629
|
+
let payload;
|
|
630
|
+
if (summaries.length > 0) {
|
|
631
|
+
payload = { events: options.events, summaries };
|
|
632
|
+
} else {
|
|
633
|
+
payload = { events: options.events };
|
|
634
|
+
}
|
|
635
|
+
if (options.sdkVersion) payload.sdk_version = options.sdkVersion;
|
|
636
|
+
if (options.sdkName) payload.sdk_name = options.sdkName;
|
|
637
|
+
const json = JSON.stringify(payload);
|
|
638
|
+
const headers = {
|
|
639
|
+
Authorization: `Bearer ${options.projectApiKey}`,
|
|
640
|
+
"Content-Type": "application/json",
|
|
641
|
+
Accept: "application/json"
|
|
642
|
+
};
|
|
643
|
+
let body = json;
|
|
644
|
+
if (Buffer.byteLength(json, "utf8") >= options.gzipThresholdBytes) {
|
|
645
|
+
const compressed = gzipSync(json);
|
|
646
|
+
const bytes = new Uint8Array(compressed.byteLength);
|
|
647
|
+
bytes.set(compressed);
|
|
648
|
+
body = bytes;
|
|
649
|
+
headers["Content-Encoding"] = "gzip";
|
|
650
|
+
}
|
|
651
|
+
for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
|
|
652
|
+
if (options.signal?.aborted) return "failed";
|
|
653
|
+
try {
|
|
654
|
+
const response = await options.fetch(options.ingestUrl, {
|
|
655
|
+
method: "POST",
|
|
656
|
+
headers,
|
|
657
|
+
body,
|
|
658
|
+
signal: options.signal
|
|
659
|
+
});
|
|
660
|
+
if (response.ok) return "ok";
|
|
661
|
+
if (response.status === 429) {
|
|
662
|
+
if (attempt === options.maxRetries) return "failed";
|
|
663
|
+
const wait = retryAfterMs(response) ?? backoffMs(attempt);
|
|
664
|
+
await sleep(wait);
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
if (response.status >= 400 && response.status < 500) return "dropped";
|
|
668
|
+
if (attempt === options.maxRetries) return "failed";
|
|
669
|
+
await sleep(backoffMs(attempt));
|
|
670
|
+
} catch {
|
|
671
|
+
if (options.signal?.aborted) return "failed";
|
|
672
|
+
if (attempt === options.maxRetries) return "failed";
|
|
673
|
+
await sleep(backoffMs(attempt));
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return "failed";
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
// src/transport/transport-queue.ts
|
|
680
|
+
var RANK = {
|
|
681
|
+
repeat: 0,
|
|
682
|
+
summary: 1,
|
|
683
|
+
first: 2
|
|
684
|
+
};
|
|
685
|
+
var createQueue = (maxSize) => {
|
|
686
|
+
const items = [];
|
|
687
|
+
const oldestLowestIndex = () => {
|
|
688
|
+
let best = 0;
|
|
689
|
+
for (let i = 1; i < items.length; i++) {
|
|
690
|
+
const current = items[i];
|
|
691
|
+
const champion = items[best];
|
|
692
|
+
if (!current || !champion) continue;
|
|
693
|
+
if (RANK[current.priority] < RANK[champion.priority]) best = i;
|
|
694
|
+
}
|
|
695
|
+
return best;
|
|
696
|
+
};
|
|
697
|
+
const enqueue = (item) => {
|
|
698
|
+
while (items.length >= maxSize) {
|
|
699
|
+
const victimIndex = oldestLowestIndex();
|
|
700
|
+
const victim = items[victimIndex];
|
|
701
|
+
if (!victim) break;
|
|
702
|
+
if (RANK[item.priority] < RANK[victim.priority]) return;
|
|
703
|
+
items.splice(victimIndex, 1);
|
|
704
|
+
}
|
|
705
|
+
items.push(item);
|
|
706
|
+
};
|
|
707
|
+
const drain = (count) => items.splice(0, count);
|
|
708
|
+
return {
|
|
709
|
+
enqueue,
|
|
710
|
+
drain,
|
|
711
|
+
size: () => items.length,
|
|
712
|
+
clear: () => {
|
|
713
|
+
items.length = 0;
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
// src/transport/create-transport.ts
|
|
719
|
+
var DEFAULTS2 = {
|
|
720
|
+
enabled: true,
|
|
721
|
+
maxQueueSize: 500,
|
|
722
|
+
batchSize: 30,
|
|
723
|
+
flushIntervalMs: 5e3,
|
|
724
|
+
gzipThresholdBytes: 10 * 1024,
|
|
725
|
+
maxRetries: 5,
|
|
726
|
+
installSignalHandlers: true
|
|
727
|
+
};
|
|
728
|
+
var createTransport = (options) => {
|
|
729
|
+
const enabled = options.enabled ?? DEFAULTS2.enabled;
|
|
730
|
+
const batchSize = options.batchSize ?? DEFAULTS2.batchSize;
|
|
731
|
+
const flushIntervalMs = options.flushIntervalMs ?? DEFAULTS2.flushIntervalMs;
|
|
732
|
+
const gzipThresholdBytes = options.gzipThresholdBytes ?? DEFAULTS2.gzipThresholdBytes;
|
|
733
|
+
const maxRetries = options.maxRetries ?? DEFAULTS2.maxRetries;
|
|
734
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
735
|
+
const queue = createQueue(options.maxQueueSize ?? DEFAULTS2.maxQueueSize);
|
|
736
|
+
let timer;
|
|
737
|
+
let flushing;
|
|
738
|
+
let closed = false;
|
|
739
|
+
const schedule = () => {
|
|
740
|
+
if (timer || !enabled || closed) return;
|
|
741
|
+
timer = setInterval(() => {
|
|
742
|
+
void flush();
|
|
743
|
+
}, flushIntervalMs);
|
|
744
|
+
timer.unref?.();
|
|
745
|
+
};
|
|
746
|
+
const stopTimer = () => {
|
|
747
|
+
if (!timer) return;
|
|
748
|
+
clearInterval(timer);
|
|
749
|
+
timer = void 0;
|
|
750
|
+
};
|
|
751
|
+
const flush = async (timeoutMs = 1e4) => {
|
|
752
|
+
if (!enabled || closed) {
|
|
753
|
+
queue.clear();
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
if (flushing) return flushing;
|
|
757
|
+
flushing = (async () => {
|
|
758
|
+
const controller = new AbortController();
|
|
759
|
+
const timeout = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
|
|
760
|
+
try {
|
|
761
|
+
while (queue.size() > 0) {
|
|
762
|
+
if (controller.signal.aborted) break;
|
|
763
|
+
const batch = queue.drain(batchSize);
|
|
764
|
+
if (batch.length === 0) break;
|
|
765
|
+
const events = [];
|
|
766
|
+
const summaries = [];
|
|
767
|
+
for (const item of batch) {
|
|
768
|
+
if (item.kind === "summary") {
|
|
769
|
+
summaries.push(item.summary);
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
if (options.prepareEvent && item.rawFrames.length > 0) {
|
|
773
|
+
try {
|
|
774
|
+
events.push(
|
|
775
|
+
await options.prepareEvent({
|
|
776
|
+
event: item.event,
|
|
777
|
+
rawFrames: item.rawFrames
|
|
778
|
+
})
|
|
779
|
+
);
|
|
780
|
+
} catch {
|
|
781
|
+
events.push(item.event);
|
|
782
|
+
}
|
|
783
|
+
} else {
|
|
784
|
+
events.push(item.event);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
const result = await sendBatch({
|
|
788
|
+
ingestUrl: options.ingestUrl,
|
|
789
|
+
projectApiKey: options.projectApiKey,
|
|
790
|
+
events,
|
|
791
|
+
summaries,
|
|
792
|
+
sdkVersion: options.sdkVersion,
|
|
793
|
+
sdkName: options.sdkName,
|
|
794
|
+
gzipThresholdBytes,
|
|
795
|
+
maxRetries,
|
|
796
|
+
fetch: fetchFn,
|
|
797
|
+
signal: controller.signal
|
|
798
|
+
});
|
|
799
|
+
if (result === "failed") {
|
|
800
|
+
for (const item of batch) queue.enqueue(item);
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
} finally {
|
|
805
|
+
if (timeout) clearTimeout(timeout);
|
|
806
|
+
if (queue.size() === 0) stopTimer();
|
|
807
|
+
flushing = void 0;
|
|
808
|
+
}
|
|
809
|
+
})();
|
|
810
|
+
return flushing;
|
|
811
|
+
};
|
|
812
|
+
const enqueue = (event, priority = "first", rawFrames = []) => {
|
|
813
|
+
if (!enabled || closed) return;
|
|
814
|
+
queue.enqueue({ kind: "event", event, priority, rawFrames });
|
|
815
|
+
schedule();
|
|
816
|
+
if (queue.size() >= batchSize) void flush();
|
|
817
|
+
};
|
|
818
|
+
const enqueueSummary = (summary) => {
|
|
819
|
+
if (!enabled || closed) return;
|
|
820
|
+
queue.enqueue({ kind: "summary", summary, priority: "summary" });
|
|
821
|
+
schedule();
|
|
822
|
+
if (queue.size() >= batchSize) void flush();
|
|
823
|
+
};
|
|
824
|
+
const onSignal = () => {
|
|
825
|
+
try {
|
|
826
|
+
void flush(2e3);
|
|
827
|
+
} catch {
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
if (enabled && (options.installSignalHandlers ?? DEFAULTS2.installSignalHandlers)) {
|
|
831
|
+
process.on("beforeExit", onSignal);
|
|
832
|
+
process.on("SIGTERM", onSignal);
|
|
833
|
+
process.on("SIGINT", onSignal);
|
|
834
|
+
}
|
|
835
|
+
const close = async () => {
|
|
836
|
+
closed = true;
|
|
837
|
+
stopTimer();
|
|
838
|
+
process.off("beforeExit", onSignal);
|
|
839
|
+
process.off("SIGTERM", onSignal);
|
|
840
|
+
process.off("SIGINT", onSignal);
|
|
841
|
+
await flush(2e3);
|
|
842
|
+
queue.clear();
|
|
843
|
+
};
|
|
844
|
+
return {
|
|
845
|
+
enqueue,
|
|
846
|
+
enqueueSummary,
|
|
847
|
+
flush,
|
|
848
|
+
close,
|
|
849
|
+
size: () => queue.size()
|
|
850
|
+
};
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
// src/create-client/is-client-enabled.ts
|
|
854
|
+
var isClientEnabled = (options) => options.enabled ?? true;
|
|
855
|
+
var noopClient = () => ({
|
|
856
|
+
captureException: () => {
|
|
857
|
+
},
|
|
858
|
+
flush: async () => {
|
|
859
|
+
},
|
|
860
|
+
close: async () => {
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
// src/create-client/resolve-endpoint-urls.ts
|
|
865
|
+
var DEFAULT_API_URL = "https://rasputin-api.fly.dev";
|
|
866
|
+
var resolveEndpointUrls = (apiUrl = DEFAULT_API_URL) => {
|
|
867
|
+
const base = apiUrl.replace(/\/+$/, "");
|
|
868
|
+
return {
|
|
869
|
+
ingestUrl: `${base}/ingest`,
|
|
870
|
+
deployUrl: `${base}/deploy`
|
|
871
|
+
};
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
// src/create-client/create-client.ts
|
|
875
|
+
var warnedUnresolvedMaps = false;
|
|
876
|
+
var warnedPartialResolution = false;
|
|
877
|
+
var warnUnresolvedMaps = (unresolved) => {
|
|
878
|
+
if (!unresolved || warnedUnresolvedMaps) return;
|
|
879
|
+
warnedUnresolvedMaps = true;
|
|
880
|
+
console.warn(
|
|
881
|
+
"[rasputin] Could not resolve source maps for one or more stack frames; fingerprinting uses generated paths"
|
|
882
|
+
);
|
|
883
|
+
};
|
|
884
|
+
var warnPartialResolution = () => {
|
|
885
|
+
if (warnedPartialResolution) return;
|
|
886
|
+
warnedPartialResolution = true;
|
|
887
|
+
console.warn(
|
|
888
|
+
"[rasputin] Source map paths could not be normalized to the repo root; fingerprinting may be degraded"
|
|
889
|
+
);
|
|
890
|
+
};
|
|
891
|
+
var prepareResolvedEvent = (event, normalized, resolution) => {
|
|
892
|
+
const stackFrames = framesToStackFrames(normalized);
|
|
893
|
+
const finalResolution = finalizeResolution(resolution, stackFrames);
|
|
894
|
+
if (finalResolution === "partial") warnPartialResolution();
|
|
895
|
+
return withResolvedFrames(event, normalized, finalResolution);
|
|
896
|
+
};
|
|
897
|
+
var toResolvedFrames = (raw) => raw.map((frame) => ({
|
|
898
|
+
generated: { file: frame.file, line: frame.line, column: frame.column },
|
|
899
|
+
...frame.function ? { function: frame.function } : {}
|
|
900
|
+
}));
|
|
901
|
+
var stackOf = (error) => {
|
|
902
|
+
if (error instanceof Error && error.stack) return error.stack;
|
|
903
|
+
return "";
|
|
904
|
+
};
|
|
905
|
+
var createClient = (options, hooks = {}) => {
|
|
906
|
+
try {
|
|
907
|
+
if (!isClientEnabled(options)) return noopClient();
|
|
908
|
+
const projectApiKey = options.projectApiKey.trim();
|
|
909
|
+
const release = detectRelease({ release: options.release });
|
|
910
|
+
const environment = options.environment;
|
|
911
|
+
const { ingestUrl, deployUrl } = resolveEndpointUrls(options.apiUrl);
|
|
912
|
+
const repoRoot = options.repoRoot;
|
|
913
|
+
const transport = createTransport({
|
|
914
|
+
ingestUrl,
|
|
915
|
+
projectApiKey,
|
|
916
|
+
enabled: true,
|
|
917
|
+
fetch: hooks.fetch,
|
|
918
|
+
installSignalHandlers: hooks.installTransportSignalHandlers,
|
|
919
|
+
sdkVersion: hooks.sdkVersion ?? SDK_VERSION,
|
|
920
|
+
sdkName: hooks.sdkName ?? SDK_NAME,
|
|
921
|
+
prepareEvent: async ({ event, rawFrames }) => {
|
|
922
|
+
const { frames, resolution, unresolvedGenerated } = await resolveSourceMaps(rawFrames);
|
|
923
|
+
warnUnresolvedMaps(unresolvedGenerated);
|
|
924
|
+
const normalized = normalizeFrames(frames, { appRoot: repoRoot });
|
|
925
|
+
return prepareResolvedEvent(event, normalized, resolution);
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
const suppression = createSuppression();
|
|
929
|
+
if (hooks.announceDeploy ?? true) {
|
|
930
|
+
void announceDeploy({
|
|
931
|
+
deployUrl,
|
|
932
|
+
projectApiKey,
|
|
933
|
+
environment,
|
|
934
|
+
release,
|
|
935
|
+
enabled: true,
|
|
936
|
+
fetch: hooks.fetch
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
const captureException = (error, context) => {
|
|
940
|
+
try {
|
|
941
|
+
const rawFrames = parseStack(stackOf(error));
|
|
942
|
+
const frames = normalizeFrames(toResolvedFrames(rawFrames), { appRoot: repoRoot });
|
|
943
|
+
const event = createIngestEvent({
|
|
944
|
+
error,
|
|
945
|
+
frames,
|
|
946
|
+
environment,
|
|
947
|
+
release,
|
|
948
|
+
origin: "server",
|
|
949
|
+
resolution: "none",
|
|
950
|
+
request: context?.request
|
|
951
|
+
});
|
|
952
|
+
const decision = suppression.decide({
|
|
953
|
+
errorType: event.type,
|
|
954
|
+
message: event.message,
|
|
955
|
+
frames,
|
|
956
|
+
rawFrames,
|
|
957
|
+
origin: event.origin,
|
|
958
|
+
code: event.code,
|
|
959
|
+
environment,
|
|
960
|
+
release,
|
|
961
|
+
route: context?.request?.route
|
|
962
|
+
});
|
|
963
|
+
if (decision.action === "send") {
|
|
964
|
+
transport.enqueue(event, decision.priority, rawFrames);
|
|
965
|
+
}
|
|
966
|
+
} catch {
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
const flush = async (timeoutMs) => {
|
|
970
|
+
try {
|
|
971
|
+
const summaries = await suppression.takeSummaries(Date.now(), async (raw) => {
|
|
972
|
+
const { frames, resolution, unresolvedGenerated } = await resolveSourceMaps(raw);
|
|
973
|
+
warnUnresolvedMaps(unresolvedGenerated);
|
|
974
|
+
const normalized = normalizeFrames(frames, { appRoot: repoRoot });
|
|
975
|
+
const stack_frames = framesToStackFrames(normalized);
|
|
976
|
+
const finalResolution = finalizeResolution(resolution, stack_frames);
|
|
977
|
+
if (finalResolution === "partial") warnPartialResolution();
|
|
978
|
+
return {
|
|
979
|
+
stack_frames,
|
|
980
|
+
resolution: finalResolution
|
|
981
|
+
};
|
|
982
|
+
});
|
|
983
|
+
for (const summary of summaries) {
|
|
984
|
+
transport.enqueueSummary(summary);
|
|
985
|
+
}
|
|
986
|
+
await transport.flush(timeoutMs);
|
|
987
|
+
} catch {
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
const close = async () => {
|
|
991
|
+
try {
|
|
992
|
+
await flush(2e3);
|
|
993
|
+
await transport.close();
|
|
994
|
+
} catch {
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
return { captureException, flush, close };
|
|
998
|
+
} catch {
|
|
999
|
+
return noopClient();
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
export {
|
|
1003
|
+
announceDeploy,
|
|
1004
|
+
createClient,
|
|
1005
|
+
createIngestEvent,
|
|
1006
|
+
createSuppression,
|
|
1007
|
+
createTransport,
|
|
1008
|
+
detectRelease,
|
|
1009
|
+
isClientEnabled,
|
|
1010
|
+
normalizeFrames,
|
|
1011
|
+
parseStack,
|
|
1012
|
+
resolveSourceMaps
|
|
1013
|
+
};
|