@littlefriend/node
v0.1.1
Published
Little Friend for Node.js: send server-confirmed outcomes (orders, signups, upgrades) with a secret server key.
Maintainers
Readme
@littlefriend/node
Send server-confirmed outcomes to Little Friend: the order that was paid, the signup that was verified, the plan that was upgraded. A click in the browser is intent; this is the result.
No dependencies. Node 18.17 or later.
Install
pnpm add @littlefriend/nodeimport { LittleFriend } from '@littlefriend/node';
const lf = new LittleFriend({ key: process.env.LF_SERVER_KEY! }); // lfs_...
lf.track({
id: order.id, // optional; reuse it on resend so the outcome counts once
name: 'order.completed',
route: '/checkout',
props: { plan: 'pro', seats: 5 },
value: { amount: 4900, currency: 'USD' }, // minor units: $49.00
});Call await lf.shutdown() before the process exits. In a serverless function, call await lf.flush() before returning.
API
new LittleFriend(options)
| Option | Default | |
|---|---|---|
| key | required | Secret server key, lfs_.... Throws for any other kind of key. |
| endpoint | https://in.littlefriend.io | Collector origin. Events go to {endpoint}/v1/server. |
| flushAt | 20 | Send once this many events are queued (at most 50). |
| flushInterval | 5000 | Send queued events at least this often, in ms. The timer never keeps the process alive. |
| maxQueue | 10000 | Most events held in memory. New events beyond it are dropped and reported. |
| retries | 3 | Retries for network errors, timeouts, 429 and 5xx. |
| timeout | 10000 | Timeout for one request, in ms. |
| onError | console.warn | Receives every LittleFriendError. |
| fetch | global fetch | Custom fetch, for example with a proxy agent. |
| disabled | false | Validate but send nothing, for tests and local development. |
lf.track(event): string | null
| Field | |
|---|---|
| name | Required. Lowercase, such as order.completed: ^[a-z][a-z0-9_.:-]{0,63}$. |
| id | 8 to 32 URL-safe characters. Generated when absent. |
| at | Date, epoch ms or ISO string. Default: now. |
| route | Path or template. The query and fragment are removed, and id-like or personal segments become :id or :redacted, before the event is queued. |
| sessionId | Journey session id handed over from the browser (journey mode projects only). |
| correlationId | Links this outcome to the browser event sent with the same id, from track(name, props, correlationId) in @littlefriend/tracker (journey mode). Use an opaque value the browser accepts too: 16 to 64 characters of A-Z a-z 0-9 _ -, such as a base64url or hex SHA-256 digest of the order id, or a UUID. Values that look like an email address or a long number, including a card number with no separators, are dropped. |
| props | Up to 8 props: snake_case keys, strings (cut to 64 characters), numbers under 100,000,000, booleans. |
| value | { amount, currency }: whole minor units and an ISO 4217 code. |
Returns the event id, or null when the event was not queued. track never throws. A bad name, id or at rejects the event; a bad optional field is dropped, the event still goes, and onError names the field.
lf.flush(): Promise<FlushResult>
Sends everything queued now and resolves when those events are delivered or given up on: { accepted, dropped, failed }. It never rejects.
lf.shutdown({ timeout? }): Promise<FlushResult>
Stops accepting events and flushes. After timeout ms (default 10000) in-flight requests are cancelled and what is left counts as failed.
Errors
onError receives a LittleFriendError with a code, the eventIds concerned and, for delivery errors, the HTTP status.
| Code | Meaning |
|---|---|
| invalid_event | Not queued: bad name, id or time. |
| invalid_field | Queued without the fields in fields. |
| queue_full | Not queued: maxQueue reached. |
| closed | track after shutdown. |
| rejected | The collector refused the batch (400, 401, 403, 413). Not retried. |
| send_failed | Network errors, timeouts, 429 or 5xx outlasted the retries. |
| dropped | The collector accepted the batch but dropped some events. |
Delivery
Batches hold up to 50 events and stay under 60,000 bytes. They go out one at a time, in order. A retry resends the exact same body, so event ids stay the same and the collector counts each event once. Retries back off exponentially with jitter and follow Retry-After, capped at 30 s. Nothing is written to disk: events still queued when the process dies are lost.
Development
pnpm --filter @littlefriend/node test
pnpm --filter @littlefriend/node typecheckTests run against a stub collector on a real socket and check every body with the rules in @littlefriend/contract.
