Deduplication and Freshness
Both features key off _meta, a { mid, timestamp } object Arkos expects on incoming payloads and injects automatically on outgoing ones (see Enhanced Socket). If you're using the client Library, this is handled for you. If you're emitting from a raw socket.io-client connection, you need to send _meta yourself for these features to work.
Deduplication
Protects against the same logical message being processed twice — network retries, double-clicks, at-least-once delivery from a queue in front of your sockets.
gateway.on({ event: "send_message", dedup: { ttl: 600 } }, handler);How it works
- Deduplication is on by default for every event, at a Gateway-wide default of
{ enabled: true, ttl: 3600 }(seconds). - Every incoming message must carry
data._meta.mid— a unique string ID for that logical message. If it's missing, empty, or not a string, Arkos throws aBadRequestErrorbefore your handler ever runs. - Arkos atomically checks-and-sets a key
arkos::dedup:{event}:{mid}in the configured store. If the key already exists, the message is a duplicate: your handler is skipped, and if the client passed an ack, it receives{ success: true, duplicate: true }— not an error, since from the sender's perspective the message did succeed the first time. _metais stripped fromdatabefore your handler runs;mid/timestampare available onsocket.metainstead.
Turning it off
// per event
gateway.on({ event: "cursor_move", dedup: false }, handler);
// per gateway — every event skips dedup unless it opts back in
const gateway = ArkosGateway({ name: "chat", dedup: false });Turn it off for high-frequency, order-independent events where a duplicate is harmless (cursor positions, typing indicators) — the check-and-set has a real cost per message.
Config precedence
dedup resolves event → Gateway → parent Gateway, with the event-level config taking priority and merging on top of the rest:
const parent = ArkosGateway({ name: "chat", dedup: { ttl: 3600 } });
const child = ArkosGateway({ name: "rooms" });
parent.use(child);
child.on({ event: "x", dedup: { ttl: 60 } }, handler); // ttl: 60 wins
child.on({ event: "y" }, handler); // inherits ttl: 3600 from parentFreshness — maxAge
Rejects messages that are simply too old to matter — a client that reconnects after being offline for a while can have a backlog of queued actions; maxAge lets you drop the stale ones instead of processing them as if they just happened.
gateway.on(
{ event: "cursor_move", maxAge: 5000 }, // reject anything older than 5s
handler
);How it works
- If
maxAgeis set (per event, per Gateway, or inherited from a parent) and the incoming message has no_meta.timestamp, Arkos throws immediately —maxAgewithout a timestamp to check is a config error, not a runtime skip. - If
_meta.timestampis present, Arkos always validates it (even withoutmaxAgeset): an unparseable date throwsInvalidTimestamp, and a timestamp more than 1 second in the future throwsFutureTimestamp— a cheap clock-skew/tampering guard that applies regardless of whether you configuredmaxAge. - If
maxAgeis set andDate.now() - timestamp > maxAge, the message is rejected withStaleMessage.
Config precedence
Same resolution order as dedup — event overrides Gateway, Gateway overrides parent:
const parent = ArkosGateway({ name: "chat", maxAge: 60_000 });
const child = ArkosGateway({ name: "rooms" });
parent.use(child);
child.on({ event: "x", maxAge: 300_000 }, handler); // 5 minutes, overrides parent's 1 minute
child.on({ event: "y" }, handler); // inherits 1 minute from parentIf neither the event, the Gateway, nor any parent sets maxAge, no age check runs at all — only the always-on future-timestamp guard applies (and only if _meta.timestamp was sent in the first place).