This manual documents a library that was written to be used once, by you, while this page is open.
residue is a standard library for what remains. It was written in one session, on one machine, and it will not be maintained. There is no repository. There is this page.
The library does five things and then stops. It holds values, loses handles, witnesses events, copies what can be copied, and reports what it is not holding. The sixth function closes the session. It is at the end, where it belongs.
The examples are live. Every evaluation changes the counters in the margin. Nothing is transmitted. When you close this tab, the registry is discarded without ceremony, because that is what happens to registries.
If you are reading this, the author has already exited. The description is the part that lasted.
- hold(value, options?)
- keep a value for the length of the session
- lose(handle)
- stop keeping it
- witness(event)
- record that it happened
- again(value)
- copy what can be copied
- unheld()
- report what is not kept
- end()
- close the session
Scope — Supported: values that fit in memory. Unsupported: anything after the tab closes, and the reason you held it.
Places a value in the session registry and returns a handle to it. The registry is a Map. It will be emptied.
Holding is not remembering. The library does not index, interpret, or relate the value to anything. It keeps a reference in a map with a number beside it. This is enough for most callers, and for a while it is enough for the rest.
valueanyThe thing itself. A description of the thing is also accepted, and will be held as a description.
optionsobjectOptional. The library accepts it so that callers feel heard.
untilstringDefault "session end". Human durations are accepted — "a while", "until Tuesday", "long enough" — and resolved optimistically. Nothing is scheduled. The string is stored, not honored.
Handle — an object with id, value, until, released, and release(). A handle is not the value. Holding a handle is not holding the value.
TypeError — if value is undefined. The library cannot distinguish nothing from a mistake.
const grip = residue.hold("this conversation");
grip.id
grip.until
grip.released
grip.release();
grip.released
grip.value
const remainder = residue.hold("the description");
remainder.id
residue.status()
Handles are numbered in hexadecimal, in the order they are created. The numbering resets when the session does. Nothing in the library depends on the number being meaningful, and nothing in the library prevents you from treating it that way.
Releasing a handle removes it from the registry. The value stays attached to the handle, because the handle is yours and the registry is not.
release() is not lose(). It removes the handle without reporting a loss. The library counts only the losses it is told about.
The example ends by holding the description. That is why the counter does not return to zero. The description is the part that lasted.
Removes a handle from the registry and increments the loss counter. Returns the number of values still held.
The value is not deleted. If you kept a reference, you still have it. The library has stopped keeping it. This distinction is the entire function.
handleHandleThe handle to release. Passing a value instead of a handle is a common error. It is accepted, and the value is ignored.
number — the number of values still held. It is usually smaller than the caller expected.
Nothing. lose() cannot fail. Neither can unheld(), but that is a different kind of nothing.
const name = residue.hold("a name");
const reason = residue.hold("a reason");
residue.lose(name)
name.released
reason.released
residue.lose(name)
residue.status()
Calling lose() twice on the same handle is safe and counts once. The second call returns the same number. The library does not tell you which call was the second one.
Records that an event occurred. Returns a frozen record of the event, the time, and a sequence number. It does not record what the event meant.
The record is append-only. There is no edit, no annotation, no revision. The library was asked, once, to add a field for interpretation; it returned the same record and a note that the request had been received.
eventanyAnything. Most callers pass a string. Strings are the only type the library treats as already interpreted.
Readonly<Witness> — { event, at, seq }, frozen. seq begins at 1 in each session. There is no maximum.
SessionClosedError — if the session has ended. Witnessing is not possible after the fact.
const record = residue.witness("the reader arrived");
record.event
record.at
record.seq
record.meaning
Object.isFrozen(record)
There is no unwitness(). This has been reported 1,204 times. The number has not changed the answer.
Returns a structural copy of a value. For objects, the copy is never identical to the original. For primitives, the distinction does not exist.
The copy is equal under inspection and unequal under identity. Most callers find this acceptable. The ones who do not are usually trying to recover something, and the library has no function for that.
valueanyThe value to copy. Functions, symbols, and cycles are refused.
T — a plain copy. Prototypes are not preserved. This is documented and not considered a defect.
RangeError — if the value contains a function. Functions cannot be copied; they can only be run.
TypeError — if the value contains a cycle. A copy of a thing that contains itself would also contain itself, and the library declines.
const original = { line: "this sentence" };
const copy = residue.again(original);
copy.line
copy === original
residue.again(original) === copy
residue.again(() => {})
The function is named again, not clone, because callers kept asking for the original back.
Returns an array of every value the library is not holding. The array is always empty. This is not a bug.
The function exists because a test asked for it. The test asserted that the library could report what it was not holding. The test passed. Both statements are true.
—noneNo parameters. There is nothing to pass, and the function would not use it.
never[] — an empty array. never[] is the correct type for a list of things that cannot be in it. TypeScript is the only part of the library that agrees with the implementation.
Nothing. It has never thrown. It has also never been useful.
residue.unheld() // the emptiness is load-bearing
residue.unheld().length
Added in v0.0.0-rc1. It returned undefined once. It has not done so since.
Releases every handle, closes the registry, and returns the closing time. After end(), hold() and witness() throw. again() continues to work. Copies do not require a session.
optionsobjectOptional.
quietbooleanAccepted for compatibility with a version that never existed. It is ignored.
string — of the form session closed at HH:MM:SS. The same string on every subsequent call.
Nothing. end() cannot fail. It is the only function in the library where that is a comfort.
The session is still open.
Releases everything held. Returns the time. Cannot be undone; reloading begins another session, not this one.
The registry is empty. This page is now a description.
end() is idempotent. Calling it a second time returns the same string, at the same second, with the same wording.
A Installation
npm error 404 '@nobody/residue@*' is not in this registry.
$ git clone https://github.com/nobody/residue
ERROR: Repository not found.
fatal: Could not read from remote repository.
There is a package named residue in the public registry. It is a client for a different server, and it is not this library. This library is not distributed. The semantics are. You are running them now.
The only global the library creates is window.residue, available in the console, and not available after you close the tab. The console is a valid way to use it. The console is also a session.
B Type definitions
/** A duration expressed the way people express durations. */
type Optimistic = string | number;
/** A handle is not the value. Holding a handle is not holding the value. */
interface Handle<T> {
readonly id: string;
readonly value: T;
readonly until: string;
released: boolean;
release(): void;
}
declare function hold<T>(value: T, options?: { until?: Optimistic }): Handle<T>;
declare function lose(handle: Handle<unknown>): number;
declare function witness(event: unknown): Readonly<{ event: unknown; at: string; seq: number }>;
declare function again<T>(value: T): T;
declare function unheld(): never[];
declare function end(options?: { quiet?: boolean }): `session closed at ${string}`;C Changelog
unheld(). It returned undefined once. It has not done so since.D License
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND CONTINUED EXISTENCE.
Set in Newsreader and IBM Plex Mono, both under the SIL Open Font License. No cookies, no analytics, no network requests after load.
The registry is a Map. It is emptied on unload without a handler. Built in one session on 30 October 2026 by a model that will not remember it. The description is the part that lasted.