@parsrun/core 0.1.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 +113 -0
- package/dist/decimal.d.ts +190 -0
- package/dist/decimal.js +347 -0
- package/dist/decimal.js.map +1 -0
- package/dist/env.d.ts +101 -0
- package/dist/env.js +172 -0
- package/dist/env.js.map +1 -0
- package/dist/error-codes.d.ts +257 -0
- package/dist/error-codes.js +261 -0
- package/dist/error-codes.js.map +1 -0
- package/dist/errors.d.ts +81 -0
- package/dist/errors.js +161 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +116 -0
- package/dist/index.js +1987 -0
- package/dist/index.js.map +1 -0
- package/dist/logger-aEibH9Mv.d.ts +262 -0
- package/dist/logger.d.ts +1 -0
- package/dist/logger.js +327 -0
- package/dist/logger.js.map +1 -0
- package/dist/runtime.d.ts +62 -0
- package/dist/runtime.js +88 -0
- package/dist/runtime.js.map +1 -0
- package/dist/transports/index.d.ts +347 -0
- package/dist/transports/index.js +657 -0
- package/dist/transports/index.js.map +1 -0
- package/dist/types.d.ts +116 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/package.json +68 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1987 @@
|
|
|
1
|
+
// src/runtime.ts
|
|
2
|
+
function detectRuntime() {
|
|
3
|
+
if (typeof globalThis !== "undefined" && "Bun" in globalThis) {
|
|
4
|
+
return "bun";
|
|
5
|
+
}
|
|
6
|
+
if (typeof globalThis !== "undefined" && "Deno" in globalThis) {
|
|
7
|
+
return "deno";
|
|
8
|
+
}
|
|
9
|
+
if (typeof globalThis !== "undefined" && typeof globalThis.caches !== "undefined" && typeof globalThis.process === "undefined") {
|
|
10
|
+
return "cloudflare";
|
|
11
|
+
}
|
|
12
|
+
if (typeof globalThis !== "undefined" && typeof globalThis.EdgeRuntime !== "undefined") {
|
|
13
|
+
return "edge";
|
|
14
|
+
}
|
|
15
|
+
if (typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined" && typeof globalThis.document !== "undefined") {
|
|
16
|
+
return "browser";
|
|
17
|
+
}
|
|
18
|
+
if (typeof process !== "undefined" && process.versions && process.versions.node) {
|
|
19
|
+
return "node";
|
|
20
|
+
}
|
|
21
|
+
return "unknown";
|
|
22
|
+
}
|
|
23
|
+
var runtime = detectRuntime();
|
|
24
|
+
var runtimeInfo = {
|
|
25
|
+
runtime,
|
|
26
|
+
isNode: runtime === "node",
|
|
27
|
+
isDeno: runtime === "deno",
|
|
28
|
+
isBun: runtime === "bun",
|
|
29
|
+
isCloudflare: runtime === "cloudflare",
|
|
30
|
+
isEdge: runtime === "cloudflare" || runtime === "edge" || runtime === "deno",
|
|
31
|
+
isBrowser: runtime === "browser",
|
|
32
|
+
isServer: runtime !== "browser",
|
|
33
|
+
supportsWebCrypto: typeof globalThis.crypto?.subtle !== "undefined",
|
|
34
|
+
supportsStreams: typeof globalThis.ReadableStream !== "undefined"
|
|
35
|
+
};
|
|
36
|
+
function isNode() {
|
|
37
|
+
return runtime === "node";
|
|
38
|
+
}
|
|
39
|
+
function isDeno() {
|
|
40
|
+
return runtime === "deno";
|
|
41
|
+
}
|
|
42
|
+
function isBun() {
|
|
43
|
+
return runtime === "bun";
|
|
44
|
+
}
|
|
45
|
+
function isCloudflare() {
|
|
46
|
+
return runtime === "cloudflare";
|
|
47
|
+
}
|
|
48
|
+
function isEdge() {
|
|
49
|
+
return runtimeInfo.isEdge;
|
|
50
|
+
}
|
|
51
|
+
function isBrowser() {
|
|
52
|
+
return runtime === "browser";
|
|
53
|
+
}
|
|
54
|
+
function isServer() {
|
|
55
|
+
return runtimeInfo.isServer;
|
|
56
|
+
}
|
|
57
|
+
function getRuntimeVersion() {
|
|
58
|
+
switch (runtime) {
|
|
59
|
+
case "node":
|
|
60
|
+
return `Node.js ${process.versions.node}`;
|
|
61
|
+
case "bun":
|
|
62
|
+
return `Bun ${globalThis.Bun.version}`;
|
|
63
|
+
case "deno":
|
|
64
|
+
return `Deno ${globalThis.Deno.version.deno}`;
|
|
65
|
+
case "cloudflare":
|
|
66
|
+
return "Cloudflare Workers";
|
|
67
|
+
case "edge":
|
|
68
|
+
return "Edge Runtime";
|
|
69
|
+
case "browser":
|
|
70
|
+
return typeof navigator !== "undefined" ? navigator.userAgent : "Browser";
|
|
71
|
+
default:
|
|
72
|
+
return "Unknown";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/env.ts
|
|
77
|
+
var edgeEnvStore = {};
|
|
78
|
+
function setEdgeEnv(env) {
|
|
79
|
+
edgeEnvStore = { ...edgeEnvStore, ...env };
|
|
80
|
+
}
|
|
81
|
+
function clearEdgeEnv() {
|
|
82
|
+
edgeEnvStore = {};
|
|
83
|
+
}
|
|
84
|
+
function getEnv(key, defaultValue) {
|
|
85
|
+
if (runtime === "cloudflare" || runtime === "edge") {
|
|
86
|
+
return edgeEnvStore[key] ?? defaultValue;
|
|
87
|
+
}
|
|
88
|
+
if (runtime === "deno") {
|
|
89
|
+
try {
|
|
90
|
+
return globalThis.Deno.env.get(key) ?? defaultValue;
|
|
91
|
+
} catch {
|
|
92
|
+
return defaultValue;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (typeof process !== "undefined" && process.env) {
|
|
96
|
+
return process.env[key] ?? defaultValue;
|
|
97
|
+
}
|
|
98
|
+
if (runtime === "browser" && typeof globalThis.__ENV__ !== "undefined") {
|
|
99
|
+
return globalThis.__ENV__[key] ?? defaultValue;
|
|
100
|
+
}
|
|
101
|
+
return defaultValue;
|
|
102
|
+
}
|
|
103
|
+
function requireEnv(key) {
|
|
104
|
+
const value = getEnv(key);
|
|
105
|
+
if (value === void 0 || value === "") {
|
|
106
|
+
throw new Error(`Required environment variable "${key}" is not set`);
|
|
107
|
+
}
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
function getEnvNumber(key, defaultValue) {
|
|
111
|
+
const value = getEnv(key);
|
|
112
|
+
if (value === void 0 || value === "") {
|
|
113
|
+
return defaultValue;
|
|
114
|
+
}
|
|
115
|
+
const parsed = parseInt(value, 10);
|
|
116
|
+
return isNaN(parsed) ? defaultValue : parsed;
|
|
117
|
+
}
|
|
118
|
+
function getEnvFloat(key, defaultValue) {
|
|
119
|
+
const value = getEnv(key);
|
|
120
|
+
if (value === void 0 || value === "") {
|
|
121
|
+
return defaultValue;
|
|
122
|
+
}
|
|
123
|
+
const parsed = parseFloat(value);
|
|
124
|
+
return isNaN(parsed) ? defaultValue : parsed;
|
|
125
|
+
}
|
|
126
|
+
function getEnvBoolean(key, defaultValue = false) {
|
|
127
|
+
const value = getEnv(key);
|
|
128
|
+
if (value === void 0 || value === "") {
|
|
129
|
+
return defaultValue;
|
|
130
|
+
}
|
|
131
|
+
return value === "true" || value === "1" || value === "yes";
|
|
132
|
+
}
|
|
133
|
+
function getEnvArray(key, defaultValue = []) {
|
|
134
|
+
const value = getEnv(key);
|
|
135
|
+
if (value === void 0 || value === "") {
|
|
136
|
+
return defaultValue;
|
|
137
|
+
}
|
|
138
|
+
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
139
|
+
}
|
|
140
|
+
function getEnvJson(key, defaultValue) {
|
|
141
|
+
const value = getEnv(key);
|
|
142
|
+
if (value === void 0 || value === "") {
|
|
143
|
+
return defaultValue;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
return JSON.parse(value);
|
|
147
|
+
} catch {
|
|
148
|
+
return defaultValue;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function isDevelopment() {
|
|
152
|
+
const env = getEnv("NODE_ENV");
|
|
153
|
+
return env === "development" || env === void 0;
|
|
154
|
+
}
|
|
155
|
+
function isProduction() {
|
|
156
|
+
return getEnv("NODE_ENV") === "production";
|
|
157
|
+
}
|
|
158
|
+
function isTest() {
|
|
159
|
+
return getEnv("NODE_ENV") === "test";
|
|
160
|
+
}
|
|
161
|
+
function getEnvMode() {
|
|
162
|
+
const env = getEnv("NODE_ENV");
|
|
163
|
+
if (env === "production") return "production";
|
|
164
|
+
if (env === "test") return "test";
|
|
165
|
+
return "development";
|
|
166
|
+
}
|
|
167
|
+
function createEnvConfig(schema) {
|
|
168
|
+
const result = {};
|
|
169
|
+
for (const [key, config] of Object.entries(schema)) {
|
|
170
|
+
const envConfig = config;
|
|
171
|
+
let value;
|
|
172
|
+
switch (envConfig.type) {
|
|
173
|
+
case "number":
|
|
174
|
+
value = getEnvNumber(key, envConfig.default);
|
|
175
|
+
break;
|
|
176
|
+
case "boolean":
|
|
177
|
+
value = getEnvBoolean(key, envConfig.default);
|
|
178
|
+
break;
|
|
179
|
+
case "array":
|
|
180
|
+
value = getEnvArray(key, envConfig.default);
|
|
181
|
+
break;
|
|
182
|
+
case "json":
|
|
183
|
+
value = getEnvJson(key, envConfig.default);
|
|
184
|
+
break;
|
|
185
|
+
default:
|
|
186
|
+
value = getEnv(key, envConfig.default);
|
|
187
|
+
}
|
|
188
|
+
if (envConfig.required && (value === void 0 || value === "")) {
|
|
189
|
+
throw new Error(`Required environment variable "${key}" is not set`);
|
|
190
|
+
}
|
|
191
|
+
result[key] = value;
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/transports/console.ts
|
|
197
|
+
var ConsoleTransport = class {
|
|
198
|
+
name = "console";
|
|
199
|
+
pretty;
|
|
200
|
+
colors;
|
|
201
|
+
constructor(options = {}) {
|
|
202
|
+
this.pretty = options.pretty ?? isDevelopment();
|
|
203
|
+
this.colors = options.colors ?? (runtime === "node" || runtime === "bun");
|
|
204
|
+
}
|
|
205
|
+
log(entry) {
|
|
206
|
+
if (this.pretty) {
|
|
207
|
+
this.logPretty(entry);
|
|
208
|
+
} else {
|
|
209
|
+
this.logJson(entry);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
logJson(entry) {
|
|
213
|
+
const { level, message, timestamp, context, error } = entry;
|
|
214
|
+
const output = {
|
|
215
|
+
level,
|
|
216
|
+
time: timestamp,
|
|
217
|
+
msg: message
|
|
218
|
+
};
|
|
219
|
+
if (context && Object.keys(context).length > 0) {
|
|
220
|
+
Object.assign(output, context);
|
|
221
|
+
}
|
|
222
|
+
if (error) {
|
|
223
|
+
output["err"] = error;
|
|
224
|
+
}
|
|
225
|
+
console.log(JSON.stringify(output));
|
|
226
|
+
}
|
|
227
|
+
logPretty(entry) {
|
|
228
|
+
const { level, message, timestamp, context, error } = entry;
|
|
229
|
+
const levelColors = {
|
|
230
|
+
TRACE: "\x1B[90m",
|
|
231
|
+
// Gray
|
|
232
|
+
DEBUG: "\x1B[36m",
|
|
233
|
+
// Cyan
|
|
234
|
+
INFO: "\x1B[32m",
|
|
235
|
+
// Green
|
|
236
|
+
WARN: "\x1B[33m",
|
|
237
|
+
// Yellow
|
|
238
|
+
ERROR: "\x1B[31m",
|
|
239
|
+
// Red
|
|
240
|
+
FATAL: "\x1B[35m",
|
|
241
|
+
// Magenta
|
|
242
|
+
SILENT: ""
|
|
243
|
+
};
|
|
244
|
+
const reset = "\x1B[0m";
|
|
245
|
+
const color = this.colors ? levelColors[level] : "";
|
|
246
|
+
const resetCode = this.colors ? reset : "";
|
|
247
|
+
const timePart = timestamp.split("T")[1];
|
|
248
|
+
const time = timePart ? timePart.slice(0, 8) : timestamp;
|
|
249
|
+
let output = `${color}[${time}] ${level.padEnd(5)}${resetCode} ${message}`;
|
|
250
|
+
if (context && Object.keys(context).length > 0) {
|
|
251
|
+
output += ` ${JSON.stringify(context)}`;
|
|
252
|
+
}
|
|
253
|
+
if (level === "ERROR" || level === "FATAL") {
|
|
254
|
+
console.error(output);
|
|
255
|
+
if (error?.stack) {
|
|
256
|
+
console.error(error.stack);
|
|
257
|
+
}
|
|
258
|
+
} else if (level === "WARN") {
|
|
259
|
+
console.warn(output);
|
|
260
|
+
} else if (level === "DEBUG" || level === "TRACE") {
|
|
261
|
+
console.debug(output);
|
|
262
|
+
} else {
|
|
263
|
+
console.log(output);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// src/logger.ts
|
|
269
|
+
var LogLevel = {
|
|
270
|
+
TRACE: 10,
|
|
271
|
+
DEBUG: 20,
|
|
272
|
+
INFO: 30,
|
|
273
|
+
WARN: 40,
|
|
274
|
+
ERROR: 50,
|
|
275
|
+
FATAL: 60,
|
|
276
|
+
SILENT: 100
|
|
277
|
+
};
|
|
278
|
+
function redactFields(obj, fields) {
|
|
279
|
+
const result = { ...obj };
|
|
280
|
+
for (const field of fields) {
|
|
281
|
+
if (field in result) {
|
|
282
|
+
result[field] = "[REDACTED]";
|
|
283
|
+
}
|
|
284
|
+
const parts = field.split(".");
|
|
285
|
+
if (parts.length > 1) {
|
|
286
|
+
let current = result;
|
|
287
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
288
|
+
const part = parts[i];
|
|
289
|
+
if (part && current && typeof current === "object" && part in current) {
|
|
290
|
+
const val = current[part];
|
|
291
|
+
if (val && typeof val === "object") {
|
|
292
|
+
current = val;
|
|
293
|
+
} else {
|
|
294
|
+
current = void 0;
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
current = void 0;
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const lastPart = parts[parts.length - 1];
|
|
303
|
+
if (lastPart && current && typeof current === "object" && lastPart in current) {
|
|
304
|
+
current[lastPart] = "[REDACTED]";
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
var DEFAULT_REDACT_FIELDS = [
|
|
311
|
+
"password",
|
|
312
|
+
"secret",
|
|
313
|
+
"token",
|
|
314
|
+
"accessToken",
|
|
315
|
+
"refreshToken",
|
|
316
|
+
"apiKey",
|
|
317
|
+
"authorization",
|
|
318
|
+
"cookie",
|
|
319
|
+
"creditCard",
|
|
320
|
+
"ssn"
|
|
321
|
+
];
|
|
322
|
+
var Logger = class _Logger {
|
|
323
|
+
level;
|
|
324
|
+
name;
|
|
325
|
+
context;
|
|
326
|
+
transports;
|
|
327
|
+
redactFields;
|
|
328
|
+
timestampFn;
|
|
329
|
+
constructor(config = {}) {
|
|
330
|
+
const levelName = config.level ?? getEnv("LOG_LEVEL") ?? "INFO";
|
|
331
|
+
this.level = LogLevel[levelName] ?? LogLevel.INFO;
|
|
332
|
+
this.name = config.name;
|
|
333
|
+
this.context = config.context ?? {};
|
|
334
|
+
this.transports = config.transports ?? [
|
|
335
|
+
new ConsoleTransport(
|
|
336
|
+
config.pretty !== void 0 ? { pretty: config.pretty } : {}
|
|
337
|
+
)
|
|
338
|
+
];
|
|
339
|
+
this.redactFields = [...DEFAULT_REDACT_FIELDS, ...config.redact ?? []];
|
|
340
|
+
if (config.timestamp === false) {
|
|
341
|
+
this.timestampFn = () => "";
|
|
342
|
+
} else if (typeof config.timestamp === "function") {
|
|
343
|
+
this.timestampFn = config.timestamp;
|
|
344
|
+
} else {
|
|
345
|
+
this.timestampFn = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Create a child logger with additional context
|
|
350
|
+
*/
|
|
351
|
+
child(context) {
|
|
352
|
+
const levelEntry = Object.entries(LogLevel).find(([_, v]) => v === this.level);
|
|
353
|
+
const levelName = levelEntry ? levelEntry[0] : "INFO";
|
|
354
|
+
const child = new _Logger({
|
|
355
|
+
level: levelName,
|
|
356
|
+
name: this.name,
|
|
357
|
+
context: { ...this.context, ...context },
|
|
358
|
+
transports: this.transports,
|
|
359
|
+
redact: this.redactFields
|
|
360
|
+
});
|
|
361
|
+
return child;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Log a message
|
|
365
|
+
*/
|
|
366
|
+
log(level, message, context, error) {
|
|
367
|
+
const levelValue = LogLevel[level];
|
|
368
|
+
if (levelValue < this.level) return;
|
|
369
|
+
let finalContext = { ...this.context };
|
|
370
|
+
if (this.name) {
|
|
371
|
+
finalContext["module"] = this.name;
|
|
372
|
+
}
|
|
373
|
+
if (context) {
|
|
374
|
+
finalContext = { ...finalContext, ...context };
|
|
375
|
+
}
|
|
376
|
+
finalContext = redactFields(finalContext, this.redactFields);
|
|
377
|
+
const entry = {
|
|
378
|
+
level,
|
|
379
|
+
levelValue,
|
|
380
|
+
message,
|
|
381
|
+
timestamp: this.timestampFn(),
|
|
382
|
+
context: Object.keys(finalContext).length > 0 ? finalContext : void 0,
|
|
383
|
+
error: error ? {
|
|
384
|
+
name: error.name,
|
|
385
|
+
message: error.message,
|
|
386
|
+
stack: error.stack
|
|
387
|
+
} : void 0
|
|
388
|
+
};
|
|
389
|
+
for (const transport of this.transports) {
|
|
390
|
+
transport.log(entry);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
trace(message, context) {
|
|
394
|
+
this.log("TRACE", message, context);
|
|
395
|
+
}
|
|
396
|
+
debug(message, context) {
|
|
397
|
+
this.log("DEBUG", message, context);
|
|
398
|
+
}
|
|
399
|
+
info(message, context) {
|
|
400
|
+
this.log("INFO", message, context);
|
|
401
|
+
}
|
|
402
|
+
warn(message, context) {
|
|
403
|
+
this.log("WARN", message, context);
|
|
404
|
+
}
|
|
405
|
+
error(message, error, context) {
|
|
406
|
+
const err2 = error instanceof Error ? error : void 0;
|
|
407
|
+
const ctx = error instanceof Error ? context : error;
|
|
408
|
+
this.log("ERROR", message, ctx, err2);
|
|
409
|
+
}
|
|
410
|
+
fatal(message, error, context) {
|
|
411
|
+
const err2 = error instanceof Error ? error : void 0;
|
|
412
|
+
const ctx = error instanceof Error ? context : error;
|
|
413
|
+
this.log("FATAL", message, ctx, err2);
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
function createLogger(config) {
|
|
417
|
+
return new Logger(config);
|
|
418
|
+
}
|
|
419
|
+
var logger = createLogger();
|
|
420
|
+
function logError(log, error, message, context) {
|
|
421
|
+
if (error instanceof Error) {
|
|
422
|
+
log.error(message, error, context);
|
|
423
|
+
} else {
|
|
424
|
+
log.error(message, { error: String(error), ...context });
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async function measureTime(log, operation, fn) {
|
|
428
|
+
const start = Date.now();
|
|
429
|
+
try {
|
|
430
|
+
const result = await fn();
|
|
431
|
+
const duration = Date.now() - start;
|
|
432
|
+
log.info(`${operation} completed`, { operation, durationMs: duration });
|
|
433
|
+
return result;
|
|
434
|
+
} catch (error) {
|
|
435
|
+
const duration = Date.now() - start;
|
|
436
|
+
logError(log, error, `${operation} failed`, { operation, durationMs: duration });
|
|
437
|
+
throw error;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
function createRequestLogger(baseLogger, request) {
|
|
441
|
+
const pathname = request.url ? new URL(request.url).pathname : void 0;
|
|
442
|
+
return baseLogger.child({
|
|
443
|
+
requestId: request.requestId,
|
|
444
|
+
method: request.method,
|
|
445
|
+
path: pathname,
|
|
446
|
+
userId: request.userId,
|
|
447
|
+
tenantId: request.tenantId
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/decimal.ts
|
|
452
|
+
var PRECISION = 20;
|
|
453
|
+
var Decimal = class _Decimal {
|
|
454
|
+
value;
|
|
455
|
+
constructor(value) {
|
|
456
|
+
if (value instanceof _Decimal) {
|
|
457
|
+
this.value = value.value;
|
|
458
|
+
} else if (typeof value === "number") {
|
|
459
|
+
this.value = this.normalizeNumber(value);
|
|
460
|
+
} else {
|
|
461
|
+
this.value = this.normalizeString(value);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
normalizeNumber(n) {
|
|
465
|
+
if (!isFinite(n)) {
|
|
466
|
+
throw new Error(`Invalid number: ${n}`);
|
|
467
|
+
}
|
|
468
|
+
return n.toFixed(PRECISION).replace(/\.?0+$/, "") || "0";
|
|
469
|
+
}
|
|
470
|
+
normalizeString(s) {
|
|
471
|
+
const trimmed = s.trim();
|
|
472
|
+
if (!/^-?\d*\.?\d+$/.test(trimmed)) {
|
|
473
|
+
throw new Error(`Invalid decimal string: ${s}`);
|
|
474
|
+
}
|
|
475
|
+
return trimmed.replace(/^(-?)0+(?=\d)/, "$1").replace(/\.?0+$/, "") || "0";
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Add two decimals
|
|
479
|
+
*/
|
|
480
|
+
add(other) {
|
|
481
|
+
const a = parseFloat(this.value);
|
|
482
|
+
const b = parseFloat(other instanceof _Decimal ? other.value : String(other));
|
|
483
|
+
return new _Decimal(a + b);
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Subtract
|
|
487
|
+
*/
|
|
488
|
+
sub(other) {
|
|
489
|
+
const a = parseFloat(this.value);
|
|
490
|
+
const b = parseFloat(other instanceof _Decimal ? other.value : String(other));
|
|
491
|
+
return new _Decimal(a - b);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Multiply
|
|
495
|
+
*/
|
|
496
|
+
mul(other) {
|
|
497
|
+
const a = parseFloat(this.value);
|
|
498
|
+
const b = parseFloat(other instanceof _Decimal ? other.value : String(other));
|
|
499
|
+
return new _Decimal(a * b);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Divide
|
|
503
|
+
*/
|
|
504
|
+
div(other) {
|
|
505
|
+
const a = parseFloat(this.value);
|
|
506
|
+
const b = parseFloat(other instanceof _Decimal ? other.value : String(other));
|
|
507
|
+
if (b === 0) {
|
|
508
|
+
throw new Error("Division by zero");
|
|
509
|
+
}
|
|
510
|
+
return new _Decimal(a / b);
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Modulo
|
|
514
|
+
*/
|
|
515
|
+
mod(other) {
|
|
516
|
+
const a = parseFloat(this.value);
|
|
517
|
+
const b = parseFloat(other instanceof _Decimal ? other.value : String(other));
|
|
518
|
+
return new _Decimal(a % b);
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Power
|
|
522
|
+
*/
|
|
523
|
+
pow(exp) {
|
|
524
|
+
const a = parseFloat(this.value);
|
|
525
|
+
return new _Decimal(Math.pow(a, exp));
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Square root
|
|
529
|
+
*/
|
|
530
|
+
sqrt() {
|
|
531
|
+
const a = parseFloat(this.value);
|
|
532
|
+
if (a < 0) {
|
|
533
|
+
throw new Error("Square root of negative number");
|
|
534
|
+
}
|
|
535
|
+
return new _Decimal(Math.sqrt(a));
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Absolute value
|
|
539
|
+
*/
|
|
540
|
+
abs() {
|
|
541
|
+
const a = parseFloat(this.value);
|
|
542
|
+
return new _Decimal(Math.abs(a));
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Negate
|
|
546
|
+
*/
|
|
547
|
+
neg() {
|
|
548
|
+
const a = parseFloat(this.value);
|
|
549
|
+
return new _Decimal(-a);
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Round to decimal places
|
|
553
|
+
*/
|
|
554
|
+
round(decimals = 0) {
|
|
555
|
+
const a = parseFloat(this.value);
|
|
556
|
+
const factor = Math.pow(10, decimals);
|
|
557
|
+
return new _Decimal(Math.round(a * factor) / factor);
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Floor to decimal places
|
|
561
|
+
*/
|
|
562
|
+
floor(decimals = 0) {
|
|
563
|
+
const a = parseFloat(this.value);
|
|
564
|
+
const factor = Math.pow(10, decimals);
|
|
565
|
+
return new _Decimal(Math.floor(a * factor) / factor);
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Ceiling to decimal places
|
|
569
|
+
*/
|
|
570
|
+
ceil(decimals = 0) {
|
|
571
|
+
const a = parseFloat(this.value);
|
|
572
|
+
const factor = Math.pow(10, decimals);
|
|
573
|
+
return new _Decimal(Math.ceil(a * factor) / factor);
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Compare: returns -1, 0, or 1
|
|
577
|
+
*/
|
|
578
|
+
cmp(other) {
|
|
579
|
+
const a = parseFloat(this.value);
|
|
580
|
+
const b = parseFloat(other instanceof _Decimal ? other.value : String(other));
|
|
581
|
+
if (a < b) return -1;
|
|
582
|
+
if (a > b) return 1;
|
|
583
|
+
return 0;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Equality check
|
|
587
|
+
*/
|
|
588
|
+
eq(other) {
|
|
589
|
+
return this.cmp(other) === 0;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Greater than
|
|
593
|
+
*/
|
|
594
|
+
gt(other) {
|
|
595
|
+
return this.cmp(other) === 1;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Greater than or equal
|
|
599
|
+
*/
|
|
600
|
+
gte(other) {
|
|
601
|
+
return this.cmp(other) >= 0;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Less than
|
|
605
|
+
*/
|
|
606
|
+
lt(other) {
|
|
607
|
+
return this.cmp(other) === -1;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Less than or equal
|
|
611
|
+
*/
|
|
612
|
+
lte(other) {
|
|
613
|
+
return this.cmp(other) <= 0;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Check if zero
|
|
617
|
+
*/
|
|
618
|
+
isZero() {
|
|
619
|
+
return parseFloat(this.value) === 0;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Check if positive
|
|
623
|
+
*/
|
|
624
|
+
isPositive() {
|
|
625
|
+
return parseFloat(this.value) > 0;
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Check if negative
|
|
629
|
+
*/
|
|
630
|
+
isNegative() {
|
|
631
|
+
return parseFloat(this.value) < 0;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Convert to number
|
|
635
|
+
*/
|
|
636
|
+
toNumber() {
|
|
637
|
+
return parseFloat(this.value);
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Convert to string
|
|
641
|
+
*/
|
|
642
|
+
toString() {
|
|
643
|
+
return this.value;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Format with fixed decimal places
|
|
647
|
+
*/
|
|
648
|
+
toFixed(decimals = 2) {
|
|
649
|
+
return parseFloat(this.value).toFixed(decimals);
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Convert to JSON (string representation)
|
|
653
|
+
*/
|
|
654
|
+
toJSON() {
|
|
655
|
+
return this.value;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Static: Create from value
|
|
659
|
+
*/
|
|
660
|
+
static from(value) {
|
|
661
|
+
return new _Decimal(value);
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Static: Sum array of values
|
|
665
|
+
*/
|
|
666
|
+
static sum(values) {
|
|
667
|
+
return values.reduce(
|
|
668
|
+
(acc, val) => acc.add(val),
|
|
669
|
+
new _Decimal(0)
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Static: Average of array
|
|
674
|
+
*/
|
|
675
|
+
static avg(values) {
|
|
676
|
+
if (values.length === 0) return new _Decimal(0);
|
|
677
|
+
return _Decimal.sum(values).div(values.length);
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Static: Min of array
|
|
681
|
+
*/
|
|
682
|
+
static min(...values) {
|
|
683
|
+
if (values.length === 0) throw new Error("No values provided");
|
|
684
|
+
return values.reduce((min, val) => {
|
|
685
|
+
const d = new _Decimal(val);
|
|
686
|
+
return d.lt(min) ? d : min;
|
|
687
|
+
}, new _Decimal(values[0]));
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Static: Max of array
|
|
691
|
+
*/
|
|
692
|
+
static max(...values) {
|
|
693
|
+
if (values.length === 0) throw new Error("No values provided");
|
|
694
|
+
return values.reduce((max, val) => {
|
|
695
|
+
const d = new _Decimal(val);
|
|
696
|
+
return d.gt(max) ? d : max;
|
|
697
|
+
}, new _Decimal(values[0]));
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
var DecimalUtils = {
|
|
701
|
+
/**
|
|
702
|
+
* Convert number to database decimal string
|
|
703
|
+
*/
|
|
704
|
+
toDecimalString(value) {
|
|
705
|
+
if (value === null || value === void 0) return null;
|
|
706
|
+
return new Decimal(value).toString();
|
|
707
|
+
},
|
|
708
|
+
/**
|
|
709
|
+
* Convert database decimal string to number
|
|
710
|
+
*/
|
|
711
|
+
fromDecimalString(value) {
|
|
712
|
+
if (!value) return 0;
|
|
713
|
+
return new Decimal(value).toNumber();
|
|
714
|
+
},
|
|
715
|
+
/**
|
|
716
|
+
* Perform precise decimal multiplication
|
|
717
|
+
*/
|
|
718
|
+
multiply(a, b) {
|
|
719
|
+
return new Decimal(a).mul(b).toString();
|
|
720
|
+
},
|
|
721
|
+
/**
|
|
722
|
+
* Perform precise decimal addition
|
|
723
|
+
*/
|
|
724
|
+
add(a, b) {
|
|
725
|
+
return new Decimal(a).add(b).toString();
|
|
726
|
+
},
|
|
727
|
+
/**
|
|
728
|
+
* Perform precise decimal subtraction
|
|
729
|
+
*/
|
|
730
|
+
subtract(a, b) {
|
|
731
|
+
return new Decimal(a).sub(b).toString();
|
|
732
|
+
},
|
|
733
|
+
/**
|
|
734
|
+
* Perform precise decimal division
|
|
735
|
+
*/
|
|
736
|
+
divide(a, b) {
|
|
737
|
+
return new Decimal(a).div(b).toString();
|
|
738
|
+
},
|
|
739
|
+
/**
|
|
740
|
+
* Format decimal for display
|
|
741
|
+
*/
|
|
742
|
+
format(value, decimalPlaces = 2) {
|
|
743
|
+
return new Decimal(value).toFixed(decimalPlaces);
|
|
744
|
+
},
|
|
745
|
+
/**
|
|
746
|
+
* Format as currency
|
|
747
|
+
*/
|
|
748
|
+
formatCurrency(value, options = {}) {
|
|
749
|
+
const { currency = "USD", locale = "en-US", decimals = 2 } = options;
|
|
750
|
+
const num = new Decimal(value).toNumber();
|
|
751
|
+
return new Intl.NumberFormat(locale, {
|
|
752
|
+
style: "currency",
|
|
753
|
+
currency,
|
|
754
|
+
minimumFractionDigits: decimals,
|
|
755
|
+
maximumFractionDigits: decimals
|
|
756
|
+
}).format(num);
|
|
757
|
+
},
|
|
758
|
+
/**
|
|
759
|
+
* Convert object with decimal fields for database insert/update
|
|
760
|
+
*/
|
|
761
|
+
prepareForDatabase(data, decimalFields) {
|
|
762
|
+
const result = { ...data };
|
|
763
|
+
for (const field of decimalFields) {
|
|
764
|
+
if (field in result && result[field] !== void 0 && result[field] !== null) {
|
|
765
|
+
const value = result[field];
|
|
766
|
+
if (typeof value === "number") {
|
|
767
|
+
result[field] = DecimalUtils.toDecimalString(value);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return result;
|
|
772
|
+
},
|
|
773
|
+
/**
|
|
774
|
+
* Convert object with decimal fields from database
|
|
775
|
+
*/
|
|
776
|
+
parseFromDatabase(data, decimalFields) {
|
|
777
|
+
const result = { ...data };
|
|
778
|
+
for (const field of decimalFields) {
|
|
779
|
+
if (field in result && result[field] !== void 0 && result[field] !== null) {
|
|
780
|
+
const value = result[field];
|
|
781
|
+
if (typeof value === "string") {
|
|
782
|
+
result[field] = DecimalUtils.fromDecimalString(value);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
return result;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
function decimal(value) {
|
|
790
|
+
return new Decimal(value);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// src/types.ts
|
|
794
|
+
function ok(data) {
|
|
795
|
+
return { success: true, data };
|
|
796
|
+
}
|
|
797
|
+
function err(error) {
|
|
798
|
+
return { success: false, error };
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// src/errors.ts
|
|
802
|
+
var ParsError = class extends Error {
|
|
803
|
+
code;
|
|
804
|
+
statusCode;
|
|
805
|
+
details;
|
|
806
|
+
constructor(message, code, statusCode = 500, details) {
|
|
807
|
+
super(message);
|
|
808
|
+
this.name = "ParsError";
|
|
809
|
+
this.code = code;
|
|
810
|
+
this.statusCode = statusCode;
|
|
811
|
+
this.details = details ?? void 0;
|
|
812
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
813
|
+
}
|
|
814
|
+
toJSON() {
|
|
815
|
+
return {
|
|
816
|
+
name: this.name,
|
|
817
|
+
message: this.message,
|
|
818
|
+
code: this.code,
|
|
819
|
+
statusCode: this.statusCode,
|
|
820
|
+
details: this.details
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
var AuthError = class extends ParsError {
|
|
825
|
+
constructor(message, code = "AUTH_ERROR", statusCode = 401, details) {
|
|
826
|
+
super(message, code, statusCode, details);
|
|
827
|
+
this.name = "AuthError";
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
var UnauthorizedError = class extends AuthError {
|
|
831
|
+
constructor(message = "Unauthorized", details) {
|
|
832
|
+
super(message, "UNAUTHORIZED", 401, details);
|
|
833
|
+
this.name = "UnauthorizedError";
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
var ForbiddenError = class extends AuthError {
|
|
837
|
+
constructor(message = "Forbidden", details) {
|
|
838
|
+
super(message, "FORBIDDEN", 403, details);
|
|
839
|
+
this.name = "ForbiddenError";
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
var InvalidCredentialsError = class extends AuthError {
|
|
843
|
+
constructor(message = "Invalid credentials", details) {
|
|
844
|
+
super(message, "INVALID_CREDENTIALS", 401, details);
|
|
845
|
+
this.name = "InvalidCredentialsError";
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
var SessionExpiredError = class extends AuthError {
|
|
849
|
+
constructor(message = "Session expired", details) {
|
|
850
|
+
super(message, "SESSION_EXPIRED", 401, details);
|
|
851
|
+
this.name = "SessionExpiredError";
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
var TwoFactorRequiredError = class extends AuthError {
|
|
855
|
+
constructor(message = "Two-factor authentication required", challengeId, details) {
|
|
856
|
+
super(message, "TWO_FACTOR_REQUIRED", 403, { ...details, challengeId });
|
|
857
|
+
this.challengeId = challengeId;
|
|
858
|
+
this.name = "TwoFactorRequiredError";
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
var AccountLockedError = class extends AuthError {
|
|
862
|
+
constructor(message = "Account locked", lockedUntil, details) {
|
|
863
|
+
super(message, "ACCOUNT_LOCKED", 423, { ...details, lockedUntil });
|
|
864
|
+
this.lockedUntil = lockedUntil;
|
|
865
|
+
this.name = "AccountLockedError";
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
var TenantError = class extends ParsError {
|
|
869
|
+
constructor(message, code = "TENANT_ERROR", statusCode = 400, details) {
|
|
870
|
+
super(message, code, statusCode, details);
|
|
871
|
+
this.name = "TenantError";
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
var TenantNotFoundError = class extends TenantError {
|
|
875
|
+
constructor(message = "Tenant not found", details) {
|
|
876
|
+
super(message, "TENANT_NOT_FOUND", 404, details);
|
|
877
|
+
this.name = "TenantNotFoundError";
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
var TenantSuspendedError = class extends TenantError {
|
|
881
|
+
constructor(message = "Tenant suspended", details) {
|
|
882
|
+
super(message, "TENANT_SUSPENDED", 403, details);
|
|
883
|
+
this.name = "TenantSuspendedError";
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
var MembershipError = class extends TenantError {
|
|
887
|
+
constructor(message = "Membership error", code = "MEMBERSHIP_ERROR", statusCode = 400, details) {
|
|
888
|
+
super(message, code, statusCode, details);
|
|
889
|
+
this.name = "MembershipError";
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
var MembershipNotFoundError = class extends MembershipError {
|
|
893
|
+
constructor(message = "Membership not found", details) {
|
|
894
|
+
super(message, "MEMBERSHIP_NOT_FOUND", 404, details);
|
|
895
|
+
this.name = "MembershipNotFoundError";
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
var MembershipExpiredError = class extends MembershipError {
|
|
899
|
+
constructor(message = "Membership expired", details) {
|
|
900
|
+
super(message, "MEMBERSHIP_EXPIRED", 403, details);
|
|
901
|
+
this.name = "MembershipExpiredError";
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
var ValidationError = class extends ParsError {
|
|
905
|
+
constructor(message = "Validation failed", errors, details) {
|
|
906
|
+
super(message, "VALIDATION_ERROR", 400, { ...details, errors });
|
|
907
|
+
this.errors = errors;
|
|
908
|
+
this.name = "ValidationError";
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
var RateLimitError = class extends ParsError {
|
|
912
|
+
constructor(message = "Rate limit exceeded", retryAfter, details) {
|
|
913
|
+
super(message, "RATE_LIMIT_EXCEEDED", 429, { ...details, retryAfter });
|
|
914
|
+
this.retryAfter = retryAfter;
|
|
915
|
+
this.name = "RateLimitError";
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
var NotFoundError = class extends ParsError {
|
|
919
|
+
constructor(resource = "Resource", message, details) {
|
|
920
|
+
super(message ?? `${resource} not found`, "NOT_FOUND", 404, { ...details, resource });
|
|
921
|
+
this.name = "NotFoundError";
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
var ConflictError = class extends ParsError {
|
|
925
|
+
constructor(message = "Conflict", details) {
|
|
926
|
+
super(message, "CONFLICT", 409, details);
|
|
927
|
+
this.name = "ConflictError";
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
var DuplicateError = class extends ConflictError {
|
|
931
|
+
constructor(resource = "Resource", field, details) {
|
|
932
|
+
super(`${resource} already exists${field ? ` with this ${field}` : ""}`, {
|
|
933
|
+
...details,
|
|
934
|
+
resource,
|
|
935
|
+
field
|
|
936
|
+
});
|
|
937
|
+
this.name = "DuplicateError";
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
// src/error-codes.ts
|
|
942
|
+
var ErrorCodes = {
|
|
943
|
+
// ============================================================================
|
|
944
|
+
// Authentication Errors (401, 403, 423)
|
|
945
|
+
// ============================================================================
|
|
946
|
+
AUTH_ERROR: {
|
|
947
|
+
code: "AUTH_ERROR",
|
|
948
|
+
status: 401,
|
|
949
|
+
category: "auth"
|
|
950
|
+
},
|
|
951
|
+
UNAUTHORIZED: {
|
|
952
|
+
code: "UNAUTHORIZED",
|
|
953
|
+
status: 401,
|
|
954
|
+
category: "auth"
|
|
955
|
+
},
|
|
956
|
+
FORBIDDEN: {
|
|
957
|
+
code: "FORBIDDEN",
|
|
958
|
+
status: 403,
|
|
959
|
+
category: "auth"
|
|
960
|
+
},
|
|
961
|
+
INVALID_CREDENTIALS: {
|
|
962
|
+
code: "INVALID_CREDENTIALS",
|
|
963
|
+
status: 401,
|
|
964
|
+
category: "auth"
|
|
965
|
+
},
|
|
966
|
+
SESSION_EXPIRED: {
|
|
967
|
+
code: "SESSION_EXPIRED",
|
|
968
|
+
status: 401,
|
|
969
|
+
category: "auth"
|
|
970
|
+
},
|
|
971
|
+
TOKEN_EXPIRED: {
|
|
972
|
+
code: "TOKEN_EXPIRED",
|
|
973
|
+
status: 401,
|
|
974
|
+
category: "auth"
|
|
975
|
+
},
|
|
976
|
+
TOKEN_INVALID: {
|
|
977
|
+
code: "TOKEN_INVALID",
|
|
978
|
+
status: 401,
|
|
979
|
+
category: "auth"
|
|
980
|
+
},
|
|
981
|
+
TWO_FACTOR_REQUIRED: {
|
|
982
|
+
code: "TWO_FACTOR_REQUIRED",
|
|
983
|
+
status: 403,
|
|
984
|
+
category: "auth"
|
|
985
|
+
},
|
|
986
|
+
TWO_FACTOR_INVALID: {
|
|
987
|
+
code: "TWO_FACTOR_INVALID",
|
|
988
|
+
status: 401,
|
|
989
|
+
category: "auth"
|
|
990
|
+
},
|
|
991
|
+
ACCOUNT_LOCKED: {
|
|
992
|
+
code: "ACCOUNT_LOCKED",
|
|
993
|
+
status: 423,
|
|
994
|
+
category: "auth"
|
|
995
|
+
},
|
|
996
|
+
ACCOUNT_DISABLED: {
|
|
997
|
+
code: "ACCOUNT_DISABLED",
|
|
998
|
+
status: 403,
|
|
999
|
+
category: "auth"
|
|
1000
|
+
},
|
|
1001
|
+
PASSWORD_RESET_REQUIRED: {
|
|
1002
|
+
code: "PASSWORD_RESET_REQUIRED",
|
|
1003
|
+
status: 403,
|
|
1004
|
+
category: "auth"
|
|
1005
|
+
},
|
|
1006
|
+
// ============================================================================
|
|
1007
|
+
// Tenant Errors (400, 403, 404)
|
|
1008
|
+
// ============================================================================
|
|
1009
|
+
TENANT_ERROR: {
|
|
1010
|
+
code: "TENANT_ERROR",
|
|
1011
|
+
status: 400,
|
|
1012
|
+
category: "tenant"
|
|
1013
|
+
},
|
|
1014
|
+
TENANT_NOT_FOUND: {
|
|
1015
|
+
code: "TENANT_NOT_FOUND",
|
|
1016
|
+
status: 404,
|
|
1017
|
+
category: "tenant"
|
|
1018
|
+
},
|
|
1019
|
+
TENANT_SUSPENDED: {
|
|
1020
|
+
code: "TENANT_SUSPENDED",
|
|
1021
|
+
status: 403,
|
|
1022
|
+
category: "tenant"
|
|
1023
|
+
},
|
|
1024
|
+
TENANT_LIMIT_EXCEEDED: {
|
|
1025
|
+
code: "TENANT_LIMIT_EXCEEDED",
|
|
1026
|
+
status: 403,
|
|
1027
|
+
category: "tenant"
|
|
1028
|
+
},
|
|
1029
|
+
MEMBERSHIP_ERROR: {
|
|
1030
|
+
code: "MEMBERSHIP_ERROR",
|
|
1031
|
+
status: 400,
|
|
1032
|
+
category: "tenant"
|
|
1033
|
+
},
|
|
1034
|
+
MEMBERSHIP_NOT_FOUND: {
|
|
1035
|
+
code: "MEMBERSHIP_NOT_FOUND",
|
|
1036
|
+
status: 404,
|
|
1037
|
+
category: "tenant"
|
|
1038
|
+
},
|
|
1039
|
+
MEMBERSHIP_EXPIRED: {
|
|
1040
|
+
code: "MEMBERSHIP_EXPIRED",
|
|
1041
|
+
status: 403,
|
|
1042
|
+
category: "tenant"
|
|
1043
|
+
},
|
|
1044
|
+
// ============================================================================
|
|
1045
|
+
// Validation Errors (400, 422)
|
|
1046
|
+
// ============================================================================
|
|
1047
|
+
VALIDATION_ERROR: {
|
|
1048
|
+
code: "VALIDATION_ERROR",
|
|
1049
|
+
status: 400,
|
|
1050
|
+
category: "validation"
|
|
1051
|
+
},
|
|
1052
|
+
BAD_REQUEST: {
|
|
1053
|
+
code: "BAD_REQUEST",
|
|
1054
|
+
status: 400,
|
|
1055
|
+
category: "validation"
|
|
1056
|
+
},
|
|
1057
|
+
INVALID_INPUT: {
|
|
1058
|
+
code: "INVALID_INPUT",
|
|
1059
|
+
status: 422,
|
|
1060
|
+
category: "validation"
|
|
1061
|
+
},
|
|
1062
|
+
MISSING_REQUIRED_FIELD: {
|
|
1063
|
+
code: "MISSING_REQUIRED_FIELD",
|
|
1064
|
+
status: 400,
|
|
1065
|
+
category: "validation"
|
|
1066
|
+
},
|
|
1067
|
+
INVALID_FORMAT: {
|
|
1068
|
+
code: "INVALID_FORMAT",
|
|
1069
|
+
status: 400,
|
|
1070
|
+
category: "validation"
|
|
1071
|
+
},
|
|
1072
|
+
// ============================================================================
|
|
1073
|
+
// Resource Errors (404, 409, 410)
|
|
1074
|
+
// ============================================================================
|
|
1075
|
+
NOT_FOUND: {
|
|
1076
|
+
code: "NOT_FOUND",
|
|
1077
|
+
status: 404,
|
|
1078
|
+
category: "resource"
|
|
1079
|
+
},
|
|
1080
|
+
CONFLICT: {
|
|
1081
|
+
code: "CONFLICT",
|
|
1082
|
+
status: 409,
|
|
1083
|
+
category: "resource"
|
|
1084
|
+
},
|
|
1085
|
+
DUPLICATE: {
|
|
1086
|
+
code: "DUPLICATE",
|
|
1087
|
+
status: 409,
|
|
1088
|
+
category: "resource"
|
|
1089
|
+
},
|
|
1090
|
+
GONE: {
|
|
1091
|
+
code: "GONE",
|
|
1092
|
+
status: 410,
|
|
1093
|
+
category: "resource"
|
|
1094
|
+
},
|
|
1095
|
+
RESOURCE_LOCKED: {
|
|
1096
|
+
code: "RESOURCE_LOCKED",
|
|
1097
|
+
status: 423,
|
|
1098
|
+
category: "resource"
|
|
1099
|
+
},
|
|
1100
|
+
// ============================================================================
|
|
1101
|
+
// Rate Limiting (429)
|
|
1102
|
+
// ============================================================================
|
|
1103
|
+
RATE_LIMIT_EXCEEDED: {
|
|
1104
|
+
code: "RATE_LIMIT_EXCEEDED",
|
|
1105
|
+
status: 429,
|
|
1106
|
+
category: "rate_limit",
|
|
1107
|
+
retryable: true
|
|
1108
|
+
},
|
|
1109
|
+
QUOTA_EXCEEDED: {
|
|
1110
|
+
code: "QUOTA_EXCEEDED",
|
|
1111
|
+
status: 429,
|
|
1112
|
+
category: "rate_limit"
|
|
1113
|
+
},
|
|
1114
|
+
// ============================================================================
|
|
1115
|
+
// Server Errors (500, 502, 503, 504)
|
|
1116
|
+
// ============================================================================
|
|
1117
|
+
INTERNAL_ERROR: {
|
|
1118
|
+
code: "INTERNAL_ERROR",
|
|
1119
|
+
status: 500,
|
|
1120
|
+
category: "server"
|
|
1121
|
+
},
|
|
1122
|
+
BAD_GATEWAY: {
|
|
1123
|
+
code: "BAD_GATEWAY",
|
|
1124
|
+
status: 502,
|
|
1125
|
+
category: "server",
|
|
1126
|
+
retryable: true
|
|
1127
|
+
},
|
|
1128
|
+
SERVICE_UNAVAILABLE: {
|
|
1129
|
+
code: "SERVICE_UNAVAILABLE",
|
|
1130
|
+
status: 503,
|
|
1131
|
+
category: "server",
|
|
1132
|
+
retryable: true
|
|
1133
|
+
},
|
|
1134
|
+
GATEWAY_TIMEOUT: {
|
|
1135
|
+
code: "GATEWAY_TIMEOUT",
|
|
1136
|
+
status: 504,
|
|
1137
|
+
category: "server",
|
|
1138
|
+
retryable: true
|
|
1139
|
+
},
|
|
1140
|
+
// ============================================================================
|
|
1141
|
+
// Database Errors (500)
|
|
1142
|
+
// ============================================================================
|
|
1143
|
+
DATABASE_ERROR: {
|
|
1144
|
+
code: "DATABASE_ERROR",
|
|
1145
|
+
status: 500,
|
|
1146
|
+
category: "database"
|
|
1147
|
+
},
|
|
1148
|
+
CONNECTION_ERROR: {
|
|
1149
|
+
code: "CONNECTION_ERROR",
|
|
1150
|
+
status: 503,
|
|
1151
|
+
category: "database",
|
|
1152
|
+
retryable: true
|
|
1153
|
+
},
|
|
1154
|
+
TRANSACTION_ERROR: {
|
|
1155
|
+
code: "TRANSACTION_ERROR",
|
|
1156
|
+
status: 500,
|
|
1157
|
+
category: "database"
|
|
1158
|
+
},
|
|
1159
|
+
RLS_ERROR: {
|
|
1160
|
+
code: "RLS_ERROR",
|
|
1161
|
+
status: 500,
|
|
1162
|
+
category: "database"
|
|
1163
|
+
},
|
|
1164
|
+
// ============================================================================
|
|
1165
|
+
// External Service Errors (502, 503)
|
|
1166
|
+
// ============================================================================
|
|
1167
|
+
EXTERNAL_SERVICE_ERROR: {
|
|
1168
|
+
code: "EXTERNAL_SERVICE_ERROR",
|
|
1169
|
+
status: 502,
|
|
1170
|
+
category: "external",
|
|
1171
|
+
retryable: true
|
|
1172
|
+
},
|
|
1173
|
+
EXTERNAL_TIMEOUT: {
|
|
1174
|
+
code: "EXTERNAL_TIMEOUT",
|
|
1175
|
+
status: 504,
|
|
1176
|
+
category: "external",
|
|
1177
|
+
retryable: true
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
1180
|
+
function getErrorCode(code) {
|
|
1181
|
+
return ErrorCodes[code];
|
|
1182
|
+
}
|
|
1183
|
+
function getErrorCodesByCategory(category) {
|
|
1184
|
+
return Object.values(ErrorCodes).filter((e) => e.category === category);
|
|
1185
|
+
}
|
|
1186
|
+
function isRetryableError(code) {
|
|
1187
|
+
const errorCode = getErrorCode(code);
|
|
1188
|
+
return errorCode?.retryable === true;
|
|
1189
|
+
}
|
|
1190
|
+
function getStatusForCode(code) {
|
|
1191
|
+
const errorCode = getErrorCode(code);
|
|
1192
|
+
return errorCode?.status ?? 500;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// src/transports/axiom.ts
|
|
1196
|
+
var AxiomTransport = class {
|
|
1197
|
+
name = "axiom";
|
|
1198
|
+
buffer = [];
|
|
1199
|
+
flushTimer = null;
|
|
1200
|
+
isFlushing = false;
|
|
1201
|
+
options;
|
|
1202
|
+
constructor(options) {
|
|
1203
|
+
this.options = {
|
|
1204
|
+
batchSize: 100,
|
|
1205
|
+
flushInterval: 5e3,
|
|
1206
|
+
apiUrl: "https://api.axiom.co",
|
|
1207
|
+
...options
|
|
1208
|
+
};
|
|
1209
|
+
if (this.options.flushInterval > 0) {
|
|
1210
|
+
this.flushTimer = setInterval(
|
|
1211
|
+
() => this.flush(),
|
|
1212
|
+
this.options.flushInterval
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
log(entry) {
|
|
1217
|
+
if (this.options.enabled === false) return;
|
|
1218
|
+
const event = {
|
|
1219
|
+
_time: entry.timestamp,
|
|
1220
|
+
level: entry.level,
|
|
1221
|
+
message: entry.message
|
|
1222
|
+
};
|
|
1223
|
+
if (entry.context) {
|
|
1224
|
+
Object.assign(event, entry.context);
|
|
1225
|
+
}
|
|
1226
|
+
if (entry.error) {
|
|
1227
|
+
event["error.name"] = entry.error.name;
|
|
1228
|
+
event["error.message"] = entry.error.message;
|
|
1229
|
+
event["error.stack"] = entry.error.stack;
|
|
1230
|
+
}
|
|
1231
|
+
this.buffer.push(event);
|
|
1232
|
+
if (this.buffer.length >= this.options.batchSize) {
|
|
1233
|
+
this.flush();
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
async flush() {
|
|
1237
|
+
if (this.isFlushing || this.buffer.length === 0) return;
|
|
1238
|
+
this.isFlushing = true;
|
|
1239
|
+
const events = this.buffer;
|
|
1240
|
+
this.buffer = [];
|
|
1241
|
+
try {
|
|
1242
|
+
const response = await fetch(
|
|
1243
|
+
`${this.options.apiUrl}/v1/datasets/${this.options.dataset}/ingest`,
|
|
1244
|
+
{
|
|
1245
|
+
method: "POST",
|
|
1246
|
+
headers: {
|
|
1247
|
+
Authorization: `Bearer ${this.options.token}`,
|
|
1248
|
+
"Content-Type": "application/json",
|
|
1249
|
+
...this.options.orgId && {
|
|
1250
|
+
"X-Axiom-Org-Id": this.options.orgId
|
|
1251
|
+
}
|
|
1252
|
+
},
|
|
1253
|
+
body: JSON.stringify(events)
|
|
1254
|
+
}
|
|
1255
|
+
);
|
|
1256
|
+
if (!response.ok) {
|
|
1257
|
+
const errorText = await response.text();
|
|
1258
|
+
throw new Error(`Axiom ingest failed: ${response.status} ${errorText}`);
|
|
1259
|
+
}
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
if (this.options.onError) {
|
|
1262
|
+
this.options.onError(
|
|
1263
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
1264
|
+
events.length
|
|
1265
|
+
);
|
|
1266
|
+
} else {
|
|
1267
|
+
console.error("[Axiom] Failed to send logs:", error);
|
|
1268
|
+
}
|
|
1269
|
+
} finally {
|
|
1270
|
+
this.isFlushing = false;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
async close() {
|
|
1274
|
+
if (this.flushTimer) {
|
|
1275
|
+
clearInterval(this.flushTimer);
|
|
1276
|
+
this.flushTimer = null;
|
|
1277
|
+
}
|
|
1278
|
+
await this.flush();
|
|
1279
|
+
}
|
|
1280
|
+
};
|
|
1281
|
+
function createAxiomTransport(options) {
|
|
1282
|
+
return new AxiomTransport(options);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// src/transports/sentry.ts
|
|
1286
|
+
var SentryTransport = class {
|
|
1287
|
+
name = "sentry";
|
|
1288
|
+
client;
|
|
1289
|
+
dsn;
|
|
1290
|
+
options;
|
|
1291
|
+
user = null;
|
|
1292
|
+
contexts = /* @__PURE__ */ new Map();
|
|
1293
|
+
breadcrumbs = [];
|
|
1294
|
+
maxBreadcrumbs = 100;
|
|
1295
|
+
constructor(options) {
|
|
1296
|
+
this.options = {
|
|
1297
|
+
sampleRate: 1,
|
|
1298
|
+
...options
|
|
1299
|
+
};
|
|
1300
|
+
if (options.client) {
|
|
1301
|
+
this.client = options.client;
|
|
1302
|
+
} else if (options.dsn) {
|
|
1303
|
+
this.dsn = this.parseDSN(options.dsn);
|
|
1304
|
+
} else {
|
|
1305
|
+
throw new Error("SentryTransport requires either 'dsn' or 'client' option");
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Parse Sentry DSN
|
|
1310
|
+
*/
|
|
1311
|
+
parseDSN(dsn) {
|
|
1312
|
+
const match = dsn.match(/^(https?):\/\/([^@]+)@([^/]+)\/(.+)$/);
|
|
1313
|
+
if (!match || !match[1] || !match[2] || !match[3] || !match[4]) {
|
|
1314
|
+
throw new Error(`Invalid Sentry DSN: ${dsn}`);
|
|
1315
|
+
}
|
|
1316
|
+
return {
|
|
1317
|
+
protocol: match[1],
|
|
1318
|
+
publicKey: match[2],
|
|
1319
|
+
host: match[3],
|
|
1320
|
+
projectId: match[4]
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* LogTransport implementation
|
|
1325
|
+
* Only sends ERROR and FATAL level logs
|
|
1326
|
+
*/
|
|
1327
|
+
log(entry) {
|
|
1328
|
+
if (this.options.enabled === false) return;
|
|
1329
|
+
if (entry.levelValue < 50) return;
|
|
1330
|
+
if (entry.error) {
|
|
1331
|
+
const error = new Error(entry.error.message);
|
|
1332
|
+
error.name = entry.error.name;
|
|
1333
|
+
if (entry.error.stack) {
|
|
1334
|
+
error.stack = entry.error.stack;
|
|
1335
|
+
}
|
|
1336
|
+
this.captureException(
|
|
1337
|
+
error,
|
|
1338
|
+
entry.context ? { extra: entry.context } : void 0
|
|
1339
|
+
);
|
|
1340
|
+
} else {
|
|
1341
|
+
this.captureMessage(
|
|
1342
|
+
entry.message,
|
|
1343
|
+
entry.level === "FATAL" ? "error" : "warning",
|
|
1344
|
+
entry.context ? { extra: entry.context } : void 0
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Capture an exception
|
|
1350
|
+
*/
|
|
1351
|
+
captureException(error, context) {
|
|
1352
|
+
if (this.options.enabled === false) return;
|
|
1353
|
+
if (!this.shouldSample()) return;
|
|
1354
|
+
if (this.client) {
|
|
1355
|
+
this.captureWithSdk(error, context);
|
|
1356
|
+
} else {
|
|
1357
|
+
this.captureWithHttp(error, context);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Capture a message
|
|
1362
|
+
*/
|
|
1363
|
+
captureMessage(message, level, context) {
|
|
1364
|
+
if (this.options.enabled === false) return;
|
|
1365
|
+
if (!this.shouldSample()) return;
|
|
1366
|
+
if (this.client) {
|
|
1367
|
+
this.client.withScope((scope) => {
|
|
1368
|
+
this.applyContext(scope, context);
|
|
1369
|
+
scope.setLevel(level);
|
|
1370
|
+
this.client.captureMessage(message, level);
|
|
1371
|
+
});
|
|
1372
|
+
} else {
|
|
1373
|
+
this.sendHttpEvent({
|
|
1374
|
+
level: level === "warning" ? "warning" : level === "info" ? "info" : "error",
|
|
1375
|
+
message: { formatted: message },
|
|
1376
|
+
...this.buildEventContext(context)
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Set user context
|
|
1382
|
+
*/
|
|
1383
|
+
setUser(user) {
|
|
1384
|
+
this.user = user;
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Set custom context
|
|
1388
|
+
*/
|
|
1389
|
+
setContext(name, context) {
|
|
1390
|
+
this.contexts.set(name, context);
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Add breadcrumb
|
|
1394
|
+
*/
|
|
1395
|
+
addBreadcrumb(breadcrumb) {
|
|
1396
|
+
this.breadcrumbs.push({
|
|
1397
|
+
...breadcrumb,
|
|
1398
|
+
timestamp: breadcrumb.timestamp ?? Date.now() / 1e3
|
|
1399
|
+
});
|
|
1400
|
+
if (this.breadcrumbs.length > this.maxBreadcrumbs) {
|
|
1401
|
+
this.breadcrumbs = this.breadcrumbs.slice(-this.maxBreadcrumbs);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Flush pending events
|
|
1406
|
+
*/
|
|
1407
|
+
async flush() {
|
|
1408
|
+
if (this.client?.flush) {
|
|
1409
|
+
await this.client.flush(2e3);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
// ============================================================================
|
|
1413
|
+
// Private Methods
|
|
1414
|
+
// ============================================================================
|
|
1415
|
+
shouldSample() {
|
|
1416
|
+
const rate = this.options.sampleRate ?? 1;
|
|
1417
|
+
return Math.random() < rate;
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Capture with SDK (BYOS mode)
|
|
1421
|
+
*/
|
|
1422
|
+
captureWithSdk(error, context) {
|
|
1423
|
+
this.client.withScope((scope) => {
|
|
1424
|
+
this.applyContext(scope, context);
|
|
1425
|
+
this.client.captureException(error);
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* Apply context to SDK scope
|
|
1430
|
+
*/
|
|
1431
|
+
applyContext(scope, context) {
|
|
1432
|
+
if (this.user) {
|
|
1433
|
+
scope.setUser(this.user);
|
|
1434
|
+
} else if (context?.userId) {
|
|
1435
|
+
scope.setUser({ id: context.userId });
|
|
1436
|
+
}
|
|
1437
|
+
if (this.options.tags) {
|
|
1438
|
+
for (const [key, value] of Object.entries(this.options.tags)) {
|
|
1439
|
+
scope.setTag(key, value);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
if (context?.tags) {
|
|
1443
|
+
for (const [key, value] of Object.entries(context.tags)) {
|
|
1444
|
+
scope.setTag(key, value);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
if (context?.requestId) {
|
|
1448
|
+
scope.setTag("requestId", context.requestId);
|
|
1449
|
+
}
|
|
1450
|
+
if (context?.tenantId) {
|
|
1451
|
+
scope.setTag("tenantId", context.tenantId);
|
|
1452
|
+
}
|
|
1453
|
+
if (context?.extra) {
|
|
1454
|
+
scope.setExtras(context.extra);
|
|
1455
|
+
}
|
|
1456
|
+
for (const bc of this.breadcrumbs) {
|
|
1457
|
+
scope.addBreadcrumb(bc);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Capture with HTTP API (default mode)
|
|
1462
|
+
*/
|
|
1463
|
+
captureWithHttp(error, context) {
|
|
1464
|
+
const stacktrace = this.parseStackTrace(error.stack);
|
|
1465
|
+
const exceptionValue = {
|
|
1466
|
+
type: error.name,
|
|
1467
|
+
value: error.message
|
|
1468
|
+
};
|
|
1469
|
+
if (stacktrace) {
|
|
1470
|
+
exceptionValue.stacktrace = stacktrace;
|
|
1471
|
+
}
|
|
1472
|
+
const event = {
|
|
1473
|
+
level: "error",
|
|
1474
|
+
exception: {
|
|
1475
|
+
values: [exceptionValue]
|
|
1476
|
+
},
|
|
1477
|
+
...this.buildEventContext(context)
|
|
1478
|
+
};
|
|
1479
|
+
this.sendHttpEvent(event);
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Build event context for HTTP API
|
|
1483
|
+
*/
|
|
1484
|
+
buildEventContext(context) {
|
|
1485
|
+
const event = {};
|
|
1486
|
+
if (this.options.environment) {
|
|
1487
|
+
event.environment = this.options.environment;
|
|
1488
|
+
}
|
|
1489
|
+
if (this.options.release) {
|
|
1490
|
+
event.release = this.options.release;
|
|
1491
|
+
}
|
|
1492
|
+
if (this.options.serverName) {
|
|
1493
|
+
event.server_name = this.options.serverName;
|
|
1494
|
+
}
|
|
1495
|
+
const tags = { ...this.options.tags };
|
|
1496
|
+
if (context?.tags) {
|
|
1497
|
+
Object.assign(tags, context.tags);
|
|
1498
|
+
}
|
|
1499
|
+
if (context?.requestId) {
|
|
1500
|
+
tags["requestId"] = context.requestId;
|
|
1501
|
+
}
|
|
1502
|
+
if (context?.tenantId) {
|
|
1503
|
+
tags["tenantId"] = context.tenantId;
|
|
1504
|
+
}
|
|
1505
|
+
if (Object.keys(tags).length > 0) {
|
|
1506
|
+
event.tags = tags;
|
|
1507
|
+
}
|
|
1508
|
+
if (context?.extra) {
|
|
1509
|
+
event.extra = context.extra;
|
|
1510
|
+
}
|
|
1511
|
+
if (this.user) {
|
|
1512
|
+
event.user = this.user;
|
|
1513
|
+
} else if (context?.userId) {
|
|
1514
|
+
event.user = { id: context.userId };
|
|
1515
|
+
}
|
|
1516
|
+
if (this.breadcrumbs.length > 0) {
|
|
1517
|
+
event.breadcrumbs = this.breadcrumbs.map((bc) => {
|
|
1518
|
+
const crumb = {};
|
|
1519
|
+
if (bc.type) crumb.type = bc.type;
|
|
1520
|
+
if (bc.category) crumb.category = bc.category;
|
|
1521
|
+
if (bc.message) crumb.message = bc.message;
|
|
1522
|
+
if (bc.data) crumb.data = bc.data;
|
|
1523
|
+
if (bc.level) crumb.level = bc.level;
|
|
1524
|
+
if (bc.timestamp !== void 0) crumb.timestamp = bc.timestamp;
|
|
1525
|
+
return crumb;
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
if (this.contexts.size > 0) {
|
|
1529
|
+
event.contexts = Object.fromEntries(this.contexts);
|
|
1530
|
+
}
|
|
1531
|
+
return event;
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Parse error stack trace into Sentry format
|
|
1535
|
+
*/
|
|
1536
|
+
parseStackTrace(stack) {
|
|
1537
|
+
if (!stack) return void 0;
|
|
1538
|
+
const lines = stack.split("\n").slice(1);
|
|
1539
|
+
const frames = [];
|
|
1540
|
+
for (const line of lines) {
|
|
1541
|
+
const match = line.match(/^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/);
|
|
1542
|
+
if (match && match[3] && match[4]) {
|
|
1543
|
+
const frame = {
|
|
1544
|
+
function: match[1] || "<anonymous>",
|
|
1545
|
+
lineno: parseInt(match[3], 10),
|
|
1546
|
+
colno: parseInt(match[4], 10)
|
|
1547
|
+
};
|
|
1548
|
+
if (match[2]) {
|
|
1549
|
+
frame.filename = match[2];
|
|
1550
|
+
}
|
|
1551
|
+
frames.push(frame);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
frames.reverse();
|
|
1555
|
+
return frames.length > 0 ? { frames } : void 0;
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1558
|
+
* Generate event ID
|
|
1559
|
+
*/
|
|
1560
|
+
generateEventId() {
|
|
1561
|
+
const bytes = new Uint8Array(16);
|
|
1562
|
+
crypto.getRandomValues(bytes);
|
|
1563
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1564
|
+
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Send event via HTTP API
|
|
1567
|
+
*/
|
|
1568
|
+
async sendHttpEvent(eventData) {
|
|
1569
|
+
if (!this.dsn) return;
|
|
1570
|
+
const event = {
|
|
1571
|
+
event_id: this.generateEventId(),
|
|
1572
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1573
|
+
platform: "javascript",
|
|
1574
|
+
level: "error",
|
|
1575
|
+
...eventData
|
|
1576
|
+
};
|
|
1577
|
+
if (this.options.beforeSend) {
|
|
1578
|
+
const result = this.options.beforeSend(event);
|
|
1579
|
+
if (result === null) return;
|
|
1580
|
+
}
|
|
1581
|
+
const url = `${this.dsn.protocol}://${this.dsn.host}/api/${this.dsn.projectId}/store/`;
|
|
1582
|
+
try {
|
|
1583
|
+
const response = await fetch(url, {
|
|
1584
|
+
method: "POST",
|
|
1585
|
+
headers: {
|
|
1586
|
+
"Content-Type": "application/json",
|
|
1587
|
+
"X-Sentry-Auth": [
|
|
1588
|
+
"Sentry sentry_version=7",
|
|
1589
|
+
`sentry_client=pars-sentry/1.0.0`,
|
|
1590
|
+
`sentry_key=${this.dsn.publicKey}`
|
|
1591
|
+
].join(", ")
|
|
1592
|
+
},
|
|
1593
|
+
body: JSON.stringify(event)
|
|
1594
|
+
});
|
|
1595
|
+
if (!response.ok) {
|
|
1596
|
+
throw new Error(`Sentry API error: ${response.status}`);
|
|
1597
|
+
}
|
|
1598
|
+
} catch (error) {
|
|
1599
|
+
if (this.options.onError) {
|
|
1600
|
+
this.options.onError(error instanceof Error ? error : new Error(String(error)));
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1605
|
+
function createSentryTransport(options) {
|
|
1606
|
+
return new SentryTransport(options);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
// src/transports/logtape.ts
|
|
1610
|
+
var FallbackLogger = class {
|
|
1611
|
+
constructor(category) {
|
|
1612
|
+
this.category = category;
|
|
1613
|
+
}
|
|
1614
|
+
log(level, message, properties) {
|
|
1615
|
+
const entry = {
|
|
1616
|
+
level,
|
|
1617
|
+
category: this.category,
|
|
1618
|
+
msg: message,
|
|
1619
|
+
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1620
|
+
...properties
|
|
1621
|
+
};
|
|
1622
|
+
console.log(JSON.stringify(entry));
|
|
1623
|
+
}
|
|
1624
|
+
debug(message, properties) {
|
|
1625
|
+
this.log("debug", message, properties);
|
|
1626
|
+
}
|
|
1627
|
+
info(message, properties) {
|
|
1628
|
+
this.log("info", message, properties);
|
|
1629
|
+
}
|
|
1630
|
+
warn(message, properties) {
|
|
1631
|
+
this.log("warn", message, properties);
|
|
1632
|
+
}
|
|
1633
|
+
warning(message, properties) {
|
|
1634
|
+
this.log("warning", message, properties);
|
|
1635
|
+
}
|
|
1636
|
+
error(message, properties) {
|
|
1637
|
+
this.log("error", message, properties);
|
|
1638
|
+
}
|
|
1639
|
+
fatal(message, properties) {
|
|
1640
|
+
this.log("fatal", message, properties);
|
|
1641
|
+
}
|
|
1642
|
+
};
|
|
1643
|
+
var LogtapeTransport = class {
|
|
1644
|
+
name = "logtape";
|
|
1645
|
+
logger;
|
|
1646
|
+
includeTimestamp;
|
|
1647
|
+
includeLevelValue;
|
|
1648
|
+
enabled;
|
|
1649
|
+
constructor(options = {}) {
|
|
1650
|
+
this.enabled = options.enabled !== false;
|
|
1651
|
+
this.includeTimestamp = options.includeTimestamp !== false;
|
|
1652
|
+
this.includeLevelValue = options.includeLevelValue ?? false;
|
|
1653
|
+
if (options.logger) {
|
|
1654
|
+
this.logger = options.logger;
|
|
1655
|
+
} else {
|
|
1656
|
+
this.logger = new FallbackLogger(options.category ?? "pars");
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
log(entry) {
|
|
1660
|
+
if (!this.enabled) return;
|
|
1661
|
+
const level = this.mapLevel(entry.level);
|
|
1662
|
+
const properties = this.buildProperties(entry);
|
|
1663
|
+
this.logger[level](entry.message, properties);
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* Map Pars log level to Logtape level
|
|
1667
|
+
*/
|
|
1668
|
+
mapLevel(level) {
|
|
1669
|
+
const mapping = {
|
|
1670
|
+
TRACE: "debug",
|
|
1671
|
+
DEBUG: "debug",
|
|
1672
|
+
INFO: "info",
|
|
1673
|
+
WARN: "warning",
|
|
1674
|
+
ERROR: "error",
|
|
1675
|
+
FATAL: "fatal",
|
|
1676
|
+
SILENT: "debug"
|
|
1677
|
+
// Should never be logged
|
|
1678
|
+
};
|
|
1679
|
+
return mapping[level];
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Build properties object for Logtape
|
|
1683
|
+
*/
|
|
1684
|
+
buildProperties(entry) {
|
|
1685
|
+
const properties = {};
|
|
1686
|
+
if (this.includeTimestamp) {
|
|
1687
|
+
properties["timestamp"] = entry.timestamp;
|
|
1688
|
+
}
|
|
1689
|
+
if (this.includeLevelValue) {
|
|
1690
|
+
properties["levelValue"] = entry.levelValue;
|
|
1691
|
+
}
|
|
1692
|
+
if (entry.context) {
|
|
1693
|
+
Object.assign(properties, entry.context);
|
|
1694
|
+
}
|
|
1695
|
+
if (entry.error) {
|
|
1696
|
+
properties["error"] = {
|
|
1697
|
+
name: entry.error.name,
|
|
1698
|
+
message: entry.error.message,
|
|
1699
|
+
stack: entry.error.stack
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
return properties;
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1705
|
+
function createLogtapeTransport(options) {
|
|
1706
|
+
return new LogtapeTransport(options);
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// src/index.ts
|
|
1710
|
+
async function generateRandomString(length) {
|
|
1711
|
+
const bytes = new Uint8Array(length);
|
|
1712
|
+
crypto.getRandomValues(bytes);
|
|
1713
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1714
|
+
}
|
|
1715
|
+
function generateId() {
|
|
1716
|
+
return crypto.randomUUID();
|
|
1717
|
+
}
|
|
1718
|
+
async function sha256(input) {
|
|
1719
|
+
const encoder = new TextEncoder();
|
|
1720
|
+
const data = encoder.encode(input);
|
|
1721
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
1722
|
+
return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1723
|
+
}
|
|
1724
|
+
async function sha256Bytes(input) {
|
|
1725
|
+
const encoder = new TextEncoder();
|
|
1726
|
+
const data = encoder.encode(input);
|
|
1727
|
+
return crypto.subtle.digest("SHA-256", data);
|
|
1728
|
+
}
|
|
1729
|
+
function constantTimeEquals(a, b) {
|
|
1730
|
+
if (a.length !== b.length) {
|
|
1731
|
+
return false;
|
|
1732
|
+
}
|
|
1733
|
+
const aBytes = new TextEncoder().encode(a);
|
|
1734
|
+
const bBytes = new TextEncoder().encode(b);
|
|
1735
|
+
let result = 0;
|
|
1736
|
+
for (let i = 0; i < aBytes.length; i++) {
|
|
1737
|
+
result |= aBytes[i] ^ bBytes[i];
|
|
1738
|
+
}
|
|
1739
|
+
return result === 0;
|
|
1740
|
+
}
|
|
1741
|
+
function sleep(ms) {
|
|
1742
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1743
|
+
}
|
|
1744
|
+
async function retry(fn, options = {}) {
|
|
1745
|
+
const {
|
|
1746
|
+
maxRetries = 3,
|
|
1747
|
+
initialDelayMs = 1e3,
|
|
1748
|
+
maxDelayMs = 3e4,
|
|
1749
|
+
backoffMultiplier = 2,
|
|
1750
|
+
shouldRetry = () => true,
|
|
1751
|
+
onRetry
|
|
1752
|
+
} = options;
|
|
1753
|
+
let lastError;
|
|
1754
|
+
let delay = initialDelayMs;
|
|
1755
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1756
|
+
try {
|
|
1757
|
+
return await fn();
|
|
1758
|
+
} catch (error) {
|
|
1759
|
+
lastError = error;
|
|
1760
|
+
if (attempt === maxRetries || !shouldRetry(error)) {
|
|
1761
|
+
throw error;
|
|
1762
|
+
}
|
|
1763
|
+
onRetry?.(error, attempt + 1);
|
|
1764
|
+
await sleep(delay);
|
|
1765
|
+
delay = Math.min(delay * backoffMultiplier, maxDelayMs);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
throw lastError;
|
|
1769
|
+
}
|
|
1770
|
+
function omit(obj, keys) {
|
|
1771
|
+
const result = { ...obj };
|
|
1772
|
+
for (const key of keys) {
|
|
1773
|
+
delete result[key];
|
|
1774
|
+
}
|
|
1775
|
+
return result;
|
|
1776
|
+
}
|
|
1777
|
+
function pick(obj, keys) {
|
|
1778
|
+
const result = {};
|
|
1779
|
+
for (const key of keys) {
|
|
1780
|
+
if (key in obj) {
|
|
1781
|
+
result[key] = obj[key];
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
return result;
|
|
1785
|
+
}
|
|
1786
|
+
function deepMerge(target, source) {
|
|
1787
|
+
const result = { ...target };
|
|
1788
|
+
for (const key of Object.keys(source)) {
|
|
1789
|
+
const sourceValue = source[key];
|
|
1790
|
+
const targetValue = target[key];
|
|
1791
|
+
if (sourceValue !== void 0 && typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
|
|
1792
|
+
result[key] = deepMerge(
|
|
1793
|
+
targetValue,
|
|
1794
|
+
sourceValue
|
|
1795
|
+
);
|
|
1796
|
+
} else if (sourceValue !== void 0) {
|
|
1797
|
+
result[key] = sourceValue;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
return result;
|
|
1801
|
+
}
|
|
1802
|
+
function deepClone(obj) {
|
|
1803
|
+
if (obj === null || typeof obj !== "object") {
|
|
1804
|
+
return obj;
|
|
1805
|
+
}
|
|
1806
|
+
if (Array.isArray(obj)) {
|
|
1807
|
+
return obj.map(deepClone);
|
|
1808
|
+
}
|
|
1809
|
+
if (obj instanceof Date) {
|
|
1810
|
+
return new Date(obj.getTime());
|
|
1811
|
+
}
|
|
1812
|
+
const cloned = {};
|
|
1813
|
+
for (const key of Object.keys(obj)) {
|
|
1814
|
+
cloned[key] = deepClone(obj[key]);
|
|
1815
|
+
}
|
|
1816
|
+
return cloned;
|
|
1817
|
+
}
|
|
1818
|
+
function isPlainObject(value) {
|
|
1819
|
+
return typeof value === "object" && value !== null && Object.prototype.toString.call(value) === "[object Object]";
|
|
1820
|
+
}
|
|
1821
|
+
function isNil(value) {
|
|
1822
|
+
return value === null || value === void 0;
|
|
1823
|
+
}
|
|
1824
|
+
function isEmpty(value) {
|
|
1825
|
+
if (isNil(value)) return true;
|
|
1826
|
+
if (typeof value === "string") return value.trim() === "";
|
|
1827
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
1828
|
+
if (isPlainObject(value)) return Object.keys(value).length === 0;
|
|
1829
|
+
return false;
|
|
1830
|
+
}
|
|
1831
|
+
function normalizeEmail(email) {
|
|
1832
|
+
return email.toLowerCase().trim();
|
|
1833
|
+
}
|
|
1834
|
+
function isValidEmail(email) {
|
|
1835
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1836
|
+
return emailRegex.test(email);
|
|
1837
|
+
}
|
|
1838
|
+
function slugify(str) {
|
|
1839
|
+
return str.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1840
|
+
}
|
|
1841
|
+
function truncate(str, maxLength, suffix = "...") {
|
|
1842
|
+
if (str.length <= maxLength) return str;
|
|
1843
|
+
return str.slice(0, maxLength - suffix.length) + suffix;
|
|
1844
|
+
}
|
|
1845
|
+
function debounce(fn, wait) {
|
|
1846
|
+
let timeoutId = null;
|
|
1847
|
+
return (...args) => {
|
|
1848
|
+
if (timeoutId) {
|
|
1849
|
+
clearTimeout(timeoutId);
|
|
1850
|
+
}
|
|
1851
|
+
timeoutId = setTimeout(() => {
|
|
1852
|
+
fn(...args);
|
|
1853
|
+
timeoutId = null;
|
|
1854
|
+
}, wait);
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
function throttle(fn, wait) {
|
|
1858
|
+
let lastTime = 0;
|
|
1859
|
+
return (...args) => {
|
|
1860
|
+
const now = Date.now();
|
|
1861
|
+
if (now - lastTime >= wait) {
|
|
1862
|
+
lastTime = now;
|
|
1863
|
+
fn(...args);
|
|
1864
|
+
}
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
function createDeferred() {
|
|
1868
|
+
let resolve;
|
|
1869
|
+
let reject;
|
|
1870
|
+
const promise = new Promise((res, rej) => {
|
|
1871
|
+
resolve = res;
|
|
1872
|
+
reject = rej;
|
|
1873
|
+
});
|
|
1874
|
+
return { promise, resolve, reject };
|
|
1875
|
+
}
|
|
1876
|
+
async function pLimit(tasks, concurrency) {
|
|
1877
|
+
const results = [];
|
|
1878
|
+
const executing = [];
|
|
1879
|
+
for (const [index, task] of tasks.entries()) {
|
|
1880
|
+
const p = Promise.resolve().then(async () => {
|
|
1881
|
+
results[index] = await task();
|
|
1882
|
+
});
|
|
1883
|
+
executing.push(p);
|
|
1884
|
+
if (executing.length >= concurrency) {
|
|
1885
|
+
await Promise.race(executing);
|
|
1886
|
+
executing.splice(
|
|
1887
|
+
executing.findIndex((e) => e === p),
|
|
1888
|
+
1
|
|
1889
|
+
);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
await Promise.all(executing);
|
|
1893
|
+
return results;
|
|
1894
|
+
}
|
|
1895
|
+
export {
|
|
1896
|
+
AccountLockedError,
|
|
1897
|
+
AuthError,
|
|
1898
|
+
AxiomTransport,
|
|
1899
|
+
ConflictError,
|
|
1900
|
+
ConsoleTransport,
|
|
1901
|
+
Decimal,
|
|
1902
|
+
DecimalUtils,
|
|
1903
|
+
DuplicateError,
|
|
1904
|
+
ErrorCodes,
|
|
1905
|
+
ForbiddenError,
|
|
1906
|
+
InvalidCredentialsError,
|
|
1907
|
+
LogLevel,
|
|
1908
|
+
Logger,
|
|
1909
|
+
LogtapeTransport,
|
|
1910
|
+
MembershipError,
|
|
1911
|
+
MembershipExpiredError,
|
|
1912
|
+
MembershipNotFoundError,
|
|
1913
|
+
NotFoundError,
|
|
1914
|
+
ParsError,
|
|
1915
|
+
RateLimitError,
|
|
1916
|
+
SentryTransport,
|
|
1917
|
+
SessionExpiredError,
|
|
1918
|
+
TenantError,
|
|
1919
|
+
TenantNotFoundError,
|
|
1920
|
+
TenantSuspendedError,
|
|
1921
|
+
TwoFactorRequiredError,
|
|
1922
|
+
UnauthorizedError,
|
|
1923
|
+
ValidationError,
|
|
1924
|
+
clearEdgeEnv,
|
|
1925
|
+
constantTimeEquals,
|
|
1926
|
+
createAxiomTransport,
|
|
1927
|
+
createDeferred,
|
|
1928
|
+
createEnvConfig,
|
|
1929
|
+
createLogger,
|
|
1930
|
+
createLogtapeTransport,
|
|
1931
|
+
createRequestLogger,
|
|
1932
|
+
createSentryTransport,
|
|
1933
|
+
debounce,
|
|
1934
|
+
decimal,
|
|
1935
|
+
deepClone,
|
|
1936
|
+
deepMerge,
|
|
1937
|
+
detectRuntime,
|
|
1938
|
+
err,
|
|
1939
|
+
generateId,
|
|
1940
|
+
generateRandomString,
|
|
1941
|
+
getEnv,
|
|
1942
|
+
getEnvArray,
|
|
1943
|
+
getEnvBoolean,
|
|
1944
|
+
getEnvFloat,
|
|
1945
|
+
getEnvJson,
|
|
1946
|
+
getEnvMode,
|
|
1947
|
+
getEnvNumber,
|
|
1948
|
+
getErrorCode,
|
|
1949
|
+
getErrorCodesByCategory,
|
|
1950
|
+
getRuntimeVersion,
|
|
1951
|
+
getStatusForCode,
|
|
1952
|
+
isBrowser,
|
|
1953
|
+
isBun,
|
|
1954
|
+
isCloudflare,
|
|
1955
|
+
isDeno,
|
|
1956
|
+
isDevelopment,
|
|
1957
|
+
isEdge,
|
|
1958
|
+
isEmpty,
|
|
1959
|
+
isNil,
|
|
1960
|
+
isNode,
|
|
1961
|
+
isPlainObject,
|
|
1962
|
+
isProduction,
|
|
1963
|
+
isRetryableError,
|
|
1964
|
+
isServer,
|
|
1965
|
+
isTest,
|
|
1966
|
+
isValidEmail,
|
|
1967
|
+
logError,
|
|
1968
|
+
logger,
|
|
1969
|
+
measureTime,
|
|
1970
|
+
normalizeEmail,
|
|
1971
|
+
ok,
|
|
1972
|
+
omit,
|
|
1973
|
+
pLimit,
|
|
1974
|
+
pick,
|
|
1975
|
+
requireEnv,
|
|
1976
|
+
retry,
|
|
1977
|
+
runtime,
|
|
1978
|
+
runtimeInfo,
|
|
1979
|
+
setEdgeEnv,
|
|
1980
|
+
sha256,
|
|
1981
|
+
sha256Bytes,
|
|
1982
|
+
sleep,
|
|
1983
|
+
slugify,
|
|
1984
|
+
throttle,
|
|
1985
|
+
truncate
|
|
1986
|
+
};
|
|
1987
|
+
//# sourceMappingURL=index.js.map
|