Type-safe event-driven architecture for TypeScript.
const client = yield * makeClient;
yield *
client.billing.handle(({ payload }) =>
Effect.gen(function* () {
yield* Console.log("Billing received: ", payload);
}),
);
yield *
client.ordersCreated.publish({
orderId: "123",
});pnpm add signalgraph
pnpm add @signalgraph/runtime
pnpm add @signalgraph/adapter-*Substitute "*" for a valid adapter that is supported
SignalGraph exposes a config file signalgraph.config.ts that allows the user
to define options such as:
- out directory for the generated files
- which broker to use
- where the schema lives
Example config (taken from examples/adapter-memory):
import { defineConfig } from "signalgraph/config";
import { memory } from "@signalgraph/adapter-memory";
export default defineConfig({
schema: "./src/schema/schema.ts",
out: "./generated",
broker: memory(),
});All messages and consumers are read through a schema file which is linked through
the out key in the config file.
Each message contains a name and an Effect Schema that defines the payload of each message
Example message:
import { Schema } from "effect";
import { message } from "signalgraph";
export const OrderCreated = message({
name: "orders.created",
schema: Schema.Struct({
orderId: Schema.String,
}),
});Each consumer contains a name and the message that consumer will consume & handle
Example consumer (using the message defined above):
# schema.ts
import { Schema } from "effect";
import { consumer } from "signalgraph";
export const Billing = consumer({
name: "billing",
message: OrderCreated
});To generate the client from the config & your schema file, run this command in the ---------- of your project:
signalgraph generateThis will generate an index.ts file inside the directory you specified inside the
out field of the config file.
Each message and consumer is exposed as keys directly on the client. So using the message & consumer above, the client would expose:
client.billingclient.ordersCreated
To use the client after generating it within code just do:
const client = yield * makeClient;
yield *
client.billing.handle(({ payload }) =>
Effect.gen(function* () {
yield* Console.log("Billing received: ", payload);
yield* client.billingCompleted.publish(payload);
}),
);Then all defined messages and consumer will be available under client.*
For each message defined in the schema the generated client exposes a publish function. Using the message above the client would expose this function:
const client = yield * makeClient;
yield * client.ordersCreated.publish(payload);That takes a argument for the payload which is the same as the defined schema of the message.
For each consumer defined in the schema the generated client exposes a handle function. Using the consumer above the client would expose this function:
const client = yield * makeClient;
yield *
client.billing.handle(({ payload }) =>
Effect.gen(function* () {
yield* Console.log("Billing received: ", payload);
}),
);That takes a argument for the callback function that runs whenever that message
is published. It exposes a payload value which is typed to the consumer-specific message's schema
This example is a snippet of examples/adapter-memory
// schema.ts
import { Schema } from "effect";
import { consumer, message } from "signalgraph";
export const OrderCreated = message({
name: "orders.created",
schema: Schema.Struct({
orderId: Schema.String,
}),
});
export const BillingCompleted = message({
name: "billing.completed",
schema: Schema.Struct({
orderId: Schema.String,
}),
});
export const Billing = consumer({
name: "billing",
message: OrderCreated,
});
export const Analytics = consumer({
name: "analytics",
message: OrderCreated,
});
export const Email = consumer({
name: "email",
message: BillingCompleted,
});// index.ts
import { Console, Effect } from "effect";
import { makeClient } from "../generated/index.ts";
const program = Effect.gen(function* () {
const client = yield* makeClient;
yield* client.billing.handle(({ payload }) =>
Effect.gen(function* () {
yield* Console.log("Billing received: ", payload);
yield* client.billingCompleted.publish(payload);
}),
);
yield* client.analytics.handle(({ payload }) => Console.log("Analytics received: ", payload));
yield* client.email.handle(({ payload }) => Console.log("Email received: ", payload));
yield* client.ordersCreated.publish({
orderId: "123",
});
});
Effect.runSync(program);The example above generates this hierarchy:
orders.created
│
▼
billing
│
publishes billing.completed
│
▼
email
orders.created
│
▼
analytics
| Package | Purpose |
|---|---|
signalgraph |
CLI + schema DSL |
@signalgraph/runtime |
Runtime primitives |
@signalgraph/adapter-memory |
In-memory transport |
- Schema DSL
- Client generation
- Memory adapter
- First production adapter
- Watch mode
- Smarter watch mode
- Better diagnostics
- Graph visualization
- Interactive schema explorer
- Mermaid export
- Graphviz export
- DevTools
MIT