1.0.6 โ€ข Published 5 months ago

@glowlabs-org/events-sdk v1.0.6

Weekly downloads
-
License
MIT
Repository
github
Last release
5 months ago

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 IDZone Name
0All Zones
1Clean Grid Project
2Coming 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:

OptionTypeRequiredDescription
usernamestringYesRabbitMQ username
passwordstringYesRabbitMQ password
zoneIdnumberYesZone ID (use getZoneId("ZoneName"))
queueNamestringNo(Listener only) Pre-created queue name
exchangePrefixstringNoExchange prefix (default: glow.zone-)
hoststringNoRabbitMQ host/port (default: turntable.proxy.rlwy.net:50784)

๐Ÿ“ฆ Event Types & Versions

Currently supported event types and versions:

Event NameVersionPayload TypeDescription
audit.pushed"v1"AuditPushedV1PayloadEmitted when an audit is pushed
audit.slashed"v1"AuditSlashedV1PayloadEmitted when a farm is slashed
audit.pfees.paid"v1"AuditPfeesPaidV1PayloadPaid (by applicationId)
audit.pfees.paid"v2"AuditPfeesPaidV2PayloadPaid (by farmId)
application.created"v1"ApplicationCreatedV1PayloadEmitted when an application is created
audit.pushed"v2"AuditPushedV2PayloadEmitted 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

1.0.6

5 months ago

1.0.5

5 months ago

1.0.4

5 months ago

1.0.3

5 months ago

1.0.2

5 months ago

1.0.1

5 months ago

1.0.0

5 months ago

0.1.1

5 months ago

0.1.0

5 months ago