UNPKG

@maximai/maxim-js

Version:

Maxim AI JS SDK. Visit https://getmaxim.ai for more info.

362 lines 15.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.LogWriter = void 0; const fs_1 = __importDefault(require("fs")); const mime_types_1 = __importDefault(require("mime-types")); const os_1 = __importDefault(require("os")); const attachment_1 = require("../apis/attachment"); const logs_1 = require("../apis/logs"); const mutex_1 = require("../utils/mutex"); const queue_1 = require("../utils/queue"); const utils_1 = require("../utils/utils"); const attachment_2 = require("./components/attachment"); const types_1 = require("./components/types"); class LogWriter { constructor(config) { this.id = (0, utils_1.generateUniqueId)(); this.queue = new queue_1.Queue(); this.attachmentQueue = new queue_1.Queue(); this.mutex = mutex_1.Mutex.get(`maxim-logs-${this.id}`); this.flushInterval = null; this.logsDir = `${os_1.default.tmpdir()}/maxim-sdk/${this.id}/maxim-logs`; this.config = config; this.isDebug = config.isDebug || false; this._raiseExceptions = config.raiseExceptions; this.maxInMemoryLogs = config.maxInMemoryLogs || 100; this.cache = config.cache; this.logsAPIService = new logs_1.MaximLogsAPI(config.baseUrl, config.apiKey); this.attachmentAPIService = new attachment_1.MaximAttachmentAPI(config.baseUrl, config.apiKey); if (config.autoFlush) { this.flushInterval = setInterval(() => { this.flush(); this.flushAttachments(); }, config.flushInterval ? config.flushInterval * 1000 : 10000); this.flushInterval.unref(); } } get writerConfig() { return this.config; } get raiseExceptions() { return this._raiseExceptions; } get writerLogsAPIService() { return this.logsAPIService; } get writerCache() { return this.cache; } isOnAWSLambda() { return process.env["AWS_LAMBDA_FUNCTION_NAME"] !== undefined; } hasAccessToFilesystem() { try { fs_1.default.accessSync(os_1.default.tmpdir(), fs_1.default.constants.W_OK); return true; } catch (err) { return false; } } writeToFile(logs) { try { return new Promise((resolve, reject) => { if (!fs_1.default.existsSync(this.logsDir)) { fs_1.default.mkdirSync(this.logsDir, { recursive: true }); } const content = logs.map((l) => l.serialize()).join("\n"); const filename = `logs-${new Date().toISOString()}.log`; fs_1.default.writeFile(`${this.logsDir}/${filename}`, content, (err) => { if (err) { reject(err); return; } resolve(`${this.logsDir}/${filename}`); }); }); } catch (err) { if (this._raiseExceptions) { throw err; } else { console.error(`[Maxim-SDK] Error while writing to file: ${err instanceof Error ? err.message : err}`); return undefined; } } } async flushLogFiles() { if (!this.hasAccessToFilesystem() || !fs_1.default.existsSync(this.logsDir)) { return; } const files = fs_1.default.readdirSync(this.logsDir); await Promise.all(files.map(async (file) => { const logs = fs_1.default.readFileSync(`${this.logsDir}/${file}`, "utf-8"); try { await this.logsAPIService.pushLogs(this.config.repositoryId, logs); try { fs_1.default.rmSync(`${this.logsDir}/${file}`); } catch (ignored) { } } catch (err) { if (err && typeof err === "object" && "message" in err && typeof err.message === "string") console.error(`Error while pushing logs: ${err.message}`); } })); } async uploadFile(attachmentData, entity, entityId) { var _a; try { if (!attachmentData.path) { console.error("[MaximSDK] Path is not set for file attachment. Skipping upload"); return; } const mimeType = attachmentData.mimeType || mime_types_1.default.lookup(attachmentData.path) || "application/octet-stream"; const size = fs_1.default.statSync(attachmentData.path).size; const key = attachmentData.key; const data = fs_1.default.readFileSync(attachmentData.path); const resp = await this.attachmentAPIService.getUploadUrl(key, mimeType, size); const addAttachmentData = { ...attachmentData }; delete addAttachmentData.path; const addAttachmentLog = new types_1.CommitLog(entity, entityId, "add-attachment", addAttachmentData); this.queue.enqueue(addAttachmentLog); await this.attachmentAPIService.uploadToSignedUrl(resp.url, data, mimeType); if (this.isDebug) { console.log(`[MaximSDK] File uploaded to the Maxim API. URL: ${resp.url}, Mime type: ${mimeType}, Size: ${size}`); } } catch (err) { const currentRetry = "retry" in attachmentData && typeof attachmentData.retry === "number" ? ((_a = attachmentData.retry) !== null && _a !== void 0 ? _a : 0) : 0; if (currentRetry < 3) { attachmentData.retry = currentRetry + 1; const retryLog = new types_1.CommitLog(entity, entityId, "upload-attachment", attachmentData); this.attachmentQueue.enqueue(retryLog); } else { console.error(`[MaximSDK] Failed to upload file: ${err instanceof Error ? err.message : err}`); } } } async uploadFileData(attachmentData, entity, entityId) { var _a; try { if (!attachmentData.data) { console.error("[MaximSDK] Data is not set for file data attachment. Skipping upload"); return; } const mimeType = attachmentData.mimeType || "application/octet-stream"; const key = attachmentData.key; const size = attachmentData.data.length; const resp = await this.attachmentAPIService.getUploadUrl(key, mimeType, size); const addAttachmentData = { ...attachmentData }; delete addAttachmentData.data; const addAttachmentLog = new types_1.CommitLog(entity, entityId, "add-attachment", addAttachmentData); this.queue.enqueue(addAttachmentLog); await this.attachmentAPIService.uploadToSignedUrl(resp.url, attachmentData.data, mimeType); if (this.isDebug) { console.log(`[MaximSDK] File data uploaded to the Maxim API. URL: ${resp.url}, Mime type: ${mimeType}, Size: ${size}`); } } catch (err) { const currentRetry = "retry" in attachmentData && typeof attachmentData.retry === "number" ? ((_a = attachmentData.retry) !== null && _a !== void 0 ? _a : 0) : 0; if (currentRetry < 3) { attachmentData.retry = currentRetry + 1; const retryLog = new types_1.CommitLog(entity, entityId, "upload-attachment", attachmentData); this.attachmentQueue.enqueue(retryLog); } else { console.error(`[MaximSDK] Failed to upload file data: ${err instanceof Error ? err.message : err}`); } } } async uploadAttachment(attachment) { const entity = attachment.type; const entityId = attachment.id; const attachmentData = attachment.data; const populatedAttachment = (0, attachment_2.populateAttachmentFields)(attachmentData); const attachmentType = populatedAttachment.type; switch (attachmentType) { case "file": await this.uploadFile(populatedAttachment, entity, entityId); break; case "fileData": await this.uploadFileData(populatedAttachment, entity, entityId); break; case "url": const addAttachmentLog = new types_1.CommitLog(entity, entityId, "add-attachment", populatedAttachment); this.queue.enqueue(addAttachmentLog); break; default: const exhaustiveCheck = attachmentType; console.error(`[MaximSDK] Unknown attachment type: ${attachmentType}. Skipping upload.`); } } async flushAttachments() { const attachments = this.attachmentQueue.dequeueAll(); if (attachments.length === 0) { return; } await Promise.all(attachments.map(async (attachment) => { return this.uploadAttachment(attachment); })); } async flushLogs(logs) { try { await this.flushLogFiles(); if (this.isDebug) { console.log("[MaximSDK] Flushing new logs"); logs.map((l) => console.log(l.serialize())); } const MAX_SIZE = 5242880; const chunks = []; let currentChunk = ""; for (const log of logs) { const serialized = log.serialize() + "\n"; if (currentChunk.length + serialized.length > MAX_SIZE) { chunks.push(currentChunk); currentChunk = serialized; } else { currentChunk += serialized; } } if (currentChunk.length > 0) { chunks.push(currentChunk); } for (const chunk of chunks) { await this.logsAPIService.pushLogs(this.config.repositoryId, chunk); if (this.isDebug) console.log(`[MaximSDK] Flushed chunk of size ${chunk.length} bytes`); } return; } catch (err) { console.error("Error while pushing logs", err); if (this.isOnAWSLambda() || !this.hasAccessToFilesystem()) { this.queue.enqueueAll(logs); return; } await this.writeToFile(logs); } } getAttachmentKey(log) { if (log.action === "upload-attachment") { const repoId = this.config.repositoryId; const entity = log.type; const entityId = log.id; const attachmentData = log.data; const fileId = attachmentData.id; return `${repoId}/${entity}/${entityId}/files/original/${fileId}`; } return null; } commit(log) { try { if (this.isDebug) console.log("[MaximSDK] Committing log: ", log.serialize()); if (!/^[a-zA-Z0-9_-]+$/.test(log.id)) { if (this._raiseExceptions) { throw new Error(`Invalid ID: ${log.id}. ID must only contain alphanumeric characters, hyphens, and underscores. Event will not be logged.`); } return; } if (log.action === "upload-attachment") { if (!log.data) { console.error("[MaximSDK] Attachment data is not set for log. Skipping upload."); return; } const key = this.getAttachmentKey(log); if (key) { log.data.key = key; this.attachmentQueue.enqueue(log); } else { console.error(`[MaximSDK] Failed to generate attachment key due to invalid action: ${log.action}. Skipping upload.`); } } else { this.queue.enqueue(log); } if (this.queue.size + this.attachmentQueue.size > this.maxInMemoryLogs) { this.flush(); } } catch (err) { if (this._raiseExceptions) { throw err; } else { console.error(`[Maxim-SDK] Error while committing log: ${err instanceof Error ? err.message : err}`); } } } async flush() { try { let items = []; const MUTEX_TIMEOUT_MS = 30000; const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { reject(new Error(`Mutex acquisition timed out after ${MUTEX_TIMEOUT_MS}ms`)); }, MUTEX_TIMEOUT_MS); }); await Promise.race([ new Promise(async (resolve) => { await this.mutex.withLock(async () => { try { await this.flushAttachments(); items = this.queue.dequeueAll(); if (items.length === 0) { await this.flushLogFiles(); if (this.isDebug) console.log("[MaximSDK] No logs to flush"); resolve(); } if (this.isDebug) console.log("[MaximSDK] Flushing logs"); await this.flushLogs(items); } catch (err) { console.error("[MaximSDK] Couldn't flush logs", err); resolve(); } }); resolve(); }), timeoutPromise, ]); if (this.isDebug) console.log("[MaximSDK] Flush complete"); } catch (err) { if (this._raiseExceptions) { throw err; } else { console.error(`[Maxim-SDK] Error while flushing logs: ${err instanceof Error ? err.message : err}`); } } } async cleanup() { try { if (this.flushInterval) clearInterval(this.flushInterval); await this.flush(); this.logsAPIService.destroyAgents(); this.attachmentAPIService.destroyAgents(); } catch (err) { if (this._raiseExceptions) { throw err; } else { console.error(`[Maxim-SDK] Error while cleaning up: ${err instanceof Error ? err.message : err}`); } } } } exports.LogWriter = LogWriter; //# sourceMappingURL=writer.js.map