> ## Documentation Index
> Fetch the complete documentation index at: https://docs.seraph.si/llms.txt
> Use this file to discover all available pages before exploring further.

# Scripts & modules

> When to use a .mod.js module, lifecycle hooks, require and sharing code.

Seraph loads two kinds of file out of the plugins folder, and the only difference is the name.

|                     | Script                      | Module                  |
| ------------------- | --------------------------- | ----------------------- |
| File name           | `name.js`                   | `name.mod.js`           |
| Run as              | Interpreted                 | Compiled to bytecode    |
| Reloading           | Hot swapped on every change | Loaded once per session |
| Lifecycle hooks     | No                          | `enable` / `disable`    |
| `import` / `export` | No                          | Yes                     |

Reach for a script by default. Reach for a module when the plugin does work every tick or every
frame, where compiled bytecode is markedly quicker, or when it needs to clean something up on
shutdown.

## Scripts

A script is just a file. It runs top to bottom when it loads, and everything it registered, its
listeners, commands and timers, is cleared and re-registered whenever it reloads. There is nothing
to tear down by hand.

```js theme={null}
declareModule({ name: "ping", permissions: ["chat", "commands"] });

events.registerCommand("ping", () => {
    chat.addChatMessage("§apong");
});
```

## Modules

Name a file `<name>.mod.js` and it is treated as a module. It is compiled rather than interpreted,
and it stays loaded for the life of the client: editing it does not reload it, and neither the
watcher nor `/seraph plugin` swaps it out. Both tell you a restart is needed instead. Ordinary
scripts carry on reloading around any running modules, which stay up.

Because a module outlives a reload, anything it registers conditionally is its own to remove:

```js theme={null}
declareModule({ name: "watcher", permissions: ["chat"] });

let listening = -1;
let ticking = -1;

exports.enable = () => {
    listening = events.on("chat", onChat);
    ticking = cron("*/5 * * * *", () => chat.addChatMessage("still here"));
};

exports.disable = () => {
    events.off(listening);
    clearCron(ticking);
};
```

`enable` runs once the file has been evaluated. `disable` runs when the client shuts down, which is
where anything outliving the game, a written file or an open connection, should be closed.

The runaway guard applies to modules exactly as it does to scripts, so a loop that never ends is
stopped rather than left to hang the client.

### ES module syntax

Modules may use `import` and `export`, which Seraph rewrites to its own loader:

```js theme={null}
declareModule({ name: "stats-core" });

export const loadedAt = Date.now();

export function describe() {
    return "stats-core reporting in";
}

export class Counter {
    #value = 0;

    bump(by = 1) {
        this.#value += by;
        return this.#value;
    }
}
```

## require

`require` loads either another plugin or another file.

```js theme={null}
const core = require("stats-core");
const helpers = require("./lib/helpers");
```

A specifier matching the `name` of a loaded module returns that module's exports. Declare it in
`dependsOn` so it is guaranteed to have loaded first. Anything else is treated as a path relative
to the requiring file, with or without the `.js` suffix, which is how you keep shared code in a
subfolder even though only top level `.js` files are discovered.

## Timers

Timers are available both as globals and on the `scheduler` object, and both return an id you can
stop later.

```js theme={null}
const once = setTimeout(() => chat.addChatMessage("later"), 5000);
const every = setInterval(() => chat.addChatMessage("tick"), 60000);
const hourly = cron("0 * * * *", () => chat.addChatMessage("on the hour"));

clearTimeout(once);
clearInterval(every);
clearCron(hourly);
```

`cron` takes the usual five fields, minute, hour, day of month, month and day of week, aligned to
the wall clock. Each accepts a number, `*`, a `first-last` range, a comma separated list and a
`/step` suffix. Months and days may be named, `jan` or `mon`, and both `0` and `7` mean Sunday.
When both the day of month and the day of week are restricted the callback runs when either
matches, as cron traditionally behaves. An expression that cannot be read returns `-1`.

A script's timers are cleared for it on reload. A module's are not, which is what `disable` is for.

## Commands

`events.registerCommand` adds a real chat command, with tab completion:

```js theme={null}
declareModule({ name: "friends", permissions: ["commands", "chat"] });

events.registerCommand(
    "greet",
    (args) => chat.addChatMessage("hello " + (args[0] ?? "nobody")),
    (args) => events.completePlayers(args),
);
```

`events.completePlayers` narrows the players currently in tab down to those matching the argument
being typed, the same set Seraph's own commands complete against.

Subcommands are a map rather than a function:

```js theme={null}
events.registerCommand("party", {
    invite: (args) => chat.sendChatMessage("/p invite " + args[0]),
    leave: () => chat.sendChatMessage("/p leave"),
});
```
