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 🙏

© 2025 – Pkg Stats / Ryan Hefner

restate-for-dummies

v0.2.4

Published

Type-safe Restate SDK wrapper with simplified API

Readme

Restate-for-Dummies

Making Restate dead simple with automatic type safety, state management, and seamless service communication.

What's the Problem?

Restate is powerful, but setting it up involves:

  • Configuring serialization for every service, object, and workflow
  • Managing type safety across service boundaries
  • Manually creating clients with the right serde configuration
  • Keeping track of which context methods are available where

This library removes all that complexity.

Why Use This?

🎯 Type-Safe State Out of the Box

// Your state is fully typed - no more guessing!
const counter = restate.createObject<{ count: number; lastUser: string }>("Counter")({
  increment: async ({ getState, setState }, userId: string) => {
    const count = (await getState("count")) ?? 0;  // ✨ TypeScript knows this is number | undefined
    await setState("count", count + 1);             // ✨ TypeScript enforces count is a number
    await setState("lastUser", userId);             // ✨ TypeScript enforces lastUser is a string
    return count + 1;
  }
});

🔗 Call Any Service/Object/Workflow Without Setup

const orderService = restate.createService("OrderService")({
  createOrder: async ({ service, object }, userId: string, items: string[]) => {
    // Call other services - serde is handled automatically!
    const user = await service(userService).getUser(userId);

    // Call objects - same simple syntax
    const inventory = await object(inventoryObject, "main").checkStock(items);

    // Start workflows - still just works
    await workflow(fulfillmentWorkflow, orderId).workflowSubmit({ user, items });

    return { orderId, status: "created" };
  }
});

🚀 One Configuration, Use Everywhere

// Configure once
const restate = new RestateClient({
  SerdeClass: SuperJsonSerde  // Now you can use Dates, Sets, Maps, etc everywhere!
});

// Everything created from this client uses your serde automatically
const service1 = restate.createService("Service1")({ /* ... */ });
const service2 = restate.createService("Service2")({ /* ... */ });
const object1 = restate.createObject<State>("Object1")({ /* ... */ });

Quick Start

bun add restate-for-dummies
import { RestateClient } from "restate-for-dummies";

// 1. Create your client (usually just once per app)
const restate = new RestateClient();

// 2. Define services with auto-typed context
const greetingService = restate.createService("GreetingService")({
  greet: async ({ ctx }, name: string) => {
    return `Hello, ${name}!`;
  },

  greetDelayed: async ({ ctx, serviceSend }, name: string, delayMs: number) => {
    // Schedule a delayed call to ourselves - serde handled automatically
    await serviceSend(greetingService).greet(name).send({ delay: delayMs });
    return "Scheduled!";
  }
});

// 3. Export for Restate runtime
export default greetingService;

Core Concepts

Services - Stateless Operations

Services are for business logic without persistent state:

const emailService = restate.createService("EmailService")({
  sendWelcomeEmail: async ({ ctx, runStep }, userId: string) => {
    // Use runStep for reliable execution
    const user = await runStep("fetch-user", () => fetchUserFromDB(userId));
    const result = await runStep("send-email", () => sendEmail(user.email, "Welcome!"));
    return result;
  }
});

Objects - Stateful Entities

Objects maintain state across calls:

interface CartState {
  items: Array<{ id: string; quantity: number }>;
  userId?: string;
}

const shoppingCart = restate.createObject<CartState>("ShoppingCart")({
  addItem: async ({ getState, setState }, itemId: string, quantity: number) => {
    const items = (await getState("items")) ?? [];
    items.push({ id: itemId, quantity });
    await setState("items", items);
  },

  checkout: async ({ ctx, getState, clearState, workflow }) => {
    const items = await getState("items");
    if (!items?.length) throw new Error("Cart is empty");

    // Start a checkout workflow
    await workflow(checkoutWorkflow, ctx.key).workflowSubmit({ items });

    // Clear the cart
    await clearState("items");
  }
});

Workflows - Long-Running Processes

Workflows handle complex, long-running operations:

interface OrderState {
  status: "pending" | "paid" | "shipped" | "delivered";
  trackingNumber?: string;
}

const orderWorkflow = restate.createWorkflow<OrderState>("OrderWorkflow")({
  run: async ({ ctx, setState, runStep, object }, order: Order) => {
    await setState("status", "pending");

    // Wait for payment
    const payment = await ctx.promise<Payment>("payment");
    await setState("status", "paid");

    // Reserve inventory
    await runStep("reserve-inventory", async () => {
      await object(inventory, "main").reserve(order.items);
    });

    // Ship order
    const tracking = await runStep("ship", () => shipOrder(order));
    await setState("trackingNumber", tracking);
    await setState("status", "shipped");

    return tracking;
  },

  // Workflows can have additional handlers
  recordPayment: async ({ ctx }, payment: Payment) => {
    await ctx.resolvePromise("payment", payment);
  },

  getStatus: async ({ getState }) => {
    return {
      status: await getState("status"),
      trackingNumber: await getState("trackingNumber")
    };
  }
});

The Magic: Every Handler Gets Everything

No more context confusion! Every handler automatically gets the right tools:

const myService = restate.createService("MyService")({
  doEverything: async (context) => {
    // Every handler can:
    context.service(userService).getUser(id);      // ✅ Call any service
    context.object(cart, "cart-123").getItems();   // ✅ Call any object
    context.workflow(order, "order-456").getStatus(); // ✅ Call any workflow
    context.runStep("step", () => { /* ... */ });  // ✅ Use durable execution

    // Objects and workflows also get:
    context.getState("myKey");    // ✅ Type-safe state access
    context.setState("myKey", value); // ✅ Type-safe state updates
  }
});

Using from Outside Restate

Need to call your services from a regular Node.js app? We've got you covered:

// From your Express/Fastify/etc app:
const client = new RestateClient({ restateUrl: "http://localhost:8080" });

// Call services
const greeting = await client.serviceClient(greetingService).greet("World");

// Call objects
const items = await client.objectClient(shoppingCart, "user-123").getItems();

// Call workflows
const orderClient = client.workflowClient(orderWorkflow, "order-789");
await orderClient.workflowSubmit({ items, userId });
const status = await orderClient.getStatus();

Custom Serialization

By default, only JSON-serializable types work. Want to use Dates, Sets, Maps, or custom classes? Just plug in your serde:

import superjson from "superjson";

class SuperJsonSerde {
  contentType = "application/json";

  serialize(value: any): Uint8Array {
    return new TextEncoder().encode(superjson.stringify(value));
  }

  deserialize(bytes: Uint8Array): any {
    return superjson.parse(new TextDecoder().decode(bytes));
  }
}

// Now use rich types everywhere!
const restate = new RestateClient({ SerdeClass: SuperJsonSerde });

const dateService = restate.createService("DateService")({
  scheduleFor: async ({ ctx }, date: Date) => {  // ✅ Date objects work!
    return { scheduled: date, in: date.getTime() - Date.now() };
  }
});

Before vs After

Before (Raw Restate SDK):

// Define serde
const serde = new SuperJsonSerde();

// Create service with manual serde config
const userService = restate.service({
  name: "UserService",
  handlers: {
    getUser: restate.handlers.handler({
      input: serde, output: serde
    }, async (ctx: Context, id: string) => {
      // Manually create client with serde
      const profileClient = ctx.serviceClient(profileService, { serde });
      const profile = await profileClient.getProfile(id);
      return { id, profile };
    })
  }
});

After (With This Library):

// Configure once
const restate = new RestateClient({ SerdeClass: SuperJsonSerde });

// Everything just works!
const userService = restate.createService("UserService")({
  getUser: async ({ service }, id: string) => {
    const profile = await service(profileService).getProfile(id);
    return { id, profile };
  }
});

Installation

bun add restate-for-dummies
# or
npm install restate-for-dummies

Complete Example

Here's a real-world example showing the power of this library:

import { RestateClient } from "restate-for-dummies";
import { SuperJsonSerde } from "./serde";

const restate = new RestateClient({ SerdeClass: SuperJsonSerde });

// User service
const userService = restate.createService("UserService")({
  createUser: async ({ ctx, runStep }, email: string, name: string) => {
    const user = await runStep("create-in-db", () =>
      db.users.create({ email, name })
    );

    await runStep("send-welcome-email", () =>
      sendEmail(email, "Welcome!")
    );

    return user;
  }
});

// Shopping cart object with state
interface CartState {
  items: Map<string, number>;  // productId -> quantity
  coupon?: string;
}

const cartObject = restate.createObject<CartState>("Cart")({
  addItem: async ({ getState, setState }, productId: string, quantity: number) => {
    const items = (await getState("items")) ?? new Map();
    items.set(productId, (items.get(productId) ?? 0) + quantity);
    await setState("items", items);
  },

  applyCoupon: async ({ setState }, code: string) => {
    await setState("coupon", code);
  },

  checkout: async ({ ctx, getState, clearState, workflow }) => {
    const items = await getState("items");
    const coupon = await getState("coupon");

    if (!items?.size) throw new Error("Cart empty");

    // Start checkout workflow
    const orderId = `order-${Date.now()}`;
    await workflow(checkoutWorkflow, orderId).workflowSubmit({
      cartId: ctx.key,
      items: Array.from(items.entries()),
      coupon
    });

    // Clear cart
    await clearState("items");
    await clearState("coupon");

    return orderId;
  }
});

// Checkout workflow
const checkoutWorkflow = restate.createWorkflow<{
  status: string;
  paymentId?: string;
}>("CheckoutWorkflow")({
  run: async ({ ctx, setState, service, object }, input: CheckoutInput) => {
    await setState("status", "processing");

    // Calculate total
    const total = await service(pricingService).calculateTotal(
      input.items,
      input.coupon
    );

    // Process payment
    const paymentId = await ctx.promise<string>("payment-processed");
    await setState("paymentId", paymentId);
    await setState("status", "paid");

    // Update inventory
    for (const [productId, quantity] of input.items) {
      await object(inventoryObject, productId).reserve(quantity);
    }

    await setState("status", "completed");
    return { orderId: ctx.key, total, paymentId };
  },

  processPayment: async ({ ctx, getState }, paymentId: string) => {
    const status = await getState("status");
    if (status !== "processing") throw new Error("Invalid state");

    await ctx.resolvePromise("payment-processed", paymentId);
  }
});

// Export for Restate runtime
export const services = [userService];
export const objects = [cartObject];
export const workflows = [checkoutWorkflow];

License

MIT