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
(withzoneName: "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
andzoneName: "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
andexpectedProduction_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
andexpectedProduction_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
andlng
are numbers (coordinates).estimatedCostOfPowerPerKWh
andestimatedKWhGeneratedPerYear
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 isturntable.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 thezoneId
, an error is thrown.zoneId: 0
andzoneName: "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 eventsroutingKey = "audit.pushed.v1"
: only audit.pushed v1 eventsroutingKey = "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:
- Create a new schema in
src/schemas/
. - Add the event type and version to
eventTypeRegistry
insrc/event-registry.ts
. - 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