npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ziky/error-event-contract

v1.0.0

Published

Versioned error event contract for Mixi backend services

Readme

@ziky/error-event-contract

Package định nghĩa contract dùng chung và runtime validation cho error event của hệ thống Mixi. Producer và consumer nên phụ thuộc vào package này thay vì tự sao chép interface hoặc ép kiểu payload nhận từ RabbitMQ.

Cài đặt

Package được phát hành công khai trên npm dưới scope @ziky.

npm install @ziky/error-event-contract

Ví dụ .npmrc khi package nằm trên npmjs:

@ziky:registry=https://registry.npmjs.org/
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

Không commit token thật vào repository.

Public API

import {
  ERROR_EVENT_EXCHANGE,
  ERROR_EVENT_ROUTING_PREFIX,
  ERROR_EVENT_SPEC_VERSION,
  ERROR_EVENT_TYPE,
  InvalidErrorEventError,
  errorRoutingKey,
  parseErrorEvent,
  type ErrorEventEnvironment,
  type ErrorEventV1,
} from "@ziky/error-event-contract";

| Export | Giá trị/vai trò | | ---------------------------- | ----------------------------------------------------- | | ERROR_EVENT_SPEC_VERSION | Phiên bản contract hiện tại: "1.0" | | ERROR_EVENT_TYPE | Loại event: "com.mixi.backend.error.v1" | | ERROR_EVENT_EXCHANGE | Topic exchange mặc định: "mixi.events" | | ERROR_EVENT_ROUTING_PREFIX | Routing prefix mặc định: "observability.error" | | ErrorEventV1 | TypeScript interface của event v1 | | parseErrorEvent() | Validate dữ liệu không tin cậy tại runtime | | errorRoutingKey() | Tạo routing key chuẩn từ tên service | | InvalidErrorEventError | Lỗi được ném khi event hoặc service name không hợp lệ |

Cấu trúc ErrorEventV1

Ví dụ event đầy đủ:

{
  "specVersion": "1.0",
  "eventType": "com.mixi.backend.error.v1",
  "eventId": "0198a530-bd10-7ac2-8d84-7fa021dce33c",
  "occurredAt": "2026-07-27T08:30:00.000Z",
  "service": {
    "name": "mixi-music-api",
    "version": "2.4.1",
    "environment": "production",
    "instanceId": "mixi-music-api-7f69b6f4d9-x2kq8"
  },
  "request": {
    "requestId": "req-01",
    "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
    "correlationId": "order-123",
    "method": "POST",
    "path": "/api/playlists",
    "statusCode": 500,
    "userId": "user-42",
    "tenantId": "tenant-01"
  },
  "error": {
    "name": "DatabaseError",
    "code": "DB_TIMEOUT",
    "message": "Query timed out",
    "stack": "DatabaseError: Query timed out ...",
    "fingerprint": "database-query-timeout"
  },
  "metadata": {
    "operation": "playlist.create",
    "retryCount": 2
  }
}

Trường bắt buộc

| Trường | Quy tắc | | --------------------- | ------------------------------------------ | | specVersion | Phải bằng "1.0" | | eventType | Phải bằng "com.mixi.backend.error.v1" | | eventId | Chuỗi khác rỗng, tối đa 100 ký tự | | occurredAt | Chuỗi ngày giờ parse được, tối đa 64 ký tự | | service.name | Chuỗi khác rỗng, tối đa 120 ký tự | | service.environment | development, staging hoặc production | | error.name | Chuỗi khác rỗng, tối đa 255 ký tự | | error.message | Chuỗi khác rỗng, tối đa 4.096 ký tự |

Trường tùy chọn và giới hạn

| Trường | Giới hạn | | ------------------------------ | ------------------------ | | service.version | 80 ký tự | | service.instanceId | 160 ký tự | | request.requestId, traceId | 160 ký tự | | request.correlationId | 160 ký tự | | request.userId, tenantId | 160 ký tự | | request.method | 20 ký tự | | request.path | 2.048 ký tự | | request.statusCode | Số nguyên từ 100 đến 599 | | error.code | 120 ký tự | | error.stack | 32.768 ký tự | | error.fingerprint | 128 ký tự | | metadata | Object |

parseErrorEvent() chỉ lấy các trường đã định nghĩa ở service, request và error. metadata được giữ dưới dạng object và không được package này tự redact; producer phải loại bỏ secret trước khi publish.

Producer: tạo và publish event

Khi tự xây producer, dùng các constant của package để tránh hard-code:

import { randomUUID } from "node:crypto";
import {
  ERROR_EVENT_EXCHANGE,
  ERROR_EVENT_SPEC_VERSION,
  ERROR_EVENT_TYPE,
  errorRoutingKey,
  type ErrorEventV1,
} from "@ziky/error-event-contract";

const event: ErrorEventV1 = {
  specVersion: ERROR_EVENT_SPEC_VERSION,
  eventType: ERROR_EVENT_TYPE,
  eventId: randomUUID(),
  occurredAt: new Date().toISOString(),
  service: {
    name: "mixi-music-api",
    version: process.env.APP_VERSION,
    environment: "production",
  },
  error: {
    name: "PlaylistError",
    code: "PLAYLIST_CREATE_FAILED",
    message: "Could not create playlist",
  },
  metadata: {
    operation: "playlist.create",
  },
};

channel.publish(
  ERROR_EVENT_EXCHANGE,
  errorRoutingKey(event.service.name),
  Buffer.from(JSON.stringify(event)),
  {
    contentType: "application/json",
    messageId: event.eventId,
    persistent: true,
    type: event.eventType,
  },
);

errorRoutingKey("Mixi-Music-API") trả về observability.error.mixi-music-api. Tên service sau khi trim và chuyển thành chữ thường phải có dạng kebab-case: chỉ gồm chữ, số và dấu -, không có hai dấu - liên tiếp.

Consumer: parse message an toàn

Payload từ queue là dữ liệu không tin cậy. Luôn JSON.parse rồi gọi parseErrorEvent() trước khi xử lý:

import {
  InvalidErrorEventError,
  parseErrorEvent,
} from "@ziky/error-event-contract";
import type { ConsumeMessage } from "amqplib";

async function handleMessage(message: ConsumeMessage): Promise<void> {
  try {
    const input: unknown = JSON.parse(message.content.toString("utf8"));
    const event = parseErrorEvent(input);

    await errorStore.save(event);
    channel.ack(message);
  } catch (error) {
    if (
      error instanceof InvalidErrorEventError ||
      error instanceof SyntaxError
    ) {
      // Payload hỏng không nên retry vô hạn. Chuyển sang DLQ theo policy của queue.
      channel.nack(message, false, false);
      return;
    }

    // Lỗi hạ tầng tạm thời có thể retry/requeue theo policy đã giới hạn.
    channel.nack(message, false, true);
  }
}

Ví dụ bind queue nhận lỗi của tất cả service:

await channel.assertExchange("mixi.events", "topic", { durable: true });
await channel.assertQueue("observability.error-store", {
  durable: true,
  arguments: {
    "x-dead-letter-exchange": "mixi.events.dlx",
  },
});
await channel.bindQueue(
  "observability.error-store",
  "mixi.events",
  "observability.error.*",
);

Consumer phải có chiến lược idempotency dựa trên eventId, retry hữu hạn và dead-letter queue. Không log toàn bộ payload nếu event có thể chứa dữ liệu cá nhân hoặc secret.

Xử lý lỗi validation

import {
  InvalidErrorEventError,
  parseErrorEvent,
} from "@ziky/error-event-contract";

try {
  const event = parseErrorEvent(input);
  // input đã được validate và event có kiểu ErrorEventV1
} catch (error) {
  if (error instanceof InvalidErrorEventError) {
    logger.warn({ reason: error.message }, "Rejected invalid error event");
  }
}

Các lỗi thường gặp:

  • specVersion is unsupported: producer/consumer đang dùng contract khác phiên bản.
  • eventType is unsupported: message không phải error event v1.
  • occurredAt must be an ISO date-time: timestamp không parse được.
  • service.environment is invalid: environment nằm ngoài ba giá trị hỗ trợ.
  • request.statusCode is invalid: status không phải số nguyên HTTP 100–599.
  • Service name is invalid: tên service không thể chuyển thành routing key chuẩn.

Versioning và tương thích

  • Thêm trường optional hoặc nới giới hạn có thể phát hành minor version.
  • Sửa lỗi không thay đổi contract phát hành patch version.
  • Xóa/đổi tên trường, đổi kiểu hoặc siết validation là breaking change và phải tăng major version.
  • Khi chuyển major version, producer và consumer nên hỗ trợ song song contract cũ/mới trong thời gian migration.

Kiểm tra local

Từ root monorepo:

npm ci
npm run build --workspace @ziky/error-event-contract
npm run typecheck --workspace @ziky/error-event-contract
npm test --workspace @ziky/error-event-contract
npm pack --workspace @ziky/error-event-contract --dry-run