@adpal/shared-lib 1.0.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/dist/index.js ADDED
@@ -0,0 +1,577 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+
5
+ // src/errors.ts
6
+ var AppError = class extends Error {
7
+ /** Машиночитаемый код ошибки */
8
+ code;
9
+ /** Дополнительный контекст для отладки */
10
+ context;
11
+ /** Можно ли повторить операцию */
12
+ retryable;
13
+ /** Временная метка создания ошибки */
14
+ timestamp;
15
+ constructor(message, options = {}) {
16
+ super(message, { cause: options.cause });
17
+ this.name = "AppError";
18
+ this.code = options.code || "APP_ERROR";
19
+ this.context = options.context;
20
+ this.retryable = options.retryable ?? false;
21
+ this.timestamp = /* @__PURE__ */ new Date();
22
+ }
23
+ /**
24
+ * Преобразовать в JSON для логирования
25
+ */
26
+ toJSON() {
27
+ return {
28
+ name: this.name,
29
+ message: this.message,
30
+ code: this.code,
31
+ context: this.context,
32
+ retryable: this.retryable,
33
+ timestamp: this.timestamp.toISOString(),
34
+ stack: this.stack
35
+ };
36
+ }
37
+ };
38
+ var ApiError = class extends AppError {
39
+ /** HTTP статус код */
40
+ statusCode;
41
+ /** Имя сервиса */
42
+ service;
43
+ constructor(message, options) {
44
+ super(message, {
45
+ code: `${options.service.toUpperCase()}_API_ERROR`,
46
+ context: { ...options.context, statusCode: options.statusCode },
47
+ retryable: options.retryable ?? isRetryableStatus(options.statusCode),
48
+ cause: options.cause
49
+ });
50
+ this.name = "ApiError";
51
+ this.statusCode = options.statusCode;
52
+ this.service = options.service;
53
+ }
54
+ };
55
+ var VoyageApiError = class extends ApiError {
56
+ constructor(message, statusCode, context) {
57
+ super(message, {
58
+ statusCode,
59
+ service: "voyage",
60
+ context
61
+ });
62
+ this.name = "VoyageApiError";
63
+ }
64
+ };
65
+ var AnthropicApiError = class extends ApiError {
66
+ constructor(message, statusCode, context) {
67
+ super(message, {
68
+ statusCode,
69
+ service: "anthropic",
70
+ context
71
+ });
72
+ this.name = "AnthropicApiError";
73
+ }
74
+ };
75
+ var TurbopufferApiError = class extends ApiError {
76
+ constructor(message, statusCode, context) {
77
+ super(message, {
78
+ statusCode,
79
+ service: "turbopuffer",
80
+ context
81
+ });
82
+ this.name = "TurbopufferApiError";
83
+ }
84
+ };
85
+ var ValidationError = class extends AppError {
86
+ /** Поле, в котором обнаружена ошибка */
87
+ field;
88
+ /** Ожидаемое значение или тип */
89
+ expected;
90
+ /** Полученное значение */
91
+ received;
92
+ constructor(message, options = {}) {
93
+ super(message, {
94
+ code: "VALIDATION_ERROR",
95
+ context: {
96
+ ...options.context,
97
+ field: options.field,
98
+ expected: options.expected,
99
+ received: options.received
100
+ },
101
+ retryable: false
102
+ });
103
+ this.name = "ValidationError";
104
+ this.field = options.field;
105
+ this.expected = options.expected;
106
+ this.received = options.received;
107
+ }
108
+ };
109
+ var ConfigError = class extends AppError {
110
+ /** Имя отсутствующей переменной */
111
+ variableName;
112
+ constructor(variableName) {
113
+ super(`\u041E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u0430\u044F \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F: ${variableName}`, {
114
+ code: "CONFIG_ERROR",
115
+ context: { variableName },
116
+ retryable: false
117
+ });
118
+ this.name = "ConfigError";
119
+ this.variableName = variableName;
120
+ }
121
+ };
122
+ function isRetryableStatus(statusCode) {
123
+ return [408, 429, 500, 502, 503, 504].includes(statusCode);
124
+ }
125
+ function isAppError(error) {
126
+ return error instanceof AppError;
127
+ }
128
+ function wrapError(error, defaultMessage = "\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043E\u0448\u0438\u0431\u043A\u0430") {
129
+ if (error instanceof AppError) {
130
+ return error;
131
+ }
132
+ if (error instanceof Error) {
133
+ return new AppError(error.message || defaultMessage, {
134
+ cause: error,
135
+ context: { originalName: error.name }
136
+ });
137
+ }
138
+ return new AppError(defaultMessage, {
139
+ context: { originalError: error }
140
+ });
141
+ }
142
+
143
+ // src/logger.ts
144
+ var LOG_LEVEL_PRIORITY = {
145
+ debug: 0,
146
+ info: 1,
147
+ warn: 2,
148
+ error: 3
149
+ };
150
+ var COLORS = {
151
+ debug: "\x1B[36m",
152
+ // Cyan
153
+ info: "\x1B[32m",
154
+ // Green
155
+ warn: "\x1B[33m",
156
+ // Yellow
157
+ error: "\x1B[31m"
158
+ // Red
159
+ };
160
+ var RESET = "\x1B[0m";
161
+ function getCurrentLogLevel() {
162
+ const envLevel = process.env.LOG_LEVEL?.toLowerCase();
163
+ if (envLevel && envLevel in LOG_LEVEL_PRIORITY) {
164
+ return envLevel;
165
+ }
166
+ return process.env.NODE_ENV === "production" ? "info" : "debug";
167
+ }
168
+ function isDev() {
169
+ return process.env.NODE_ENV !== "production";
170
+ }
171
+ function formatLogEntry(entry) {
172
+ if (isDev()) {
173
+ const color = COLORS[entry.level];
174
+ const levelStr = entry.level.toUpperCase().padEnd(5);
175
+ const time = new Date(entry.timestamp).toLocaleTimeString();
176
+ let output = `${color}[${levelStr}]${RESET} ${time} ${entry.message}`;
177
+ if (entry.meta && Object.keys(entry.meta).length > 0) {
178
+ output += ` ${JSON.stringify(entry.meta)}`;
179
+ }
180
+ if (entry.error) {
181
+ output += `
182
+ Error: ${entry.error.name}: ${entry.error.message}`;
183
+ if (entry.error.stack) {
184
+ output += `
185
+ ${entry.error.stack.split("\n").slice(1).join("\n ")}`;
186
+ }
187
+ }
188
+ return output;
189
+ }
190
+ return JSON.stringify(entry);
191
+ }
192
+ function serializeError(error) {
193
+ return {
194
+ name: error.name,
195
+ message: error.message,
196
+ stack: error.stack
197
+ };
198
+ }
199
+ var Logger = class {
200
+ module;
201
+ minLevel;
202
+ constructor(module) {
203
+ this.module = module;
204
+ this.minLevel = getCurrentLogLevel();
205
+ }
206
+ /**
207
+ * Проверить, включён ли данный уровень
208
+ */
209
+ isEnabled(level) {
210
+ return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.minLevel];
211
+ }
212
+ /**
213
+ * Записать лог
214
+ */
215
+ log(level, message, meta, error) {
216
+ if (!this.isEnabled(level)) return;
217
+ const entry = {
218
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
219
+ level,
220
+ message,
221
+ meta: { module: this.module, ...meta }
222
+ };
223
+ if (error) {
224
+ entry.error = serializeError(error);
225
+ }
226
+ const formatted = formatLogEntry(entry);
227
+ switch (level) {
228
+ case "error":
229
+ console.error(formatted);
230
+ break;
231
+ case "warn":
232
+ console.warn(formatted);
233
+ break;
234
+ default:
235
+ console.log(formatted);
236
+ }
237
+ }
238
+ /**
239
+ * Debug уровень — детальная информация для отладки
240
+ */
241
+ debug(message, meta) {
242
+ this.log("debug", message, meta);
243
+ }
244
+ /**
245
+ * Info уровень — информационные сообщения
246
+ */
247
+ info(message, meta) {
248
+ this.log("info", message, meta);
249
+ }
250
+ /**
251
+ * Warn уровень — предупреждения
252
+ */
253
+ warn(message, meta) {
254
+ this.log("warn", message, meta);
255
+ }
256
+ /**
257
+ * Error уровень — ошибки
258
+ */
259
+ error(message, error, meta) {
260
+ const err = error instanceof Error ? error : void 0;
261
+ this.log("error", message, meta, err);
262
+ }
263
+ /**
264
+ * Создать child logger с дополнительным контекстом
265
+ */
266
+ child(context) {
267
+ return new ChildLogger(this, context);
268
+ }
269
+ };
270
+ var ChildLogger = class {
271
+ constructor(parent, context) {
272
+ this.parent = parent;
273
+ this.context = context;
274
+ }
275
+ debug(message, meta) {
276
+ this.parent.debug(message, { ...this.context, ...meta });
277
+ }
278
+ info(message, meta) {
279
+ this.parent.info(message, { ...this.context, ...meta });
280
+ }
281
+ warn(message, meta) {
282
+ this.parent.warn(message, { ...this.context, ...meta });
283
+ }
284
+ error(message, error, meta) {
285
+ this.parent.error(message, error, { ...this.context, ...meta });
286
+ }
287
+ };
288
+ var voyageLogger = new Logger("voyage");
289
+ var turbopufferLogger = new Logger("turbopuffer");
290
+ var anthropicLogger = new Logger("anthropic");
291
+ var apiLogger = new Logger("api");
292
+ var logger = new Logger("app");
293
+ function createTimer() {
294
+ const start = performance.now();
295
+ return () => Math.round(performance.now() - start);
296
+ }
297
+
298
+ // src/retry.ts
299
+ var DEFAULT_MAX_ATTEMPTS = 3;
300
+ var DEFAULT_BASE_DELAY_MS = 1e3;
301
+ var DEFAULT_MAX_DELAY_MS = 3e4;
302
+ var DEFAULT_JITTER = 0.1;
303
+ function sleep(ms) {
304
+ return new Promise((resolve) => setTimeout(resolve, ms));
305
+ }
306
+ function calculateDelay(attempt, baseDelayMs, maxDelayMs, jitter) {
307
+ const exponentialDelay = baseDelayMs * Math.pow(2, attempt - 1);
308
+ const cappedDelay = Math.min(exponentialDelay, maxDelayMs);
309
+ const jitterAmount = cappedDelay * jitter * Math.random();
310
+ return Math.round(cappedDelay + jitterAmount);
311
+ }
312
+ function defaultShouldRetry(error) {
313
+ if (isAppError(error) && error.retryable) {
314
+ return true;
315
+ }
316
+ if (error instanceof Error) {
317
+ const message = error.message.toLowerCase();
318
+ const networkErrors = [
319
+ "fetch failed",
320
+ "network error",
321
+ "timeout",
322
+ "econnreset",
323
+ "econnrefused",
324
+ "socket hang up"
325
+ ];
326
+ return networkErrors.some((e) => message.includes(e));
327
+ }
328
+ return false;
329
+ }
330
+ async function withRetry(fn, options = {}) {
331
+ const {
332
+ maxAttempts = DEFAULT_MAX_ATTEMPTS,
333
+ baseDelayMs = DEFAULT_BASE_DELAY_MS,
334
+ maxDelayMs = DEFAULT_MAX_DELAY_MS,
335
+ jitter = DEFAULT_JITTER,
336
+ shouldRetry = defaultShouldRetry,
337
+ onRetry,
338
+ logger: logger2
339
+ } = options;
340
+ const startTime = performance.now();
341
+ let lastError;
342
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
343
+ try {
344
+ const data = await fn();
345
+ return {
346
+ data,
347
+ attempts: attempt,
348
+ totalTimeMs: Math.round(performance.now() - startTime)
349
+ };
350
+ } catch (error) {
351
+ lastError = error;
352
+ if (attempt >= maxAttempts) {
353
+ logger2?.error(`\u0412\u0441\u0435 ${maxAttempts} \u043F\u043E\u043F\u044B\u0442\u043E\u043A \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u044B`, error);
354
+ break;
355
+ }
356
+ if (!shouldRetry(error, attempt)) {
357
+ logger2?.warn(`\u041E\u0448\u0438\u0431\u043A\u0430 \u043D\u0435 \u043F\u043E\u0434\u043B\u0435\u0436\u0438\u0442 \u043F\u043E\u0432\u0442\u043E\u0440\u0435\u043D\u0438\u044E \u043D\u0430 \u043F\u043E\u043F\u044B\u0442\u043A\u0435 ${attempt}`, {
358
+ errorMessage: error instanceof Error ? error.message : String(error)
359
+ });
360
+ break;
361
+ }
362
+ const delayMs = calculateDelay(attempt, baseDelayMs, maxDelayMs, jitter);
363
+ logger2?.info(`\u041F\u043E\u043F\u044B\u0442\u043A\u0430 ${attempt}/${maxAttempts} \u043D\u0435 \u0443\u0434\u0430\u043B\u0430\u0441\u044C, \u043F\u043E\u0432\u0442\u043E\u0440 \u0447\u0435\u0440\u0435\u0437 ${delayMs}\u043C\u0441`, {
364
+ errorMessage: error instanceof Error ? error.message : String(error),
365
+ delayMs
366
+ });
367
+ onRetry?.(error, attempt, delayMs);
368
+ await sleep(delayMs);
369
+ }
370
+ }
371
+ throw lastError;
372
+ }
373
+ async function retryApiCall(fn, options = {}) {
374
+ const result = await withRetry(fn, {
375
+ maxAttempts: options.maxAttempts ?? 3,
376
+ baseDelayMs: 1e3,
377
+ maxDelayMs: 1e4,
378
+ jitter: 0.2,
379
+ logger: options.logger,
380
+ onRetry: options.onRetry
381
+ });
382
+ return result.data;
383
+ }
384
+ async function retryCritical(fn, options = {}) {
385
+ const result = await withRetry(fn, {
386
+ maxAttempts: 5,
387
+ baseDelayMs: 500,
388
+ maxDelayMs: 3e4,
389
+ jitter: 0.3,
390
+ logger: options.logger,
391
+ onRetry: options.onRetry,
392
+ shouldRetry: () => true
393
+ // Повторяем всегда
394
+ });
395
+ return result.data;
396
+ }
397
+ var VoyageEmbeddingResponseSchema = zod.z.object({
398
+ /** Массив эмбеддингов */
399
+ data: zod.z.array(
400
+ zod.z.object({
401
+ /** Вектор эмбеддинга */
402
+ embedding: zod.z.array(zod.z.number()),
403
+ /** Индекс в исходном массиве */
404
+ index: zod.z.number().optional()
405
+ })
406
+ ),
407
+ /** Статистика использования */
408
+ usage: zod.z.object({
409
+ /** Общее количество токенов */
410
+ total_tokens: zod.z.number()
411
+ }),
412
+ /** Модель, использованная для генерации */
413
+ model: zod.z.string().optional()
414
+ });
415
+ var VoyageRerankResponseSchema = zod.z.object({
416
+ /** Массив результатов реранкинга */
417
+ data: zod.z.array(
418
+ zod.z.object({
419
+ /** Индекс документа в исходном массиве */
420
+ index: zod.z.number(),
421
+ /** Оценка релевантности (0-1) */
422
+ relevance_score: zod.z.number().min(0).max(1)
423
+ })
424
+ ),
425
+ /** Статистика использования */
426
+ usage: zod.z.object({
427
+ /** Общее количество токенов */
428
+ total_tokens: zod.z.number()
429
+ }),
430
+ /** Модель, использованная для реранкинга */
431
+ model: zod.z.string().optional()
432
+ });
433
+ var VectorAttributesSchema = zod.z.object({
434
+ /** ID записи в Supabase */
435
+ supabase_id: zod.z.number(),
436
+ /** Уникальный ID сообщения/документа */
437
+ message_id: zod.z.string(),
438
+ /** Тип источника */
439
+ source_type: zod.z.enum(["email", "pdf", "website"]),
440
+ /** Тип чанка */
441
+ chunk_type: zod.z.string(),
442
+ /** Email отправителя */
443
+ sender: zod.z.string().optional(),
444
+ /** Email получателя */
445
+ recipient: zod.z.string().optional(),
446
+ /** Дата источника */
447
+ source_date: zod.z.string().optional(),
448
+ /** ID цепочки писем */
449
+ thread_id: zod.z.string().optional(),
450
+ /** ID клиента */
451
+ client_id: zod.z.string().optional(),
452
+ /** URL источника */
453
+ source_url: zod.z.string().optional(),
454
+ /** Номер страницы */
455
+ page_number: zod.z.number().optional(),
456
+ /** Заголовок секции */
457
+ section_title: zod.z.string().optional(),
458
+ /** ID в content_sources */
459
+ content_source_id: zod.z.number().optional(),
460
+ /** Тема письма */
461
+ subject: zod.z.string().optional(),
462
+ /** Превью контента */
463
+ content_preview: zod.z.string().optional(),
464
+ /** Заголовок документа */
465
+ title: zod.z.string().optional(),
466
+ /** Полный контент */
467
+ content: zod.z.string().optional(),
468
+ /** Категория */
469
+ category: zod.z.string().optional(),
470
+ /** Тональность */
471
+ sentiment: zod.z.enum(["positive", "negative", "neutral"]).optional(),
472
+ /** Дата создания */
473
+ created_at: zod.z.string().optional()
474
+ });
475
+ var TurbopufferSearchResultSchema = zod.z.object({
476
+ /** ID вектора */
477
+ id: zod.z.string(),
478
+ /** Расстояние до запроса */
479
+ dist: zod.z.number(),
480
+ /** Атрибуты вектора */
481
+ attributes: VectorAttributesSchema.partial()
482
+ });
483
+ var ClaudeMessageSchema = zod.z.object({
484
+ /** ID сообщения */
485
+ id: zod.z.string(),
486
+ /** Тип объекта */
487
+ type: zod.z.literal("message"),
488
+ /** Роль */
489
+ role: zod.z.enum(["assistant", "user"]),
490
+ /** Контент */
491
+ content: zod.z.array(
492
+ zod.z.object({
493
+ type: zod.z.enum(["text", "tool_use"]),
494
+ text: zod.z.string().optional()
495
+ })
496
+ ),
497
+ /** Модель */
498
+ model: zod.z.string(),
499
+ /** Причина остановки */
500
+ stop_reason: zod.z.enum(["end_turn", "max_tokens", "stop_sequence", "tool_use"]).nullable(),
501
+ /** Использование токенов */
502
+ usage: zod.z.object({
503
+ input_tokens: zod.z.number(),
504
+ output_tokens: zod.z.number()
505
+ })
506
+ });
507
+ var ChatRequestSchema = zod.z.object({
508
+ /** Сообщение пользователя */
509
+ message: zod.z.string().min(1).max(1e4),
510
+ /** История сообщений */
511
+ history: zod.z.array(
512
+ zod.z.object({
513
+ role: zod.z.enum(["user", "assistant"]),
514
+ content: zod.z.string()
515
+ })
516
+ ).optional().default([]),
517
+ /** ID сессии */
518
+ sessionId: zod.z.string().optional(),
519
+ /** Режим (customer/internal) */
520
+ mode: zod.z.enum(["customer", "internal"]).optional().default("customer")
521
+ });
522
+ var SourceSchema = zod.z.object({
523
+ /** Тип источника */
524
+ type: zod.z.enum(["email", "pdf", "website"]),
525
+ /** Заголовок */
526
+ title: zod.z.string(),
527
+ /** URL или идентификатор */
528
+ url: zod.z.string().optional(),
529
+ /** Превью контента */
530
+ preview: zod.z.string().optional(),
531
+ /** Оценка релевантности */
532
+ relevance: zod.z.number().optional()
533
+ });
534
+ function safeParse(schema, data) {
535
+ const result = schema.safeParse(data);
536
+ return result.success ? result.data : null;
537
+ }
538
+ function parseOrThrow(schema, data, errorMessage = "Validation failed") {
539
+ const result = schema.safeParse(data);
540
+ if (!result.success) {
541
+ const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
542
+ throw new Error(`${errorMessage}: ${issues}`);
543
+ }
544
+ return result.data;
545
+ }
546
+
547
+ exports.AnthropicApiError = AnthropicApiError;
548
+ exports.ApiError = ApiError;
549
+ exports.AppError = AppError;
550
+ exports.ChatRequestSchema = ChatRequestSchema;
551
+ exports.ClaudeMessageSchema = ClaudeMessageSchema;
552
+ exports.ConfigError = ConfigError;
553
+ exports.Logger = Logger;
554
+ exports.SourceSchema = SourceSchema;
555
+ exports.TurbopufferApiError = TurbopufferApiError;
556
+ exports.TurbopufferSearchResultSchema = TurbopufferSearchResultSchema;
557
+ exports.ValidationError = ValidationError;
558
+ exports.VectorAttributesSchema = VectorAttributesSchema;
559
+ exports.VoyageApiError = VoyageApiError;
560
+ exports.VoyageEmbeddingResponseSchema = VoyageEmbeddingResponseSchema;
561
+ exports.VoyageRerankResponseSchema = VoyageRerankResponseSchema;
562
+ exports.anthropicLogger = anthropicLogger;
563
+ exports.apiLogger = apiLogger;
564
+ exports.createTimer = createTimer;
565
+ exports.isAppError = isAppError;
566
+ exports.logger = logger;
567
+ exports.parseOrThrow = parseOrThrow;
568
+ exports.retryApiCall = retryApiCall;
569
+ exports.retryCritical = retryCritical;
570
+ exports.safeParse = safeParse;
571
+ exports.sleep = sleep;
572
+ exports.turbopufferLogger = turbopufferLogger;
573
+ exports.voyageLogger = voyageLogger;
574
+ exports.withRetry = withRetry;
575
+ exports.wrapError = wrapError;
576
+ //# sourceMappingURL=index.js.map
577
+ //# sourceMappingURL=index.js.map