/** * External-store contract (spec: "External-Store Contract — `getStore` & * `useSyncExternalStore`") — the framework-free `{subscribe, getSnapshot}` * surface `MapController.getStore(token)` hands to React's * `useSyncExternalStore`, MobX autoruns, Redux listeners, Zustand mirrors, * and any future framework adapter. One contract, N consumers. Also home to * the shared settle/burst-origin tracker both front-ends' "camera settled" * signals ride — one settle contract, two front-ends, one implementation. * * The load-bearing rule is CACHED IDENTITY: `getSnapshot()` returns the * SAME reference until that token's event fires (a fresh object per call * makes `useSyncExternalStore` loop forever — zustand v5's headline * breaking change was exactly this fix). Invalidation only marks dirty; * the next read rebuilds — a 60fps camera stream with no subscribers * builds nothing. * * Snapshots are PLAIN SERIALIZABLE DATA — no closures — so Redux devtools * and persistence middleware never choke on them. Rich reads (`project`, * `data()`, `stats()`) stay on `ctx`; `data:` exposes a VERSION STAMP, * not the rows (mirroring a 100k-row array into a store per tick is the * anti-pattern the Redux performance guidance warns about). */ /** * Who moved the camera: a pointer/wheel gesture on the canvas ("user") or * a camera API / action / transition frame ("programmatic"). The echo-loop * half of two-way store binding — a bridge writes back only user-originated * changes; the map's idempotent setters are the other half. */ export type ViewOrigin = "user" | "programmatic"; /** The per-token store — directly consumable by `useSyncExternalStore(store.subscribe, store.getSnapshot)`. */ export interface TokenStore { /** `cb` fires after this token's state changed (read the new snapshot inside it). Returns the unsubscribe function. */ subscribe(cb: () => void): () => void; /** Cached immutable snapshot — the SAME reference until the next change event for this token. */ getSnapshot(): T; } /** `viewport` store snapshot — CameraState plus bounds and the origin of the current settle burst. */ export interface ViewportSnapshot { longitude: number; latitude: number; zoom: number; pitch: number; bearing: number; /** [[west, south], [east, north]] — same shape as `ctx.viewport.bounds`. */ bounds: [[number, number], [number, number]]; /** * Burst-latched origin: "user" if any change since the last settle was a * gesture — so a drag's inertia tail (MapLibre inertial moves carry no * `originalEvent`) can't relabel the gesture mid-burst. Matches what the * settled signals (`om-view-changed`, `onViewChange`) report. */ origin: ViewOrigin; } /** `data:` store snapshot — a version stamp, not the rows (fetch rows via `ctx.data()`). */ export interface DataVersionSnapshot { /** Bumps on every data change for the layer (load, stream tick, poll refresh). 0 = never loaded / unknown layer. */ version: number; /** Current row count (0 for unknown layers). */ rows: number; } /** * The store-token roster — ONE source of truth shared by * `MapController.getStore` (throws otherwise) and the React adapter's * warn-skip, so the two can never disagree about which tokens are stores. */ export declare function isStoreToken(token: string): boolean; /** The per-token store table — memoized store objects (calling `get` twice for one token returns the identical store). */ export declare class TokenStoreRegistry { private readonly entries; /** Memoized per token — `build` is captured on first call and reused. */ get(token: string, build: () => T): TokenStore; /** Marks the token dirty and notifies its subscribers. Unknown/never-requested tokens no-op (nothing to invalidate). */ invalidate(token: string): void; /** Drop every entry and subscriber — controller teardown: a consumer that never unsubscribed must not pin the controller in memory. */ clear(): void; } /** Trailing debounce for the settled-camera signals — long enough to coalesce a pan/flight's per-frame churn, short enough that camera persistence feels immediate. */ export declare const VIEW_SETTLE_DEBOUNCE_MS = 150; /** * The settle/burst tracker behind `om-view-changed` and `onViewChange` — * shared by both front-ends so the debounce window and the burst-origin * rule ("user" if ANY change since the last settle was a gesture; an * inertia tail can't relabel the gesture) can never drift apart. */ export declare class ViewSettleTracker { private readonly onSettle; private timer; private burstHadUser; constructor(onSettle: (origin: ViewOrigin) => void); /** Record one viewport change and (re)arm the trailing settle timer. */ bump(origin: ViewOrigin): void; /** Burst-latched origin for snapshots taken mid-burst: the gesture wins over a trailing programmatic frame. */ latchedOrigin(lastFrameOrigin: ViewOrigin): ViewOrigin; cancel(): void; }