Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

@glowlabs-org/events-sdk

glowlabs-org693MIT1.0.6TypeScript support: included

Typed event SDK for Glow, powered by RabbitMQ and Zod.

kafka, redpanda, events, sdk, zod, typescript, glow

readme

Glow Events SDK

A TypeScript-first SDK for consuming and emitting typed events on the Glow platform, powered by RabbitMQ (topic exchange). Provides runtime validation and type inference using Zod schemas.


🚀 Quick Start

1. Install

pnpm install @glowlabs-org/events-sdk

📚 Zones

The following zones are currently available:

Zone ID Zone Name
0 All Zones
1 Clean Grid Project
2 Coming Soon Zone
  • Use zoneId: 0 (with zoneName: "All Zones") to listen to all zones. Emitters must be constructed with a specific zoneId (not 0).
  • The SDK types and runtime validation now officially support zoneId: 0 and zoneName: "All Zones" everywhere for listeners, but not for emitters.
  • Use the getZoneId utility to get the correct zoneId for a given zone name:

    import { getZoneId } from "@glowlabs-org/events-sdk";
    const zoneId = getZoneId("CleanGridProject");

🛠️ Options for Listeners & Emitters

Both createGlowEventListener and createGlowEventEmitter accept the following options:

Option Type Required Description
username string Yes RabbitMQ username
password string Yes RabbitMQ password
zoneId number Yes Zone ID (use getZoneId("ZoneName"))
queueName string No (Listener only) Pre-created queue name
exchangePrefix string No Exchange prefix (default: glow.zone-)
host string No RabbitMQ host/port (default: turntable.proxy.rlwy.net:50784)

📦 Event Types & Versions

Currently supported event types and versions:

Event Name Version Payload Type Description
audit.pushed "v1" AuditPushedV1Payload Emitted when an audit is pushed
audit.slashed "v1" AuditSlashedV1Payload Emitted when a farm is slashed
audit.pfees.paid "v1" AuditPfeesPaidV1Payload Paid (by applicationId)
audit.pfees.paid "v2" AuditPfeesPaidV2Payload Paid (by farmId)
application.created "v1" ApplicationCreatedV1Payload Emitted when an application is created
audit.pushed "v2" AuditPushedV2Payload Emitted when an audit is pushed (farmId is bytes16)

Event Types Enum

The SDK provides a TypeScript constant for all supported event types to ensure type safety and avoid typos:

import { eventTypes, EventType } from "./src/event-types";

// Usage example:
const myEventType: EventType = eventTypes.auditPushed;
  • eventTypes is a readonly object containing all event type strings.
  • EventType is a TypeScript type representing any valid event type string.

Use these in your code to avoid hardcoding event names and to benefit from autocompletion and type checking.


📝 Event Payload Schemas

audit.pushed v1

export interface AuditPushedV1Payload {
  farmId: string; // UUID string
  protocolFeeUSDPrice_12Decimals: string; // uint256 (decimal) − 12 implied decimals
  expectedProduction_12Decimals: string; // uint256 (decimal) − 12 implied decimals
  txHash: string; // bytes32 hex string (0x...)
}

Validation:

  • farmId must be a valid UUID string (e.g., afbc56b6-0b16-4119-b144-025728067ba6).
  • protocolFeeUSDPrice_12Decimals and expectedProduction_12Decimals must be decimal strings representing unsigned big integers.
  • txHash must be a 32-byte hex string (e.g., 0x...).

audit.slashed v1

export interface AuditSlashedV1Payload {
  farmId: string; // bytes16 hex string (0x...)
  slasher: string; // Ethereum address (0x...)
  txHash: string; // bytes32 hex string (0x...)
}

Validation:

  • farmId must be a 16-byte hex string (e.g., 0x...).
  • slasher must be a valid Ethereum address (0x...40 hex chars).
  • txHash must be a 32-byte hex string (e.g., 0x...).

audit.pfees.paid v1 (by applicationId)

export interface AuditPfeesPaidV1Payload {
  applicationId: string; // UUID string
  payer: string; // Ethereum address (0x...)
  amount_12Decimals: string; // uint256 (decimal) − 12 implied decimals
  txHash: string; // bytes32 hex string (0x...)
}

Validation:

  • applicationId must be a valid UUID string (e.g., 3ed964b1-4f02-475a-9789-fb74b3466c70).
  • payer must be a valid Ethereum address (0x...40 hex chars).
  • amount_12Decimals must be a decimal string representing an unsigned big integer (12 implied decimals).
  • txHash must be a 32-byte hex string (e.g., 0x...).

audit.pfees.paid v2 (by farmId)

export interface AuditPfeesPaidV2Payload {
  farmId: string; // bytes16 hex string (0x...)
  payer: string; // Ethereum address (0x...)
  amount_12Decimals: string; // uint256 (decimal) − 12 implied decimals
  txHash: string; // bytes32 hex string (0x...)
}

Validation:

  • farmId must be a 16-byte hex string (e.g., 0x...).
  • payer must be a valid Ethereum address (0x...40 hex chars).
  • amount_12Decimals must be a decimal string representing an unsigned big integer (12 implied decimals).
  • txHash must be a 32-byte hex string (e.g., 0x...).

audit.pushed v2

export interface AuditPushedV2Payload {
  farmId: string; // bytes16 hex string (0x...)
  protocolFeeUSDPrice_12Decimals: string; // uint256 (decimal) − 12 implied decimals
  expectedProduction_12Decimals: string; // uint256 (decimal) − 12 implied decimals
  txHash: string; // bytes32 hex string (0x...)
}

Validation:

  • farmId must be a 16-byte hex string (e.g., 0x...).
  • protocolFeeUSDPrice_12Decimals and expectedProduction_12Decimals must be decimal strings representing unsigned big integers.
  • txHash must be a 32-byte hex string (e.g., 0x...).

application.created v1

export interface ApplicationCreatedV1Payload {
  gcaAddress: string; // Ethereum address (0x...)
  lat: number;
  lng: number;
  estimatedCostOfPowerPerKWh: number;
  estimatedKWhGeneratedPerYear: number;
  installerCompanyName: string;
}

Validation:

  • gcaAddress must be a valid Ethereum address (0x...40 hex chars).
  • lat and lng are numbers (coordinates).
  • estimatedCostOfPowerPerKWh and estimatedKWhGeneratedPerYear are numbers.
  • installerCompanyName is a string.

✨ Usage Example

Listen to Specific Event Types/Versions

import { createGlowEventListener, getZoneId } from "@glowlabs-org/events-sdk";

const listener = createGlowEventListener({
  username: "listener",
  password: "your-password-here",
  zoneId: getZoneId("CleanGridProject"),
  queueName: "my.precreated.queue",
  host: "my.rabbitmq.host:5672", // Optional: override the default host
});

listener.onEvent("audit.pushed", "v1", (event) => {
  // event: GlowEvent<AuditPushedV1Payload>
  console.log(
    "Received audit.pushed v1:",
    event.payload.farmId,
    event.zoneId,
    event.zoneName
  );
});

listener.onEvent("audit.slashed", "v1", (event) => {
  // event: GlowEvent<AuditSlashedV1Payload>
  console.log(
    "Received audit.slashed v1:",
    event.payload.farmId,
    event.payload.slasher
  );
});

listener.onEvent("audit.pfees.paid", "v1", (event) => {
  // event: GlowEvent<AuditPfeesPaidV1Payload>
  console.log(
    "Received audit.pfees.paid v1:",
    event.payload.applicationId,
    event.payload.payer,
    event.payload.amount_12Decimals
  );
});

listener.onEvent("audit.pfees.paid", "v2", (event) => {
  // event: GlowEvent<AuditPfeesPaidV2Payload>
  console.log(
    "Received audit.pfees.paid v2:",
    event.payload.farmId,
    event.payload.payer,
    event.payload.amount_12Decimals
  );
});

listener.onEvent("application.created", "v1", (event) => {
  // event: GlowEvent<ApplicationCreatedV1Payload>
  console.log(
    "Received application.created v1:",
    event.payload.gcaAddress,
    event.payload.lat,
    event.payload.lng,
    event.payload.estimatedCostOfPowerPerKWh,
    event.payload.estimatedKWhGeneratedPerYear,
    event.payload.installerCompanyName
  );
});

await listener.start();
// To stop listening:
// await listener.stop();

Emit Events (Admin Only)

import { createGlowEventEmitter, getZoneId } from "@glowlabs-org/events-sdk";

// You must construct the emitter with a specific zoneId (not 0)
const emitter = createGlowEventEmitter({
  username: "admin",
  password: "your-password-here",
  zoneId: getZoneId("CleanGridProject"), // must be a specific zone
  host: "my.rabbitmq.host:5672", // Optional: override the default host
});

await emitter.emit({
  eventType: "audit.pushed",
  schemaVersion: "v1",
  payload: {
    farmId: "afbc56b6-0b16-4119-b144-025728067ba6", // UUID string
    protocolFeeUSDPrice_12Decimals: "...",
    expectedProduction_12Decimals: "...",
    txHash: "0x...",
  },
});

await emitter.emit({
  eventType: "audit.slashed",
  schemaVersion: "v1",
  payload: {
    farmId: "0x...",
    slasher: "0x...",
    txHash: "0x...",
  },
});

await emitter.emit({
  eventType: "audit.pfees.paid",
  schemaVersion: "v1",
  payload: {
    applicationId: "3ed964b1-4f02-475a-9789-fb74b3466c70", // UUID string
    payer: "0x...",
    amount_12Decimals: "1000000000000",
    txHash: "0x...",
  },
});

await emitter.emit({
  eventType: "audit.pfees.paid",
  schemaVersion: "v2",
  payload: {
    farmId: "0x...",
    payer: "0x...",
    amount_12Decimals: "1000000000000",
    txHash: "0x...",
  },
});

await emitter.emit({
  eventType: "application.created",
  schemaVersion: "v1",
  payload: {
    gcaAddress: "0x...",
    lat: 45.5017,
    lng: -73.5673,
    estimatedCostOfPowerPerKWh: 0.12,
    estimatedKWhGeneratedPerYear: 10000,
    installerCompanyName: "SolarCo",
  },
});

await emitter.disconnect();

Note:

  • The emitter will automatically publish each event to both the global (zone 0) and the specific zone exchange.
  • You cannot construct an emitter for zoneId: 0, and you cannot specify zoneId per emit call.
  • schemaVersion is always a string (e.g., "v1", "v2").
  • You can override the RabbitMQ host using the host option. Default is turntable.proxy.rlwy.net:50784.

🌐 Listening to All Zones

You can listen to all zones at once by passing zoneId: 0 and zoneName: "All Zones" to the listener. Emitters must always use a specific zone.

Listen to All Zones

import { createGlowEventListener } from "@glowlabs-org/events-sdk";

const listener = createGlowEventListener({
  username: "listener",
  password: "your-password-here",
  zoneId: 0, // special value for all zones
  queueName: "my.precreated.queue",
});

listener.onEvent("audit.pushed", "v1", (event) => {
  console.log(
    "Received audit.pushed v1 from any zone:",
    event.payload.farmId,
    event.zoneId,
    event.zoneName
  );
});

// ... other event handlers ...

await listener.start();
// To stop listening:
// await listener.stop();

🧪 Validation & Error Handling

  • All events are validated at runtime using Zod schemas.
  • If you emit or process an event with a zoneName that does not match the zoneId, an error is thrown. zoneId: 0 and zoneName: "All Zones" are a valid pairing.
  • If you emit or process an event with a schemaVersion for which no schema exists (e.g., audit.pushed v2), an error is thrown.
  • If the payload does not match the schema, an error is thrown.

🔐 Permissions & Credentials

  • Listener credentials: Can only subscribe to events. Cannot emit events or create new queues.
  • Admin credentials: Can subscribe, emit events, and create/bind new queues and exchanges.

If you try to emit with listener credentials, the SDK will throw an error.


🛠️ Admin & Queue Management

The SDK exposes helpers for programmatically creating, binding, and deleting exchanges and queues (admin credentials required). Use these for pre-creating queues for listeners, bootstrapping environments, or advanced queue management.

createExchange(options)

Creates a topic exchange (default: exchangeType = "topic").

bindQueueToExchange(options)

Binds a queue to a topic exchange. You can specify a routingKey for fine-grained event filtering:

  • routingKey = "#" (default): all events
  • routingKey = "audit.pushed.v1": only audit.pushed v1 events
  • routingKey = "audit.pushed.*": all versions of audit.pushed

Example

import {
  createExchange,
  bindQueueToExchange,
  deleteExchange,
  deleteQueue,
} from "@glowlabs-org/events-sdk";

await createExchange({
  username: "admin",
  password: "your-password-here",
  exchange: "glow.zone-1.events",
});

await bindQueueToExchange({
  username: "admin",
  password: "your-password-here",
  exchange: "glow.zone-1.events",
  queue: "glow-listener-queue",
  routingKey: "audit.pushed.v1", // only audit.pushed v1 events
});

🔒 Strict Read-Only Listeners

If your listener credentials only have read permission (no configure), you must consume from a pre-created queue. This is the most secure pattern for production.

1. Admin: Pre-create and bind the queue

import { bindQueueToExchange } from "@glowlabs-org/events-sdk";

await bindQueueToExchange({
  username: "admin",
  password: "your-admin-password",
  exchange: "glow.zone-1.events",
  queue: "my.precreated.queue",
  routingKey: "audit.pushed.v1", // only audit.pushed v1 events
});

2. Listener: Consume from the pre-created queue

import { createGlowEventListener } from "@glowlabs-org/events-sdk";

const listener = createGlowEventListener({
  username: "listener",
  password: "your-listener-password",
  zoneId: 1,
  queueName: "my.precreated.queue",
});
  • The listener will only consume from the pre-created queue and will not attempt to create or bind anything.
  • This pattern is required for production environments with strict access control.

🧩 Advanced: Multiple Listeners/Emitters

You can create multiple listeners or emitters in the same process, each with its own configuration (e.g., for different credentials, exchanges, or RabbitMQ URLs). This is useful for multi-tenant, multi-topic, or advanced scenarios. Every listener receives every event for the bound routing key(s).


🧪 Extending Event Types

To add new event types or versions:

  1. Create a new schema in src/schemas/.
  2. Add the event type and version to eventTypeRegistry in src/event-registry.ts.
  3. Update the base event type in src/base-event.ts if needed.

🏗️ Build & Publish to npm

To build and publish the SDK to npm:

make build
make publish
make clean
  • The first time, run npm login to authenticate with npm.
  • For scoped packages (like @glowlabs-org/events-sdk), the Makefile uses --access public for publishing.

License

MIT