Skip to main content

REMEMBERED_JS

Constant REMEMBERED_JS 

Source
pub const REMEMBERED_JS: &str = "// The `remembered` placement: a signal whose store is the browser\'s own.\n//\n// `remembered` is to `client` what `durable` is to `server`. A `client`\n// cell is one tab\'s memory and dies with the tab; this one lives in\n// `localStorage`, so it survives a reload, it is shared by every tab of\n// the same browser on the same origin, and it is shared with nobody else.\n// There is no server in this file and no request: the value never leaves\n// the browser it was written in.\n//\n// # Its own module, and why\n//\n// \u{a7}16.3.1: a bundle ships nothing it does not use. A program that declares\n// no `remembered` state must not download a `localStorage` wrapper, and\n// `list.js`, `foreign.js` and `markup.js` are each here for the same\n// reason. `linked_runtime` adds this file exactly when the emitter reached\n// it, and `wire.js` with it \u{2014} see below.\n//\n// # Why `wire.js` and not `JSON.stringify`\n//\n// `localStorage` holds strings, and the values that go in are ZD values:\n// a `Map of Text to Whole`, a `List of Book`, a record. `JSON.stringify`\n// silently turns a `Map` into `{}` \u{2014} the bug `wire.js` was written to fix,\n// and its file comment tells the whole story. This is the same trip a\n// `durable` value makes to the store and back, so it is the same encoding,\n// from the same file. A second set of rules here is how the two halves of\n// a program come to disagree about what `{}` means.\n//\n// # The key, and why it is prefixed\n//\n// `zd:` plus the signal\'s source name. The survey that motivated this\n// placement found fifteen keys on one origin, all flat and unprefixed\n// (`music-open`, `snake-high-score`, `critterdex`), which is fine until\n// two things share an origin. The prefix is not a namespace the program\n// can choose, because a key it could choose is a key it could compute, and\n// a computed key is a way to read a cell the program did not declare.\n//\n// # What is deliberately not here\n//\n// **No `try`/`catch` around the read that swallows a parse failure into\n// the initial value.** A stored value that will not decode is a real\n// disagreement between what is on disk and what the program now expects,\n// and the honest ways to handle it are a migration or a versioned key \u{2014}\n// neither of which the language has yet. What this does instead is\n// narrow: it falls back only when the entry is *absent*, which is the one\n// case that is not a disagreement at all.\n\nimport { signal } from \'./signal.js\';\nimport { parse, stringify } from \'./wire.js\';\n\n/** Where a `remembered` signal\'s entry lives, given its source name. */\nexport function rememberedKey(name) {\n  return \'zd:\' + name;\n}\n\n/**\n * A signal backed by `localStorage`.\n *\n * Returns the same `[read, write]` pair `signal` does, so everything\n * downstream \u{2014} `derived`, `effect`, every binding in `dom.js` \u{2014} treats one\n * of these exactly as it treats a `client` cell. That is the point: the\n * placement changes where the value is kept and who else can write it, not\n * what a reader does with it.\n *\n * `initial` is the value on a browser that has never run this program.\n * It is not a default the read falls back to on every load: once an entry\n * exists, the entry is the value, which is what \"survives the reload\"\n * means.\n */\nexport function remembered(name, initial) {\n  const key = rememberedKey(name);\n  const store = storage();\n  const [read, write] = signal(load(store, key, initial));\n\n  // Another tab of the same browser wrote the same key. `storage` fires in\n  // every *other* document on the origin and never in the one that wrote,\n  // which is exactly the edge needed and no echo to suppress. Without it\n  // \"one value per browser\" would be false the moment a second tab is\n  // open, and the placement\'s whole claim with it.\n  if (store && typeof addEventListener === \'function\') {\n    addEventListener(\'storage\', (event) => {\n      if (!event || event.key !== key) return;\n      // A `null` newValue is the entry being removed \u{2014} by the visitor\n      // clearing site data, or by another script. The program\'s own\n      // starting value is what it had before any of this happened, so\n      // that is what it goes back to.\n      write(event.newValue === null ? initial : parse(event.newValue));\n    });\n  }\n\n  return [\n    read,\n    (next) => {\n      const value = typeof next === \'function\' ? next(read()) : next;\n      save(store, key, value);\n      return write(value);\n    },\n  ];\n}\n\n/**\n * The browser\'s `localStorage`, or `null` where there is none.\n *\n * Two hosts have none: the DOM shim the compiler\'s own tests render\n * against, and a browser in a mode where reading the property throws\n * rather than returning an object \u{2014} Safari with cookies blocked has done\n * this, and the access itself is what throws, so it cannot be checked with\n * `typeof`. In both cases a `remembered` signal degrades to a `client`\n * one: it works for the life of the tab and forgets on reload. That is the\n * one place this file guesses, and it guesses toward the program still\n * running.\n */\nfunction storage() {\n  try {\n    return typeof localStorage === \'undefined\' ? null : localStorage;\n  } catch (unavailable) {\n    return null;\n  }\n}\n\nfunction load(store, key, initial) {\n  if (!store) return initial;\n  const stored = store.getItem(key);\n  return stored === null ? initial : parse(stored);\n}\n\nfunction save(store, key, value) {\n  if (!store) return;\n  store.setItem(key, stringify(value));\n}\n";
Expand description

The remembered placement’s store: localStorage, as a signal.

Its own module for the reason foreign.js, markup.js and list.js are: a program that declares no remembered state must not download a store wrapper it never calls (§16.3.1). It imports signal.js and wire.js — the same encoding a durable value uses for the same trip, because JSON.stringify turns a Map into {} here exactly as it does there.