# Learn Effect with a bot

Understand the small set of Effect concepts needed to send a message and run a bot

Effect is optional. The [default JavaScript and TypeScript API](https://fluxerly.neontechspace.com/docs/nightly/quick-start.md) supports the same SDK capabilities with `async` functions and Results

Use the native Effect API when several bot operations must stop together, resources must close and failures need separate handling. Those operations can use the same services and cleanup scope as the rest of an Effect application

The examples introduce each Effect concept before combining them into a bot

## Install the matching version

To start from a generated Effect project with a test, run the command from [Generate the project](https://fluxerly.neontechspace.com/docs/nightly/quick-start.md#generate-the-project) with `--template effect` in the bot's folder, which may already hold the token's `.env` and a `.gitignore`.

First generate a project with [the quick start](https://fluxerly.neontechspace.com/docs/nightly/quick-start.md) and choose Effect, which installs the SDK, Effect, TypeScript 7 and the Node.js types for typechecking. Fluxerly is tested against Node.js 24.15 or newer. A project set up by hand needs `"type": "module"` in `package.json` and the TypeScript setup that the quick start's TypeScript section describes. Native examples use TypeScript

Fluxerly works with Effect 4. The installed SDK lists its accepted range under `peerDependencies.effect` in `node_modules/@neontechspace/fluxerly/package.json`. Effect 3 and Effect 4 prereleases are incompatible

These examples import Effect directly, so declare it as a direct dependency. For this documentation version, the command is:

```sh
npm install effect
```

Check which Effect version the application has installed:

```sh
npm list effect
```

Recheck the required version when upgrading the SDK. The [Effect v4 learning material](https://effect.website/docs/v4/getting-started/why-effect) matches this major version

## Start with one request

An Effect describes work but does not run it yet. `Effect.runPromise` runs it from the application's top-level code

```ts
import { Effect } from "effect"
import { createClient } from "@neontechspace/fluxerly/effect"

export function sendHello(token: string, channelId: string) {
    const program = Effect.scoped(
        Effect.gen(function* () {
            const client = yield* createClient({ token })
            return yield* client.messages.send(channelId, "Hello from Effect")
        }),
    )
    return Effect.runPromise(program)
}
```

Call `sendHello` with a private bot token and a channel the bot can write to. It resolves to the sent message after the client has finished cleanup. Sending through REST does not need a gateway connection

Read the program in order:

1. `Effect.gen` writes sequential work using a generator function
2. The `yield*` expression runs an Effect and returns its successful value. An expected failure skips the remaining steps unless handled
3. `Effect.scoped` supplies a cleanup lifetime. When that lifetime ends, Fluxerly closes the client and waits for its owned cleanup
4. `Effect.runPromise` runs the program and returns a JavaScript Promise

Each `yield*` provides the successful value directly, while failures remain part of the Effect's type. Running the same description twice repeats its requests, including writes

## Add a message handler

Native handlers must return an Effect. For an existing native client, `client.on` provides a subscription

```ts
import { Effect } from "effect"
import type { Client } from "@neontechspace/fluxerly/effect"

export function installPing(client: Client) {
    return client.on("messageCreate", message => {
        if (message.author.isBot || message.content !== "!ping") return Effect.void
        return client.messages.reply(message, "Pong!")
    })
}
```

`Effect.void` describes doing nothing successfully. Returning the reply Effect lets the SDK run and cancel it with the handler. Calling `Effect.runPromise` inside each callback would run that work separately from the handler

## Run the complete bot

This is the bot that `fluxerly init --template effect` writes. Save it as `bot.ts` and start it with `node --env-file=.env bot.ts`, using the `.env` file from the [quick start](https://fluxerly.neontechspace.com/docs/nightly/quick-start.md). The `defineBot` function holds the bot's options and returns them unchanged, keeping the types of command arguments and the services that handlers require. The runner registers the commands before it connects

```ts
import { Effect } from "effect"
import { defineBot, runBot } from "@neontechspace/fluxerly/effect"

// The bot's options without its token, so its test can run them through createTestBot without a token or a network
export const bot = defineBot({
    commands: {
        prefix: "!",
        commands: {
            // A failed reply is reported to the log without stopping the bot
            ping: { execute: ({ reply }) => reply("Pong!") },
        },
    },
})

// Running this file starts the bot, while importing it from a test does not.
// A failure that stops the bot, such as a rejected token, is logged and sets a failing exit code
if (import.meta.main)
    await Effect.runPromiseExit(runBot({ token: process.env.FLUXER_BOT_TOKEN, processSignals: true, ...bot }))
```

Send `!ping` and expect `Pong!`. Press Ctrl+C to stop the bot. The SDK interrupts it and waits for its cleanup

The last line passes the options and the token to `runBot` and runs that program with `Effect.runPromiseExit`, which resolves with the program's outcome, called an Exit, instead of rejecting. The line runs only when Node.js runs the file, so a test can import `bot` without starting it, as the [Effect application testing guide](https://fluxerly.neontechspace.com/docs/nightly/effect-application-testing.md#test-a-runbot-bot-against-an-in-memory-fluxer) shows. The `runBot` program reports its own failure: A failure that stops the bot, such as a rejected token, is logged once and sets `process.exitCode` to 1, so the Exit needs no further handling. Setting the exit code instead of calling `process.exit()` lets Node.js finish cleanup before the process ends

<details>
<summary>What the runner watches while the bot runs</summary>

The runner registers the handler before the gateway connects and then watches both the connection and the handler's subscription. A connection failure, or a subscription that closes unexpectedly, stops the bot and waits for cleanup.
A failed handler is reported and not retried, and the bot keeps running. Returning the `reply` Effect lets the SDK interrupt it when the handler stops.
Keep the token private, including in the environment and in process-manager settings

</details>

The [small bot](https://fluxerly.neontechspace.com/docs/nightly/small-bot.md) guide shows a larger native bot with commands, a permission guard, a cooldown and a welcome message

## Report failures outside runBot

Application code outside `runBot`, such as a one-off job, reports its own failure. A failed Exit holds a Cause, Effect's full record of why the program failed. Pass `exit.cause` to `describeError`, which prints each error's code, hint and details with credentials masked

```ts
import { Effect, Exit } from "effect"
import { createClient, describeError } from "@neontechspace/fluxerly/effect"

export async function announce(token: string, channelId: string) {
    const exit = await Effect.runPromiseExit(
        Effect.scoped(
            Effect.gen(function* () {
                const client = yield* createClient({ token })
                yield* client.messages.send(channelId, "Maintenance starts in ten minutes")
            }),
        ),
    )
    if (Exit.isFailure(exit)) {
        console.error(describeError(exit.cause))
        process.exitCode = 1
    }
}
```

## Read the Effect types

`Effect.Effect<A, E, R>` describes the successful value `A`, expected failures `E`, and required services `R`. A client operation can use the same application services as other Effects without the SDK starting a separate runtime

Effect represents expected errors, defects and interruption. A defect is an unexpected fault, and interruption means cancellation. Effect's Cause can retain these together, including cleanup failures. Do not turn every Cause into a retry

Continue with [scoped workflows](https://fluxerly.neontechspace.com/docs/nightly/effect-workflows.md) to combine collectors, failure handling and concurrent reads
