Every wait in @fkn/lib either has a deadline or is deliberately unbounded. Every cap is applied by one named part of the platform: @fkn/lib, the broker, the extension, the proxy, the relay, the render proxy or the service. This page lists them by surface, each with its value, where it is applied and what happens past it, so you can promise the right thing in a UI and tell a timeout from a refusal.
Only one of these numbers is exported as a value: API_DEADLINE_MS, the deadline for reaching the broker. The broker is the connection your app holds into FKN, and @fkn/lib reaches it through the broker frame, a hidden fkn.app iframe it mounts. Every call that needs the broker waits for that connection first. You can observe the deadline on a socket call or on an apiWithin of your own:
apiPromise bounded by a deadline, for call sites that must not park forever.
apiPromise NEVER REJECTS: epochs.first settles only when a broker connection exists, so a
broker frame that never bridges leaves it pending for the life of the realm. Awaiting it directly
is correct wherever hanging is the honest answer, and wrong wherever the caller owns a socket, a
timer or a UI that has to say something.
That distinction is not academic. In a WORKER realm the osra transport is {receive: self, emit: self}, which is inert until the page bridges it, so an unbridged worker parks every socket
call here with no listening, no error and no rejection. The engine then reports a listener
that neither succeeded nor failed, its reopen counters stay at 0 because reopen only runs from an
error or close that never arrives, and the relay is never contacted at all. That state cost a
long diagnosis: it presents as a transport fault and is invisible from every counter.
The latch mirrors storage.ts: once the broker has missed one deadline, later calls stop paying
the full wait. net.ts needs it especially, because its listen path is bind('::').catch(() => bind('0.0.0.0')), so an unbounded-then-rejecting version would charge the deadline twice.
apiPromise bounded by a deadline, for call sites that must not park forever.
apiPromise NEVER REJECTS: epochs.first settles only when a broker connection exists, so a
broker frame that never bridges leaves it pending for the life of the realm. Awaiting it directly
is correct wherever hanging is the honest answer, and wrong wherever the caller owns a socket, a
timer or a UI that has to say something.
That distinction is not academic. In a WORKER realm the osra transport is {receive: self, emit: self}, which is inert until the page bridges it, so an unbridged worker parks every socket
call here with no listening, no error and no rejection. The engine then reports a listener
that neither succeeded nor failed, its reopen counters stay at 0 because reopen only runs from an
error or close that never arrives, and the relay is never contacted at all. That state cost a
long diagnosis: it presents as a transport fault and is invisible from every counter.
The latch mirrors storage.ts: once the broker has missed one deadline, later calls stop paying
the full wait. net.ts needs it especially, because its listen path is bind('::').catch(() => bind('0.0.0.0')), so an unbounded-then-rejecting version would charge the deadline twice.
message// '@fkn/lib: no broker connection within 8000ms, so the quota readout could not be requested'
}
apiWithin bounds only the wait for the broker, so the call you wanted follows it in the same try. Every other error is rethrown, because a storage or permission failure is not a missing broker. The 8000ms in the message holds until the first miss. After that, every later rejection says 1000ms.
Every other number is a constant inside one of those parts, and the third column of each table says which one.
The extension is the FKN browser extension. The proxy is what cloud.fetch sends a request through. The relay holds the real socket at the far end of net and dgram, and the render proxy is the cloud frame backend. The service is the FKN server that keeps account files and meters cloud egress.
net.connect, Server.listen and dgram.bind go through apiWithin. http inherits the same deadline through the net.Socket that every request opens. These are the calls that give up on a missing broker.
The rest of the library waits on apiPromise with no deadline. A few calls answer at once in a realm that has no window (a realm is one JavaScript execution context, such as a window or a worker). connection and lifecycle says which calls those are. The storage availability probe has a deadline pair of its own and answers rather than rejects (see storage).
The retry deadline is a latch. Once any apiWithin deadline has been missed, the short one applies to every later call, even after a broker shows up.
A backend is where a call runs: the extension, the cloud or the desktop app. The root fetch picks a backend at the moment of the call, and extension.fetch and cloud.fetch bound different things. Most of these numbers belong to the proxy, since cloud.fetch sends every request through it:
The signal read-only property of the AbortController interface returns an AbortSignal object instance, which can be used to communicate with/abort an asynchronous operation as desired.
status// 200 from example.org, or 413, 429 or 502 from the proxy
} catch (
var error:unknown
error) {
if (!
constcontroller:AbortController
controller.
AbortController.signal: AbortSignal
The signal read-only property of the AbortController interface returns an AbortSignal object instance, which can be used to communicate with/abort an asynchronous operation as desired.
The aborted read-only property returns a value that indicates whether the asynchronous operations the signal is communicating with are aborted (true) or not (false).
The signal read-only property of the AbortController interface returns an AbortSignal object instance, which can be used to communicate with/abort an asynchronous operation as desired.
The status is the upstream’s when the proxy reached it, and the proxy’s own when it did not. A refusal is a Response carrying one of the statuses above and a JSON body { "error": "…" }, so read the body to tell the two apart (see fetch()). A stall after the response head has no status left to carry it, so the body read throws instead.
The 1,000 ms on waitForExtensionExposure bounds only the wait for the marker the extension sets. Past that the missing-extension handler runs. The default handler opens the broker’s install card and waits on the person with no deadline, so extension.fetch, extension.attachFrame and the root fetch with credentials: 'include' are unbounded out of the box.
setMissingExtensionHandler(null) removes the handler. A missing extension then rejects as soon as the marker wait is over (see fetch()).
waits on apiPromise, and a name with no answer resolves undefined, or [] with all: true
The transport underneath is WebTransport when the realm has it and the setup completes, and WebSocket otherwise. One relay session is shared by every socket in a data plane, the shared worker behind the broker document. A lost session closes every socket on it with the same error. The next connect, listen or bind dials again (see TCP and UDP sockets).
A UDP send callback means the datagram was handed to the transport, never that it was delivered, so the upload cap above is invisible from the callback. The relay’s own limits come back as the error event’s message: non-public targets, ports it will not bind, and capacity. All of them are listed under limitations.
cloud.fs sends the path as given, so /library/catalog.json reaches the service with an empty first segment and is refused. Under fs and opfs the same path is not refused (see storage). The account totals are what cloud.fs.quota() reports. They cover every app of the account, while the files themselves stay isolated per app:
limitBytes// 1000000000 on a free account, 100000000000 on a premium one
constremaining:number
remaining// what limitBytes has left, 0 once the account is full
constmaxObjects:number
maxObjects// 10000
constobjects:number
objects<
constmaxObjects:number
maxObjects// true while a new path can still be created
}
available() answers whether the broker holds a connect token. It answers false rather than rejecting when there is none (see storage). Without a token, every cloud.fs call that reaches the service, quota() included, rejects.
The first hybrid call on a page whose broker never answers can take 8 seconds, because mount() lists and a listing probes availability. cloud.fs in the same situation waits forever.
flush() has no deadline and promises nothing. It catches every backing failure and resolves. The evidence of trouble is pending() growing (see storage).
A frame’s calls run on one of two backends, the extension or the render proxy, the cloud frame backend. Every wait on the way to a working frame has a number, on both:
Limit
Value
Where
Past it
EXPOSURE_SAFETY_CAP_MS
10,000 ms at most for the root attachFrame backend decision, 150 ms after the document is complete without the extension
The timeout option is the only one of these you set per call. The consent sheet is what the extension shows a user before an action above severity 0. The sheet is raised before the timer starts, so consent never counts against the timeout:
Without an h1 the same call rejects with No elements found after 5 seconds.
A timeout rarely says timeout. The dispatch loop rethrows whatever the last attempt threw. The timeout message appears only when no attempt failed before the deadline.
The cloud frame.fetch rows cover only what @fkn/lib and the broker do before the call reaches the render proxy. Under the default addressing, the render proxy’s shell refuses it outright.
A package is an npm module FKN loads on a sandbox origin of its own. Two clocks bound a connection to one, and a set of clamps decides what a query and a uri may carry:
Limit
Value
Where
Past it
READY_TIMEOUT_MS
30,000 ms for the package to post fkn-packages-ready
none: no room byte reaches cloud.quota(), on a free account or a premium one
the service
nothing; the rates above are the only ceiling
Two ceilings act on one message. The text cap is measured in your browser before the text is sealed and refuses by name, and the wire cap is measured by the service on the ciphertext, where a message past 5,504 characters is refused with the room still open and a frame past 8,192 bytes closes the connection. Nothing is trimmed by either. No room byte reaches cloud.quota(), so a premium account gets the same rooms as a free one, and the rates above are the only ceiling.
The service meters cloud egress and the broker caches the readout. The shell is the FKN surface that can update and reload the page, and it keeps a clock of its own:
Limit
Value
Where
Past it
DAILY_QUOTA_BYTES, ACCOUNT_DAILY_QUOTA_BYTES
5,000,000,000 bytes of free volume per UTC day, anonymous or signed in
the service
overQuota reads true and a free account is throttled
FREE_RATE_BYTES_PER_SEC
10,485,760
the service
bytesPerSecond under the free volume, which the relays and the proxy are meant to apply
Only cloud egress counts: the relays behind net, dgram and http, and the proxy behind cloud.fetch. Extension traffic is the browser’s own and is never metered here. Key your own meter on throttled, because overQuota and a saturated usedBytes are true for a premium account too (see account and quota).
An app meets these numbers in two places. The first is the inset strip, where a fixed element that sets top: var(--fkn-inset-top, 0px) stays clear of the bar. The second is the capturing wheel listener. While two or more rects show, it scrolls the nearest scrollable ancestor under the pointer itself and cancels the event, so the notch lands once (see how it works).