# Storage

> lifecycle.storage — store-wide session and local key-value storage for section scripts, with expiry, quotas and change events.

Canonical URL: https://77865786950d:3005/scripting/storage

Section scripts cannot use `localStorage` or `sessionStorage` directly: the
policy blocks both (`storage.local`, `storage.session`). Use
`lifecycle.storage` instead. It is safe to call anywhere and never throws.

```js
const seen = lifecycle.storage.local.get("promo.seen", false);
if (!seen) {
  banner.hidden = false;
  lifecycle.storage.local.set("promo.seen", true, { ttlDays: 7 });
}
```

## Areas

| Area | Lifetime |
|---|---|
| `lifecycle.storage.session` | This visit (the browser tab) |
| `lifecycle.storage.local` | Across visits, until removed or expired |

Keys are **shared across the whole store**: every section, cart module and
island on the store's pages reads and writes the same keys. Namespace keys
yourself, e.g. `quiz.step` or `promo:summer.seen`.

Values are stored as JSON in the browser under an `lx:s:` prefix (so
`promo.seen` is stored as `lx:s:promo.seen`). Islands use the same storage;
CollectionSwipe, for example, records its tutorial under
`collection-swipe:tutorial`.

## lifecycle.storage.session.get / local.get

```ts
get(key: string, fallback?: unknown): unknown
```

The stored value, or `fallback` when the key is missing, expired, invalid or
unreadable. An expired entry is removed when read.

## lifecycle.storage.session.set / local.set

```ts
set(key: string, value: unknown, options?: { ttlDays?: number }): boolean
```

Stores a JSON-serialisable value. Returns `true` on success.

- `ttlDays` (a positive number, fractions allowed) makes the entry expire.
  Without it, the entry lasts for the area's lifetime.
- Returns `false` and logs a `[lx storage]` console warning when the key is
  invalid, the value is `undefined` or not serialisable, the value is over
  8 KB, the area would exceed 64 KB, or the browser refuses the write.
- A successful write fires a `lx:storage` event (see `onChange`).

## lifecycle.storage.session.remove / local.remove

```ts
remove(key: string): void
```

Deletes the key and fires a change event. Invalid keys are ignored.

## lifecycle.storage.session.keys / local.keys

```ts
keys(): string[]
```

Every Lexsis storage key in the area, without the `lx:s:` prefix.

## lifecycle.storage.onChange

```ts
lifecycle.storage.onChange(
  callback: (change: { area: "session" | "local"; key: string }) => void,
): () => void
```

Calls back after any write or remove from a section, cart module or island on
this page, and for `local` keys changed in another tab on the same domain.
Returns a function that unsubscribes; it is also removed when the section
stops. Read the new value with `get`.

```js
lifecycle.storage.onChange(({ area, key }) => {
  if (area === "local" && key === "wishlist") renderWishlist();
});
```

## Keys and limits

| Rule | Value |
|---|---|
| Key format | 1–64 characters of letters, digits, `_`, `.`, `:` or `-`, starting with a letter or digit |
| Value size | 8 KB per value (JSON, including expiry) |
| Area size | 64 KB per area across all Lexsis keys |
| Prefix | `lx:s:` (added for you) |

When the browser blocks storage (some private modes and in-app webviews),
both areas fall back to memory for the current page, so reads and writes still
work but nothing persists.

The MCP validator warns about literal keys that break the key format, e.g.
`lifecycle.storage.local.set("My Key!", …)`. Computed keys are checked when
the call runs.

Storage is readable by any script on the page and by the shopper. Don't store
secrets or personal data you wouldn't put in a cookie.
