pub const STORE_JS: &str = "// Live sync: the browser half of `durable` placement.\n//\n// # The transport is a seam, and here is why\n//\n// Spec \u{a7}8.1 says holding a stream open is solved on every platform. It is\n// not. Checked against vendor documentation:\n//\n// Cloudflare Workers no documented duration limit; billed on CPU, so an\n// idle stream is nearly free. The best case.\n// Lambda, streaming 900 s hard ceiling, and the full duration is billed\n// even after the client disconnects.\n// Lambda, buffered no streaming at all \u{2014} the response is delivered\n// only once complete.\n// Lambda behind an ALB no streaming at all \u{2014} the ALB takes a JSON body,\n// rejects upgrades, and drops `Transfer-Encoding`.\n// Vercel 300 s Hobby, 800 s Pro.\n// Azure Functions contested: 230 s total, or a 4-minute *idle*\n// timeout a heartbeat defeats. Unverified either way.\n// Deno Deploy no documented timeout, but an isolate can be\n// evicted mid-stream at any moment.\n//\n// Two of those shapes cannot hold a stream at all, so a runtime that hard-\n// coded `EventSource` would simply not work on them. `subscribe` therefore\n// takes a transport, and two are provided: `streamTransport` over\n// `EventSource`, and `pollTransport` over `fetch`. They speak the *same*\n// protocol \u{2014} a cursor goes up, ordered events come down \u{2014} so the difference\n// between Cloudflare (never disconnects) and Lambda behind an ALB (cannot\n// connect) is a transport choice, not a second code path.\n//\n// # Resume is load-bearing, not polish\n//\n// Because the ceiling is 900 s on Lambda, the stream *will* be cut, on a\n// timer, in normal operation. Every event carries a sequence number and\n// every reconnection sends the last one seen \u{2014} `Last-Event-ID` for the\n// stream transport, `?since=` for the poll transport. That is what makes a\n// bounded stream behave like an unbounded one.\n//\n// When the server cannot prove it has the whole tail a client missed, it\n// says `resync` instead of guessing. Continuing silently is the dropped\n// update \u{a7}8.1 forbids, and it is invisible in testing because it only\n// happens after a real disconnection.\n\nimport { remoteCell } from \'./rpc.js\';\nimport { decode as decodeValue } from \'./wire.js\';\n\n/** Where the live-sync endpoints live. One place, so the shape is one decision. */\nexport function liveUrl(keys, cursor) {\n const query = new URLSearchParams();\n query.set(\'keys\', keys.join(\',\'));\n if (cursor !== null && cursor !== undefined) query.set(\'since\', String(cursor));\n return `/_zd/live?${query.toString()}`;\n}\n\nexport function pollUrl(keys, cursor) {\n const query = new URLSearchParams();\n query.set(\'keys\', keys.join(\',\'));\n if (cursor !== null && cursor !== undefined) query.set(\'since\', String(cursor));\n return `/_zd/poll?${query.toString()}`;\n}\n\n// --- the cells ------------------------------------------------------------\n\n/// Every durable key this page reads, and how to update it.\nconst cells = new Map();\n\n/**\n * A `durable` signal read from the browser.\n *\n * The same `Remote of T` a `server` signal gives, plus a registration: when\n * a write to `key` is announced, the announcement carries the value, so\n * this cell updates without a round trip. That is \u{a7}17.2.5 fatal 4\'s\n * `LiveValue` edge, and it is the whole reason two windows move together\n * rather than one of them moving a second later.\n */\nexport function durable(name, key, inputs) {\n const [read, apply, refetch] = remoteCell(name, inputs);\n let existing = cells.get(key);\n if (!existing) {\n existing = [];\n cells.set(key, existing);\n }\n existing.push({ apply, refetch });\n return read;\n}\n\n/** Which keys have a cell. This is what a subscription asks for \u{2014} never a\n * prefix, because the stores this has to run on do not have prefix watch. */\nexport function watchedKeys() {\n // `forEach` rather than `Array.from` \u{2014} see the engine note in `signal.js`.\n const keys = [];\n cells.forEach((_bound, key) => keys.push(key));\n return keys.sort();\n}\n\n/** Apply one announced write. Exported so a transport test can drive it. */\nexport function applyUpdate(key, value) {\n const bound = cells.get(key);\n if (!bound) return false;\n for (const cell of bound) cell.apply(value);\n return true;\n}\n\n/**\n * Re-read every cell.\n *\n * The answer to `resync`: the server could not prove it had the whole tail,\n * so nothing about the current values can be trusted and the honest move is\n * to ask again.\n */\nexport function resyncAll() {\n // `forEach` rather than `for\u{2026}of` \u{2014} see the engine note in `signal.js`.\n cells.forEach((bound) => {\n for (const cell of bound) cell.refetch();\n });\n}\n\n// --- the protocol ---------------------------------------------------------\n\n/**\n * Route one decoded event.\n *\n * Returns the cursor to resume from. Pure apart from the cell writes, so\n * both transports share it and cannot drift into two dialects.\n */\nexport function receive(event, cursor) {\n const seq = typeof event.seq === \'number\' ? event.seq : undefined;\n // A sequence number that does not advance is an event this client has\n // already seen. Resume is not exact: `Last-Event-ID` and `?since=` both\n // ask for \"everything after N\", and a server that cannot seek precisely\n // answers from a little earlier \u{2014} which is allowed, and is why the\n // protocol carries the number at all. Applying such an event replays a\n // value that has since been overwritten, so the page shows the older one\n // until something writes again. It is invisible in testing because it can\n // only happen after a real reconnection.\n const seen = seq !== undefined && typeof cursor === \'number\' && seq <= cursor;\n\n if (event.event === \'resync\') {\n // Never skipped. `resync` is the server saying it cannot prove it has\n // the tail this client missed, and re-reading is the answer whether or\n // not the number moved.\n resyncAll();\n return seen ? cursor : (seq ?? cursor);\n }\n if (event.event === \'update\') {\n if (seen) return cursor;\n applyUpdate(event.key, event.value);\n return seq ?? cursor;\n }\n // `ready` and anything a newer server invents: advance the cursor if it\n // carried one, and change nothing. An unknown event must not be an error\n // \u{2014} a browser holding a stale page open across a deploy would then break\n // instead of simply learning nothing new.\n return seen ? cursor : (seq ?? cursor);\n}\n\n// --- the two transports ---------------------------------------------------\n\n/**\n * Hold a stream open.\n *\n * `EventSource` reconnects on its own and replays `Last-Event-ID`, which is\n * exactly the resume protocol \u{2014} so on a platform that can hold a stream,\n * the 900-second cut is handled by the browser and costs one round trip.\n */\nexport function streamTransport(keys, cursor, onEvent) {\n const source = new EventSource(liveUrl(keys, cursor));\n const handle = (name) => (message) => {\n onEvent(decodeFrame(name, message.data, message.lastEventId));\n };\n for (const name of [\'update\', \'resync\', \'ready\']) {\n source.addEventListener(name, handle(name));\n }\n return () => source.close();\n}\n\n/**\n * Ask, repeatedly.\n *\n * The fallback for the two shapes that cannot stream at all \u{2014} Lambda in\n * buffered mode and Lambda behind an ALB \u{2014} and the cheaper choice anywhere\n * the full stream duration is billed whether or not anyone is listening.\n *\n * It is the same protocol with a zero-length stream: the cursor goes up in\n * the query string instead of a header, and the events come down in an\n * array instead of one at a time.\n */\nexport function pollTransport(keys, cursor, onEvent, options) {\n const wait = (options && options.interval) || 1000;\n // Resolved with `typeof` rather than named directly: a bare `fetch` in a\n // runtime without one is a `ReferenceError` at subscription time, which\n // would take down module evaluation \u{2014} and therefore the whole page \u{2014}\n // over a feature the page can simply do without.\n const fetchImpl =\n (options && options.fetch) || (typeof fetch === \'function\' ? fetch : null);\n if (!fetchImpl) {\n // Neither a stream nor a request. The page still works; it just will\n // not learn about another window\'s writes until something re-reads.\n return () => {};\n }\n const sleep = (options && options.sleep) || ((ms) => new Promise((r) => setTimeout(r, ms)));\n let live = true;\n let at = cursor;\n\n (async () => {\n while (live) {\n try {\n const response = await fetchImpl(pollUrl(keys, at));\n const events = await response.json();\n for (const event of events) {\n if (!live) return;\n at = onEvent(event);\n }\n } catch (error) {\n // A failed poll is not fatal: the next one carries the same cursor,\n // so nothing is lost by one round trip going missing. Throwing here\n // would end live sync for the life of the page over one dropped\n // packet.\n }\n if (!live) return;\n await sleep(wait);\n }\n })();\n\n return () => {\n live = false;\n };\n}\n\n/** Whether this runtime can hold a stream. */\nexport function canStream() {\n return typeof EventSource === \'function\';\n}\n\n/**\n * Start live sync.\n *\n * `transport` defaults to a stream where one is available and a poll where\n * it is not, which is the honest default: the capability differs by\n * platform and by deployment shape, and a page cannot know which it is\n * behind.\n */\nexport function subscribe(options) {\n const settings = options || {};\n const keys = settings.keys || watchedKeys();\n if (keys.length === 0) return () => {};\n\n let cursor = settings.since === undefined ? null : settings.since;\n const transport = settings.transport || (canStream() ? streamTransport : pollTransport);\n const onEvent = (event) => {\n cursor = receive(event, cursor);\n return cursor;\n };\n return transport(keys, cursor, onEvent, settings);\n}\n\n/**\n * One `event:`/`data:` pair, as an object both transports produce.\n *\n * Named `decodeFrame` rather than `decode` because `wire.js` exports a\n * `decode` too, and the two do different jobs at different layers: this\n * one turns an SSE frame into an event, that one turns JSON into a ZD\n * value. Two `decode`s one import apart is a name collision waiting for\n * whichever file gets flattened into the other\'s scope.\n */\nexport function decodeFrame(name, data, lastEventId) {\n let payload = {};\n try {\n payload = JSON.parse(data);\n } catch (error) {\n payload = {};\n }\n const seq =\n typeof payload.seq === \'number\'\n ? payload.seq\n : lastEventId === undefined || lastEventId === null || lastEventId === \'\'\n ? undefined\n : Number(lastEventId);\n // Decoded here rather than at the cell, because this is the one place\n // bytes become values \u{2014} and a `Map` pushed to a second window has to\n // arrive as a `Map`, not as the `{\"$map\":[...]}` it travelled as.\n //\n // A frame this runtime cannot decode becomes a `resync` rather than an\n // exception. The alternative is a throw out of an `EventSource` listener,\n // which nothing catches; and the alternative to *that* is applying a\n // value we could not read, which is the dropped update \u{a7}8.1 forbids.\n // Asking again is the only answer that is neither.\n let value = null;\n try {\n if (payload.value !== undefined) value = decodeValue(payload.value);\n } catch (error) {\n return { event: \'resync\', seq: Number.isFinite(seq) ? seq : undefined, key: undefined, value: null };\n }\n return {\n event: name,\n seq: Number.isFinite(seq) ? seq : undefined,\n key: payload.key,\n value,\n };\n}\n";Expand description
Live sync for durable placement, and the transport seam it needs.
Shipped only when the split found a durable key. It imports rpc.js,
which a program with a crossing already has.