Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Reference: External services

External services

How polls and max_concurrent: run

This is the detail behind Polls and a connection's max_concurrent:: what a poll declares, when it runs and what it keeps, and how the cap on calls at once is counted.

  • A poll's keys. Each entry of polls: in provider.jadpo is a block of call:, every: and emits:, each required and nothing else (package.provider_invalid). call: names one of the package's operations, public or not, that takes no input: and isn't changes: true, since a poll reads a feed that doesn't depend on the app's data (checking each of the app's records is the app's schedule calling the package), and whose output: is an Object read by the package's types, with no decode: of its own, as a queue's mapping is. every: is a duration of a minute or more: the tick runs once a minute.
  • What it emits. emits: names an Event of the package that is public, since only the app's subscribers hear it, and gives no wire_name:, which marks one the provider sends; it is the package's own, as one its action emits is, so a package that receives webhooks needs no wire_name: or occurred_at on it. Each of its fields is a field of the answer of the same name and type, and only those count when the runtime asks whether the answer changed: a time the answer was fetched, left out of the event, never does. An event of fields the answer hasn't is refused at emits:.
  • The connection's interval. polls: { <name>: <interval> } on a connection gives one of its package's polls another interval, a duration of a minute or more; a name the package's polls: hasn't is connection.key_invalid with the nearest, a shorter or unwritten interval connection.value_invalid, and polls: on a connection to a package with none connection.key_invalid.
  • Only while subscribed. A poll runs for each connection to its package while one of the app's subscribers listens for its event, and is left out of the plan otherwise, so nothing is called and no state kept. Its events are kept where the package's webhooks would be, in events: { store: } or the only store of the app's records (event.store_required at the connection otherwise), with jadpo_polls, by poll <connection>.<poll>: when it is next due, the digest of the event it last kept and when that changed.
  • Running it. On each tick, after the queues, one statement takes a poll that is due, or has never run, and moves it on by its interval, so of several instances only one calls it; one due later than a whole new interval away, since shortened, is due at once. The row keeps the interval it was last claimed under, jadpo_polls.every_ms, so only a poll claimed under another interval than the connection's now counts as shortened, and a second tick at the same moment, or an instance whose clock is behind, never calls the provider again within the interval. The column may be NULL, since a migration adds it to a table that may hold rows, and a row without it counts once as claimed under another interval. The operation is called as the runtime's own work, through the connection, with no request and no function of the package's, logged and retried as any call is. The event is made from the answer's fields, written with its codec and read back, and the SHA-256 of what is written compared with the last digest: the same keeps nothing; a change, or the first answer, keeps the event with a delivery for each subscriber and the digest in one transaction, the digest checked again there. A failed call keeps nothing, is reported by the tick under the poll's name, and is due again a minute later. A poll's first answer is emitted, so the app learns the current prices without waiting for them to change.
  • max_concurrent: in provider.jadpo is a whole number of at least 1 (package.provider_invalid), and a connection's may only lower it (connection.value_invalid past it); a connection may give one when its package gives none. The cap is per app instance: the process's, shared by its worker threads, or an isolate's on Workers, and a test's host keeps its own. A provider call takes one of its connection's slots for each attempt and gives it back once answered, so none is held while a retry waits; a further call waits for one, a route's within its deadline. A subscriber's delivery calling out through a connection whose slots are all out is given back as claimed, its attempt not counted, due again a second later for the next sweep; its wait counts towards within:, as a release after a shutdown does. Polls' calls wait as any call does.
  • On Workers the cap is counted per isolate: Workers runs many isolates, and a cap across them would need a Durable Object and a round trip per call. So the deploy report and jadpo check say "on Workers, per instance" (jadpo check's "On Workers" lists each capped connection a deployable built for Workers reaches, and jadpo build's calls line each Worker's); a provider that needs a hard global limit enforces it on its side, and its rate-limit answers already make calls back off.
  • Between an isolate's invocations a slot one invocation gives back is handed to the call of another that has waited longest, which finds it on its own timer every 10 ms rather than being woken by the first, since Workers runs each invocation's work in its own context. A call holding a slot says so every second, and one not heard from for five seconds is forgotten, as is a waiter that stopped looking, so an invocation Workers ended mid-call never keeps a slot. Natively the process's waiters are woken by the call that gives a slot back.
  • The surface. A poll's event is one of the package's public types and in its surface as any is; the polls themselves and max_concurrent: join the provider's entry once the surface tells an addition inside an entry from a breaking change, since any change to the provider's entry counts as breaking.

Markdown for agents: /docs/how-polls-and-max-concurrent-run.md.Built from Jadpo main at a707efb.