@junando/worker 0.8.3 → 0.10.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.
Files changed (2) hide show
  1. package/dist/handler.cjs +172 -6
  2. package/package.json +2 -2
package/dist/handler.cjs CHANGED
@@ -65488,6 +65488,144 @@ async function loadConfig3() {
65488
65488
 
65489
65489
  // src/handler.ts
65490
65490
  var import_ioredis = __toESM(require_built3());
65491
+
65492
+ // src/adapters/csv-input.adapter.ts
65493
+ var DEFAULT_CSV_COLUMN_MAPPING = {
65494
+ serviceCol: 0,
65495
+ messageCol: 1,
65496
+ severityCol: 2,
65497
+ timestampCol: 3
65498
+ };
65499
+ var SEVERITY_TO_ALERT_TYPE = {
65500
+ error: "http_500" /* Error */,
65501
+ critical: "http_500" /* Error */,
65502
+ high: "http_500" /* Error */,
65503
+ warning: "latency_spike" /* Warning */,
65504
+ warn: "latency_spike" /* Warning */,
65505
+ latency: "latency_spike" /* Warning */,
65506
+ success: "recovery" /* Success */,
65507
+ recovery: "recovery" /* Success */,
65508
+ resolved: "recovery" /* Success */,
65509
+ info: "recovery" /* Success */
65510
+ };
65511
+ external_exports.object({
65512
+ correlationId: external_exports.string().uuid(),
65513
+ alerts: external_exports.array(NormalizedAlertSchema)
65514
+ });
65515
+ function isCsvBody(body) {
65516
+ const trimmed = body.trim();
65517
+ if (!trimmed) return false;
65518
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return false;
65519
+ return trimmed.includes(",") && (trimmed.includes("\n") || trimmed.split(",").length >= 3);
65520
+ }
65521
+ function parseCsvBody(body, mapping = DEFAULT_CSV_COLUMN_MAPPING) {
65522
+ const lines = body.trim().split("\n");
65523
+ if (lines.length < 2) return null;
65524
+ const headerLine = lines[0];
65525
+ const headers = parseRow(headerLine);
65526
+ const alerts = [];
65527
+ for (let i8 = 1; i8 < lines.length; i8++) {
65528
+ const row = lines[i8];
65529
+ const values = parseRow(row);
65530
+ if (values.length === 0) continue;
65531
+ try {
65532
+ const service = getColumnValue(headers, values, mapping.serviceCol);
65533
+ const message = getColumnValue(headers, values, mapping.messageCol);
65534
+ const severity = getColumnValue(headers, values, mapping.severityCol);
65535
+ const timestamp = getColumnValue(headers, values, mapping.timestampCol);
65536
+ const fingerprint = mapping.fingerprintCol != null ? getColumnValue(headers, values, mapping.fingerprintCol) || generateFingerprint(service, message) : generateFingerprint(service, message);
65537
+ const endpoint = mapping.endpointCol != null ? getColumnValue(headers, values, mapping.endpointCol) || "/" : "/";
65538
+ if (!service || !message || !severity || !timestamp) continue;
65539
+ const alertType = mapSeverityToAlertType(severity);
65540
+ if (!alertType) continue;
65541
+ const alert = {
65542
+ fingerprint,
65543
+ alertName: message.slice(0, 200),
65544
+ // truncate long names
65545
+ status: "firing",
65546
+ serviceName: service.trim(),
65547
+ alertType,
65548
+ endpointPath: endpoint.trim() || "/",
65549
+ startsAt: normalizeTimestamp(timestamp),
65550
+ labels: parseExtraLabels(mapping.extraLabels),
65551
+ annotations: { source: "csv-adapter" }
65552
+ };
65553
+ const result = NormalizedAlertSchema.safeParse(alert);
65554
+ if (result.success) {
65555
+ alerts.push(result.data);
65556
+ }
65557
+ } catch {
65558
+ continue;
65559
+ }
65560
+ }
65561
+ if (alerts.length === 0) return null;
65562
+ return {
65563
+ correlationId: crypto.randomUUID(),
65564
+ alerts
65565
+ };
65566
+ }
65567
+ function parseRow(line) {
65568
+ const result = [];
65569
+ let current = "";
65570
+ let inQuotes = false;
65571
+ for (let i8 = 0; i8 < line.length; i8++) {
65572
+ const char = line[i8];
65573
+ if (char === '"') {
65574
+ inQuotes = !inQuotes;
65575
+ } else if (char === "," && !inQuotes) {
65576
+ result.push(current.trim());
65577
+ current = "";
65578
+ } else {
65579
+ current += char;
65580
+ }
65581
+ }
65582
+ result.push(current.trim());
65583
+ return result;
65584
+ }
65585
+ function getColumnValue(headers, values, col) {
65586
+ if (typeof col === "number") {
65587
+ return values[col] ?? "";
65588
+ }
65589
+ const idx = headers.indexOf(col);
65590
+ return idx >= 0 ? values[idx] ?? "" : "";
65591
+ }
65592
+ function mapSeverityToAlertType(severity) {
65593
+ const lower = severity.toLowerCase().trim();
65594
+ return SEVERITY_TO_ALERT_TYPE[lower] ?? null;
65595
+ }
65596
+ function normalizeTimestamp(value) {
65597
+ const trimmed = value.trim();
65598
+ if (/\d{4}-\d{2}-\d{2}T/.test(trimmed)) return trimmed;
65599
+ if (/^\d{10}$/.test(trimmed)) {
65600
+ return new Date(Number(trimmed) * 1e3).toISOString();
65601
+ }
65602
+ if (/^\d{13}$/.test(trimmed)) {
65603
+ return new Date(Number(trimmed)).toISOString();
65604
+ }
65605
+ const parsed = Date.parse(trimmed);
65606
+ if (!isNaN(parsed)) return new Date(parsed).toISOString();
65607
+ return (/* @__PURE__ */ new Date()).toISOString();
65608
+ }
65609
+ function parseExtraLabels(extra) {
65610
+ if (!extra) return {};
65611
+ const labels = {};
65612
+ for (const pair of extra.split(",")) {
65613
+ const [k8, v2] = pair.split("=").map((s2) => s2.trim());
65614
+ if (k8) labels[k8] = v2 ?? "";
65615
+ }
65616
+ return labels;
65617
+ }
65618
+ function generateFingerprint(service, message) {
65619
+ const raw = `${service}:${message}`;
65620
+ let hash = 0;
65621
+ for (let i8 = 0; i8 < raw.length; i8++) {
65622
+ const char = raw.charCodeAt(i8);
65623
+ hash = char * 31 + hash >>> 0;
65624
+ }
65625
+ return `csv-${hash.toString(16).padStart(8, "0")}`;
65626
+ }
65627
+
65628
+ // src/handler.ts
65491
65629
  var SQSMessageSchema = external_exports.object({
65492
65630
  correlationId: external_exports.string().uuid(),
65493
65631
  alerts: external_exports.array(NormalizedAlertSchema)
@@ -65529,6 +65667,12 @@ var handler = async (event) => {
65529
65667
  await flushLoki();
65530
65668
  }
65531
65669
  };
65670
+ function parseIntEnv(key) {
65671
+ const val = process.env[key];
65672
+ if (!val) return void 0;
65673
+ const parsed = Number(val);
65674
+ return isNaN(parsed) ? void 0 : parsed;
65675
+ }
65532
65676
  async function _handler(event) {
65533
65677
  let useCaseInstance;
65534
65678
  try {
@@ -65540,13 +65684,35 @@ async function _handler(event) {
65540
65684
  for (const record of event.Records) {
65541
65685
  let parsed;
65542
65686
  try {
65543
- const raw = JSON.parse(record.body);
65544
- const result = SQSMessageSchema.safeParse(raw);
65545
- if (!result.success) {
65546
- logger7.error({ err: result.error.issues }, "Invalid SQS message body");
65547
- continue;
65687
+ if (isCsvBody(record.body)) {
65688
+ const fpCol = parseIntEnv("CSV_FINGERPRINT_COL");
65689
+ const epCol = parseIntEnv("CSV_ENDPOINT_COL");
65690
+ const extraLabels = process.env["CSV_EXTRA_LABELS"];
65691
+ const mapping = {
65692
+ serviceCol: Number(process.env["CSV_SERVICE_COL"] ?? 0),
65693
+ messageCol: Number(process.env["CSV_MESSAGE_COL"] ?? 1),
65694
+ severityCol: Number(process.env["CSV_SEVERITY_COL"] ?? 2),
65695
+ timestampCol: Number(process.env["CSV_TIMESTAMP_COL"] ?? 3),
65696
+ ...fpCol !== void 0 && { fingerprintCol: fpCol },
65697
+ ...epCol !== void 0 && { endpointCol: epCol },
65698
+ ...extraLabels !== void 0 && { extraLabels }
65699
+ };
65700
+ const csvResult = parseCsvBody(record.body, mapping);
65701
+ if (!csvResult) {
65702
+ logger7.error({ record: record.messageId }, "CSV parse returned no valid alerts");
65703
+ continue;
65704
+ }
65705
+ parsed = csvResult;
65706
+ logger7.info({ alertCount: parsed.alerts.length }, "Parsed CSV SQS message");
65707
+ } else {
65708
+ const raw = JSON.parse(record.body);
65709
+ const result = SQSMessageSchema.safeParse(raw);
65710
+ if (!result.success) {
65711
+ logger7.error({ err: result.error.issues }, "Invalid SQS message body");
65712
+ continue;
65713
+ }
65714
+ parsed = result.data;
65548
65715
  }
65549
- parsed = result.data;
65550
65716
  } catch (err) {
65551
65717
  logger7.error({ err }, "Failed to parse SQS message body");
65552
65718
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junando/worker",
3
- "version": "0.8.3",
3
+ "version": "0.10.0",
4
4
  "description": "AWS Lambda SQS worker — processes alert events and dispatches notifications for Junando",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -22,7 +22,7 @@
22
22
  "@aws-sdk/client-ssm": "^3.600.0",
23
23
  "ioredis": "^5.4.1",
24
24
  "zod": "^3.23.0",
25
- "@junando/core": "0.8.3"
25
+ "@junando/core": "0.10.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/aws-lambda": "^8.10.145",