dánwòdánwò

Examples

SaaS with integrations

Relay, a team-notes app: workspaces on Clerk organizations, a Stripe trial and billing, Slack and Google Calendar connected by OAuth, live notes over Pusher, and Inngest jobs that send email through Resend.

Relay, a team-notes app: workspaces on Clerk organizations, a Stripe trial and billing, Slack and Google Calendar connected by OAuth, live notes over Pusher, and Inngest jobs that send email through Resend. See it on GitHub.

What it shows

  • Onboarding that makes one workspace, one Clerk organization, one Stripe customer and one trialing subscription, however Clerk and Stripe fail on the way (102 trials)
  • Time travel through a trial: Stripe's trial_will_end on day 11 and the first invoice on day 14 start Inngest jobs, and the app's status matches Stripe's
  • A note posted to Slack exactly once under Slack 429s, 5xx and lost answers, and seen live by a teammate in a second browser
  • SAAS_BUG switches that turn one safeguard off, so you can watch the matrix catch it

Stack

TanStack StartHonoInngestPostgresDrizzleTailwind CSS

Emulates: Clerk, Stripe, Slack, Google, Pusher, Resend. Runs on: A TanStack Start app, a Hono API, an Inngest worker and the Inngest dev server, with Postgres in Docker.

Run it

Copy examples/saas anywhere, then from its folder:

npm install
npx @danwo/cli test

Its scripts:

npm run build               # vite build web
npm run typecheck           # tsc --noEmit && tsc --noEmit -p web
npm run test                # danwo test
npm run test:agentic        # danwo test tests/agentic.danwo.ts
npm run matrix              # danwo matrix tests/notes.danwo.ts --grep "posts a published note"
npm run matrix:plan         # danwo matrix tests/notes.danwo.ts --grep "posts a published note" --plan
npm run matrix:onboarding   # danwo matrix tests/onboarding.danwo.ts --grep "signs up and starts a trial"
npm run matrix:billing      # danwo matrix tests/billing.danwo.ts --grep "the trial ends"
npm run serve               # danwo serve

Its README says what else it needs and what the last full matrix found.

Until the danwo version it pins is on npm, install it from a local build instead: from the repository's root, bun run release <version> --pack, then node examples/link.ts saas, which installs a copy and prints where it is.

Background jobs explains what this example leans on.

The test

tests/notes.danwo.ts
/**
 * Notes: publishing one posts it to the workspace's Slack channel once (a background job on
 * Inngest), and a teammate with the workspace open sees it arrive live (Pusher).
 */
import { resendInbox, slackWorkspace, test as base } from "@danwo/sdk"

import {
  ADA,
  BOB,
  clerkMembers,
  clerkOrganizations,
  expectPostedOnce,
  expectWelcomedOnce,
  openSection,
  type RelayFixtures,
  signUp,
  WORKSPACE,
  writeNote,
} from "./relay.ts"

const test = base.extend<RelayFixtures>()

test(
  "posts a published note to Slack once",
  { session: "owner-with-slack" },
  async ({ app, screen, expect, mail, slack, settle }) => {
    await app.open("/")
    await writeNote(
      { app, screen, expect, mail },
      { title: "Weekly update", body: "Billing shipped. Next: the calendar sync." },
    )
    await settle()

    const [message] = slackWorkspace(slack).messages("#product")

    expect(message?.text).toContain("*Weekly update* by Ada Lovelace")
  },
).invariant(async (fixtures) => {
  await expectPostedOnce(fixtures)
  fixtures.expect(await fixtures.sql("select id from notes"), "one note saved").toHaveLength(1)
})

test(
  "keeps a draft out of Slack until it is published",
  { session: "owner-with-slack" },
  async ({ app, screen, expect, mail, slack, settle }) => {
    await app.open("/")
    await writeNote(
      { app, screen, expect, mail },
      { title: "Pricing ideas", body: "Rough.", draft: true },
    )
    await settle()
    expect(slackWorkspace(slack).messages("#product")).toHaveLength(0)

    await screen
      .getByRole("article", { name: "Pricing ideas" })
      .getByRole("button", { name: "Publish" })
      .click()
    await expect(
      screen.getByRole("article", { name: "Pricing ideas" }).getByText("Published"),
    ).toBeVisible()
    await settle()
    expect(slackWorkspace(slack).messages("#product")).toHaveLength(1)
  },
).invariant(expectPostedOnce)

test(
  "a teammate sees a published note live",
  { session: "owner-in-workspace" },
  async ({ app, screen, expect, mail, users }) => {
    await app.open("/")
    await openSection({ app, screen, expect, mail }, "Settings")
    await screen.getByRole("link", { name: "Members" }).click()
    await screen.getByLabel("Email address").fill(BOB.email)
    await screen.getByRole("button", { name: "Send invite" }).click()
    await expect(screen.getByRole("status")).toHaveText(`Invitation sent to ${BOB.email}.`)

    /* Bob signs up in his own browser and accepts; the front door finds his invitation. */
    const bob = await users.open("bob")

    await signUp(bob, BOB)
    await bob.expect(bob.screen.getByRole("heading", { name: `Join ${WORKSPACE}` })).toBeVisible()
    await bob.screen.getByRole("button", { name: "Accept the invitation" }).click()
    await bob.expect(bob.screen.getByRole("heading", { level: 1, name: "Notes" })).toBeVisible()

    await openSection({ app, screen, expect, mail }, "Notes")
    await writeNote(
      { app, screen, expect, mail },
      { title: "Q4 roadmap", body: "1. Calendar sync\n2. Mobile app" },
    )

    /* No reload: the note reaches Bob's open page over Pusher. */
    await bob.expect(bob.screen.getByRole("article", { name: "Q4 roadmap" })).toBeVisible()
    await bob
      .expect(bob.screen.getByRole("status"))
      .toHaveText(`${ADA.name} published “Q4 roadmap”`)
  },
).invariant(async (fixtures) => {
  const { sql, clerk, resend, expect } = fixtures
  const [org] = await clerkOrganizations(clerk)
  const members = await sql<{ email: string; role: string }>(
    "select u.email, m.role from members m join users u on u.id = m.user_id order by u.email",
  )

  expect(members, "Ada and Bob are members").toEqual([
    { email: ADA.email, role: "org:admin" },
    { email: BOB.email, role: "org:member" },
  ])
  expect(
    clerkMembers(clerk, org?.id ?? "").toSorted((a, b) =>
      (a.email ?? "").localeCompare(b.email ?? ""),
    ),
    "and so are they in the Clerk organization",
  ).toEqual(members)
  expect(
    resendInbox(resend)
      .list(BOB.email)
      .filter((email) => email.subject.includes(`invited you to ${WORKSPACE}`)),
    "one invitation email",
  ).toHaveLength(1)
  await expectWelcomedOnce(fixtures)
})

On this page

Run it on every pull request

A GitHub check and one comment per pull request, with the flight records attached.

Set up CI