modsure
Version:
Check user content against your site rules using Jev.
202 lines (148 loc) • 8.21 kB
Markdown
# modsure
Moderate posts, comments, and other user-written text against your site's rules using Jev.
**Someone clicks Post → modsure checks your rules → your site publishes the post or explains what needs to change.**
Zero runtime or development dependencies. Native `fetch`, ESM JavaScript, TypeScript declarations, Node.js 22+. Runs on your server with your own TypeSafe API key.
## 1. Install
Install modsure in your application:
```sh
npm install modsure
```
These examples use ES modules. Set `"type": "module"` in your application's `package.json`, or use `.mjs` file extensions and update the imports accordingly.
## 2. Add your API key
Get your own TypeSafe API key from [console.typesafe.ai](https://console.typesafe.ai). Create a `.env` file in your application's root:
```dotenv
TYPESAFE_API_KEY=your_api_key
```
Add `.env` to `.gitignore`. On your hosting platform, set `TYPESAFE_API_KEY` in its environment settings. Keep the key and moderation code on your server, not in browser code.
## 3. Write your rules in one config file
Create `modsure.config.js` in your application's root. This file keeps the API-key environment variable, settings, and rules together:
```js
export default {
apiKey: process.env.TYPESAFE_API_KEY,
threshold: 0.7,
rules: [
{
id: 'no-spam',
description: 'No unsolicited advertising or promotional links.',
message: 'Please remove advertising or promotional links before posting.',
},
{
id: 'be-respectful',
description: 'No personal attacks, harassment, or threats against other people.',
message: 'Please remove personal attacks, harassment, or threats.',
},
{
id: 'respect-privacy',
description: 'Do not share another person’s private contact details without permission.',
message: 'Please remove other people’s private information.',
},
],
};
```
- `id`: a unique name for the rule, returned when it is flagged.
- `description`: the rule Jev checks, written in plain language.
- `message`: what your site can show the author if the rule is flagged. Defaults to `description`.
- `threshold`: the violation probability that triggers rejection. Here, `0.7` means 70% or higher. A rule can set its own `threshold` to override this value.
The library's default threshold is `0.5` if you omit it. Try your rules against representative posts and adjust the threshold for your community.
## 4. Check text before publishing
Create `moderation.js` next to your config:
```js
import { createModerator } from 'modsure';
import config from './modsure.config.js';
// Create once and reuse for incoming posts.
export const moderator = createModerator(config);
```
In your server's post handler:
```js
import { ModerationError } from 'modsure';
import { moderator } from './moderation.js';
// `text` is the submitted post from your request handler.
try {
const result = await moderator.moderate(text);
if (result.allowed) {
// Your application saves/publishes this exact text here.
} else {
// Leave the post unpublished and return these reasons to the author.
const reasons = result.violations.map(({ id, message }) => ({
rule: id,
message,
}));
console.log(reasons);
}
} catch (error) {
if (!(error instanceof ModerationError)) throw error;
// No decision was made. Keep the draft and let the author retry.
// Your application should return an unavailable response, not publish.
console.error(error.code);
}
```
Load `.env` when starting your server, replacing `server.js` with your application's entry point:
```sh
node --env-file=.env server.js
```
If your framework already loads `.env`, use its normal start command. Modsure does not automatically load `.env` or discover `modsure.config.js`: your application loads the environment and imports the config explicitly.
**Modsure returns decisions; your application handles publishing and displaying feedback.** Any flagged rule makes `allowed` false. Errors never count as an approved post. Invalid configuration or empty text raises `TypeError`.
Keep the rules under your site's control. Post text and rule descriptions are sent to TypeSafe for evaluation; modsure does not store or log them.
## Try a complete example
With the config and `moderation.js` above, create `check-post.js`:
```js
import { moderator } from './moderation.js';
const text = process.argv[2] ?? 'Thanks for writing this helpful article!';
const result = await moderator.moderate(text);
console.log(JSON.stringify(result, null, 2));
```
```sh
node --env-file=.env check-post.js 'Thanks for writing this helpful article!'
```
This makes a real API call using your key. For a full social-feed example, use the repository's `demo/` directory.
## Rules and decisions
Each rule needs a unique `id` and a plain-language `description` of the site's policy. Optional `message` is the explanation shown to the author; it defaults to the description. Write one clear requirement per rule.
The library sends one Noul question per rule in a single request to [TypeSafe's Jev API](https://docs.typesafe.ai/introduction/quickstart). A [Noul answer](https://docs.typesafe.ai/primitives/noul) is the probability that the content violates that rule. A rule is flagged when its probability is **greater than or equal to** its threshold. If any rule is flagged, `allowed` is false.
Jev returns judgments, not generated explanations. Rejection messages come from your configured rules, so authors see your wording. These are model judgments and can be wrong; test rules and thresholds against representative posts before relying on them.
```js
// Example result; probabilities depend on the model's response.
{
allowed: false,
model: 'jev-1.13.0',
violations: [{
id: 'no-spam',
description: 'No unsolicited advertising or promotional links.',
message: 'Please remove advertising or promotional links before posting.',
threshold: 0.5,
probability: 0.94,
violated: true
}],
checks: [/* every evaluated rule, using the same fields */]
}
```
## Configuration
| Option | Default | Meaning |
| --- | --- | --- |
| `apiKey` | Required | Your TypeSafe API key |
| `rules` | Required | Nonempty array of site rules |
| `threshold` | `0.5` | Default violation threshold, between 0 and 1 |
| `rules[].threshold` | Global threshold | Override for a particular rule |
| `model` | `jev-latest` | Jev model identifier; pin a version if desired |
| `timeoutMs` | `10000` | Deadline for request and response body |
| `fetch` | Native `fetch` | Optional compatible transport, useful for tests |
Raising a threshold requires a higher violation probability to reject. A threshold of 0 flags every answer; 1 flags only an answer of exactly 1. The default is a starting point, not a calibrated policy for every community. Rules are copied when the moderator is created; create a new moderator when policy changes.
`moderator.moderate(text, { signal })` accepts an optional `AbortSignal`. Empty or whitespace-only text and invalid configuration raise `TypeError`.
Service failures throw `ModerationError`; they never return an accept/reject result:
| `code` | Meaning |
| --- | --- |
| `HTTP_ERROR` | Provider returned an error; `status` contains the HTTP status |
| `NETWORK_ERROR` | Network/transport failure |
| `TIMEOUT` | Request exceeded its deadline |
| `ABORTED` | Caller cancelled the request |
| `INVALID_RESPONSE` | Invalid JSON, malformed data, or a missing/invalid rule answer |
There are no automatic retries or hidden additional calls. Your site controls retries and how an unavailable moderation service affects posting. Limits and billing are controlled by your TypeSafe account.
## Development
A working social-feed demo and browser recording instructions are in [demo/README.md](demo/README.md). Run `npm run demo` after adding your API key to `.env` to try it locally.
```sh
npm test
npm pack --dry-run
TYPESAFE_API_KEY=your-key node examples/moderate.js 'Text to check'
```
Tests use mocked HTTP responses and require no API key. The example makes a real, billable API request. The package has no build step.
## License
MIT. See [LICENSE](LICENSE).