UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

125 lines • 5.26 kB
"use strict"; /** * hosting/signin/memorySignIns — the bounded in-memory {@link SignInStore}. * * Bounded (rule 19), and fair — one person can never sign everybody else out * (review idI34 S-1): * * - **A per-account cap** (default 10 live sign-ins per `userId`). A person's * eleventh sign-in ends THAT person's oldest; nobody else's. * - **Dead rows are swept** before anything is counted — past their lifetime, * or idle longer than `idleMinutes` when given (the door's own idle limit; * review idI57 N-12) — so dead sign-ins never crowd out live ones. * - **A full store REFUSES** a new sign-in ({@link SignInStoreFullError}, the * door answers 503) rather than evicting another account's live sign-in. * - **Every end is announced** through `onDelete`, which `signInSource` turns * into its `onEnd` — so a socket carrying an ended sign-in closes at once. * * Per process, and stated: a restart signs everybody out, and a second replica * does not know the first one's sign-ins. Run one replica, pin each browser to * one (sticky sessions), or put a shared store behind the same verbs. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.memorySignIns = exports.DEFAULT_SIGN_INS_PER_ACCOUNT = exports.DEFAULT_SIGN_IN_MAX = exports.MemorySignInsConfigError = void 0; const errors_js_1 = require("./errors.js"); var errors_js_2 = require("./errors.js"); Object.defineProperty(exports, "MemorySignInsConfigError", { enumerable: true, get: function () { return errors_js_2.MemorySignInsConfigError; } }); exports.DEFAULT_SIGN_IN_MAX = 10_000; exports.DEFAULT_SIGN_INS_PER_ACCOUNT = 10; function memorySignIns(options = {}) { const max = whole(options.max ?? exports.DEFAULT_SIGN_IN_MAX, 'max'); const perAccount = whole(options.perAccount ?? exports.DEFAULT_SIGN_INS_PER_ACCOUNT, 'perAccount'); const idleMs = options.idleMinutes === undefined ? Infinity : positiveMinutes(options.idleMinutes) * 60_000; const now = options.now ?? Date.now; const warn = options.warn ?? ((message) => console.warn(message)); // A Map iterates in insertion order: the first key is the oldest sign-in. const rows = new Map(); const byAccount = new Map(); const listeners = new Set(); let warned = false; const remove = (key) => { const row = rows.get(key); if (row === undefined) return; rows.delete(key); const keys = byAccount.get(row.identity.userId); keys?.delete(key); if (keys?.size === 0) byAccount.delete(row.identity.userId); for (const listener of [...listeners]) { try { listener(key); } catch { // A listener's fault is the listener's; the row is gone regardless. } } }; const sweep = () => { const at = now(); for (const [key, row] of rows) { if (row.expiresAt <= at || at - row.lastSeenAt >= idleMs) remove(key); } }; return { get size() { return rows.size; }, max, perAccount, onDelete(listener) { listeners.add(listener); return () => { listeners.delete(listener); }; }, async create(signIn) { remove(signIn.key); sweep(); const account = signIn.identity.userId; const mine = byAccount.get(account); while (mine !== undefined && mine.size >= perAccount) { remove(mine.values().next().value); } if (rows.size >= max) throw new errors_js_1.SignInStoreFullError(max); rows.set(signIn.key, { ...signIn }); const keys = byAccount.get(account) ?? new Set(); keys.add(signIn.key); byAccount.set(account, keys); if (!warned && rows.size >= Math.ceil(max * 0.9)) { warned = true; warn(`[hosting] memorySignIns holds ${rows.size} of at most ${max} live sign-ins. At the cap ` + `new sign-ins are refused (503). Raise IDENTITY_SIGN_IN_MAX or use a shared store.`); } }, async find(key) { const row = rows.get(key); return row === undefined ? undefined : { ...row }; }, async touch(key, at) { const row = rows.get(key); // Updated IN PLACE: touching never moves a sign-in in the eviction order. if (row !== undefined) rows.set(key, { ...row, lastSeenAt: at }); }, async delete(key) { remove(key); }, }; } exports.memorySignIns = memorySignIns; function positiveMinutes(value) { if (!Number.isFinite(value) || value <= 0) { throw new errors_js_1.MemorySignInsConfigError('idleMinutes', 'idleMinutes is a positive number'); } return value; } function whole(value, option) { if (!Number.isInteger(value) || value <= 0) { throw new errors_js_1.MemorySignInsConfigError(option, `${option} is a positive whole number`); } return value; } //# sourceMappingURL=memorySignIns.js.map