import { DatasetStatusInfo } from '@elaraai/e3-api-client'; import { TreePath, DatasetStatus as PlatformDatasetStatus } from '@elaraai/e3-types'; /** * Timer abstraction the cache uses for periodic polling. Exists so * tests can drive the poll loop deterministically with a fake clock * instead of `setInterval` real-time. */ export interface Clock { /** Schedule `fn` to run every `ms` milliseconds. Returns a handle * whose `clear()` cancels future invocations. */ setInterval(fn: () => void, ms: number): { clear(): void; }; } /** Default {@link Clock} backed by `globalThis.setInterval`. */ export declare const realClock: Clock; /** * Adapter the cache uses for every server round-trip. Exists so tests * (and any non-e3 host) can inject a synthetic implementation; the * default wraps the `@elaraai/e3-api-client` module-level functions. * * The cache passes its own `apiUrl` / `repo` / `token` config to the * adapter only via the {@link createDefaultDatasetApi} factory — the * adapter itself doesn't see those values, so tests don't need to * scaffold dummy URLs. */ export interface DatasetApi { /** Fetch a dataset's content. `hash` is the server's content hash * when it exposes one (`null` otherwise) — the cache records it so * the next status poll doesn't refetch content it already has. */ get(workspace: string, path: TreePath): Promise<{ data: Uint8Array; hash: string | null; }>; set(workspace: string, path: TreePath, value: Uint8Array): Promise; launchDataflow(workspace: string): Promise; listRoot(workspace: string): Promise; listAt(workspace: string, path: TreePath): Promise; workspaceStatus(workspace: string): Promise<{ datasets: DatasetStatusInfo[]; }>; } /** * Build the default {@link DatasetApi} that talks to a real e3 server * via `@elaraai/e3-api-client`. Tests typically construct a * hand-rolled adapter instead. */ export declare function createDefaultDatasetApi(apiUrl: string, repo: string, getToken: () => string | null): DatasetApi; /** * Configuration for the {@link ReactiveDatasetCache}. * * Strictly cache-internal — credentials and server identity (apiUrl, * repo, token) live in the {@link E3Config} context exposed by * ``. The cache talks to the server only via the injected * {@link DatasetApi} adapter, so it has no opinion on transport. * * @property workspace - The workspace this cache instance renders * against. Used by `Data.bind` to scope cache keys; consumed by * bind-runtime + the Diff renderer via `getConfig().workspace`. */ export interface ReactiveDatasetCacheConfig { workspace?: string; } /** * Interface for the ReactiveDatasetCache. */ export interface ReactiveDatasetCacheInterface { /** Read a cached dataset value synchronously */ read(workspace: string, path: TreePath): Uint8Array | undefined; /** Check if a dataset is cached */ has(workspace: string, path: TreePath): boolean; /** * Get the platform status of a dataset — `unset` | `stale` | `up-to-date`. * Returns `unset` if we don't know yet (status hasn't been polled). * `write()` flips the local entry to `stale` immediately (optimistic). * The next poll updates from the server's authoritative status. */ getStatus(workspace: string, path: TreePath): PlatformDatasetStatus; /** Write a dataset value (async - mutates remotely) */ write(workspace: string, path: TreePath, value: Uint8Array): Promise; /** * Write a dataset value AND launch a dataflow execution to propagate the * change to downstream tasks. Use when a single user action both mutates * input data and should trigger downstream recomputation (e.g. a Slider's * `onChangeEnd`). Use plain `write` for high-frequency optimistic updates * that you don't want to drive the dataflow on every tick. */ writeAndStart(workspace: string, path: TreePath, value: Uint8Array): Promise; /** * Launch a workspace dataflow run WITHOUT writing anything first, so * downstream tasks recompute against the current dataset state. This is the * standalone half of {@link writeAndStart} — use it after an out-of-band * mutation (e.g. a record commit) or to drive an explicit "Run" affordance. * Fire-and-await: resolves once the server accepts the request, not when * the dataflow finishes. */ launchDataflow(workspace: string): Promise; /** Preload a dataset into cache */ preload(workspace: string, path: TreePath): Promise; /** List fields at a path */ list(workspace: string, path: TreePath): Promise; /** Set polling interval for a dataset */ setRefetchInterval(workspace: string, path: TreePath, intervalMs: number): void; /** * Stop polling a dataset previously registered with * {@link setRefetchInterval}. The workspace's shared poller stops * entirely once its last path is cleared — without this, a long-lived * session accumulates watched paths (and network traffic) forever. */ clearRefetchInterval(workspace: string, path: TreePath): void; /** * Force one immediate workspace-status poll, reconciling every watched * path's content against the server (hash-gated; only changed datasets * refetch). Use after an out-of-band server mutation (e.g. a record * mutation commit) so a watched dataset picks up the new bytes without * waiting for the standing poll interval. No-op if the workspace has no * active poller. Fire-and-forget; resolves when the poll settles. */ refresh(workspace: string): Promise; /** Subscribe to changes on a specific key */ subscribe(key: string, callback: () => void): () => void; /** Subscribe to all changes */ subscribe(callback: () => void): () => void; /** Get global snapshot version */ getSnapshot(): number; /** Get version for a specific key */ getKeyVersion(key: string): number; /** Set notification scheduler */ setScheduler(scheduler: ((notify: () => void) => void) | undefined): void; /** Batch multiple operations */ batch(fn: () => T): T; /** Get the configuration */ getConfig(): ReactiveDatasetCacheConfig; /** Clean up resources */ destroy(): void; } /** * Convert a dataset path to a string key for caching. * * @remarks * Dot-joined to match the server's own path encoding (workspaceStatus * reports `.a.b` paths) — field names containing `.` are ambiguous at * the e3 wire level itself, so the cache doesn't try to out-encode it. */ export declare function datasetPathToString(path: TreePath): string; /** * Create a cache key from workspace and path. */ export declare function datasetCacheKey(workspace: string, path: TreePath): string; /** * ReactiveDatasetCache manages dataset caching and reactivity. * * @remarks * Content bytes live in a `@tanstack/query-core` {@link QueryClient} * (structured query keys, fetch dedup + cancellation); statuses, content * hashes, write pipelines, the workspace-status poll loop, and the * string-keyed subscription shim are cache-owned. See the module remarks * for the write/poll race rules. * * This differs from raw `@elaraai/e3-api-client` dataset functions which * are for direct API calls without reactive binding or caching. */ export declare class ReactiveDatasetCache implements ReactiveDatasetCacheInterface { private destroyed; private api; private config; private readonly client; private knownHashes; private statuses; private writeEpochs; private writeChains; private writeBaselines; private pendingWriteCounts; private keySubscribers; private globalSubscribers; private version; private keyVersions; private workspacePollers; private readonly clock; private inFlightPolls; private batchDepth; private changedKeys; private scheduler; private flushScheduled; constructor(config: ReactiveDatasetCacheConfig, api: DatasetApi, clock?: Clock); /** Structured query key for a dataset's content. */ private contentKey; /** * Read the cache's own configuration. Server identity (apiUrl, * repo, token) is NOT here — read it from `` via * `useE3Config()` instead. */ getConfig(): ReactiveDatasetCacheConfig; /** * Read a dataset value synchronously from cache. */ read(workspace: string, path: TreePath): Uint8Array | undefined; /** * Check if a dataset is cached. */ has(workspace: string, path: TreePath): boolean; /** * Get the platform status of a dataset. Defaults to `unset` until the * first poll returns a server status. */ getStatus(workspace: string, path: TreePath): PlatformDatasetStatus; /** * Write a dataset value (async - mutates remotely). * * @remarks * Optimistic and synchronous: the bytes are readable via {@link read} * before this method's first await. Concurrent writes to the same key * are serialized server-side (issue order preserved); on failure, only * the newest write rolls the key back — to the last server-confirmed * baseline, not to a possibly-superseded optimistic intermediate. */ write(workspace: string, path: TreePath, value: Uint8Array): Promise; /** * Write a dataset value AND launch a workspace dataflow run so downstream * tasks pick up the change. The launch is fire-and-await: it returns once * the server has accepted the request (not when the dataflow finishes). * Polling continues to surface live status as tasks complete. */ writeAndStart(workspace: string, path: TreePath, value: Uint8Array): Promise; /** * Launch a workspace dataflow run without writing first. The standalone * half of {@link writeAndStart}; see the interface doc for semantics. */ launchDataflow(workspace: string): Promise; /** * Preload a dataset into cache. Concurrent preloads of the same key * share one fetch (query-core dedup); a write racing the fetch cancels * it, so a preload can never clobber optimistic bytes. */ preload(workspace: string, path: TreePath): Promise; /** * List fields at a path. Empty path lists workspace root. */ list(workspace: string, path: TreePath): Promise; /** * Set refetch interval for a dataset (polling). * * @remarks * Uses hash-based change detection for efficiency: * 1. Polls workspaceStatus to get dataset hashes (lightweight) * 2. Compares hashes to detect changes * 3. Only fetches full content when hash changes * * Multiple subscriptions to the same workspace share a single poller. * Pair with {@link clearRefetchInterval} on unmount so the poller can * stop when nothing is watching. */ setRefetchInterval(workspace: string, path: TreePath, intervalMs: number): void; /** * Stop polling a dataset; the workspace poller is torn down when its * last watched path is cleared. */ clearRefetchInterval(workspace: string, path: TreePath): void; refresh(workspace: string): Promise; /** * Poll workspace status and reconcile the cache with the server. * Concurrent calls for the same workspace dedupe to a single fetch. */ private pollWorkspaceStatus; private doPollWorkspaceStatus; /** * Convert a path string back to TreePath. */ private stringToPath; /** * Subscribe to changes on a specific key or all changes. */ subscribe(callback: () => void): () => void; subscribe(key: string, callback: () => void): () => void; /** * Get global version for useSyncExternalStore. */ getSnapshot(): number; /** * Get version for a specific key. */ getKeyVersion(key: string): number; /** * Set scheduler for deferred notifications. */ setScheduler(scheduler: ((notify: () => void) => void) | undefined): void; /** * Batch multiple operations. */ batch(fn: () => T): T; /** * Notify subscribers of a change. */ private notifyChange; /** * Flush pending notifications. */ private flush; private doFlush; /** * Cleanup resources. After `destroy()` returns, in-flight fetches, * polls, and writes that are still mid-await won't write to the cache — * `this.destroyed` short-circuits their post-await branches, and the * query client's in-flight fetches are torn down with it. */ destroy(): void; } /** * Create a new {@link ReactiveDatasetCache}. The caller is responsible * for constructing a {@link DatasetApi} adapter; in a React tree the * `` builds one from the surrounding * ``. Tests inject a fake `clock` to drive polling * deterministically. */ export declare function createReactiveDatasetCache(config: ReactiveDatasetCacheConfig, api: DatasetApi, clock?: Clock): ReactiveDatasetCache; //# sourceMappingURL=dataset-store.d.ts.map