@gleanql/client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,954 @@
1
+ import { ArgMap, SchemaModel, SelectionSet } from "@gleanql/core";
2
+
3
+ //#region src/adapter.d.ts
4
+ interface GraphOperation<_TData = unknown, _TVariables = unknown> {
5
+ readonly name: string;
6
+ readonly kind: "query" | "mutation" | "subscription";
7
+ readonly document: string;
8
+ /** SHA-256 hex of `document` — the persisted-operation ID (present on compiled operations). */
9
+ readonly hash?: string;
10
+ }
11
+ interface GraphRequestContext {
12
+ readonly [key: string]: unknown;
13
+ }
14
+ interface GraphResult<TData> {
15
+ readonly data?: TData;
16
+ readonly errors?: ReadonlyArray<{
17
+ message: string;
18
+ }>;
19
+ }
20
+ interface GraphClientAdapter {
21
+ execute<TData, TVariables>(operation: GraphOperation<TData, TVariables>, variables: TVariables, context: GraphRequestContext): Promise<GraphResult<TData>>;
22
+ subscribe?<TData, TVariables>(operation: GraphOperation<TData, TVariables>, variables: TVariables, context: GraphRequestContext): AsyncIterable<GraphResult<TData>>;
23
+ }
24
+ interface FetchAdapterOptions {
25
+ readonly endpoint: string;
26
+ readonly fetch?: typeof fetch;
27
+ /** Build request headers from context (auth, shop domain, locale, ...). */
28
+ readonly headers?: (context: GraphRequestContext) => Record<string, string>;
29
+ /**
30
+ * Endpoint for subscriptions, consumed as a Server-Sent Events stream via the
31
+ * browser's `EventSource`. Defaults to `${endpoint}/stream`. A production app
32
+ * that prefers WebSockets can drop a `graphql-ws` adapter into the same
33
+ * `subscribe` seam instead.
34
+ */
35
+ readonly subscriptionEndpoint?: string;
36
+ /**
37
+ * Send operations BY HASH instead of by document (`extensions.persistedQuery.
38
+ * sha256Hash` — the Apollo APQ wire shape, which `createPersistedResolver`
39
+ * understands server-side). The document never rides the request, so the
40
+ * server can enforce a build-produced allowlist. If the server answers
41
+ * `PersistedQueryNotFound` (e.g. an APQ cache that hasn't seen the hash), the
42
+ * request retries ONCE with the full document so the server can register it.
43
+ * Operations without a `hash` fall back to sending the document.
44
+ */
45
+ readonly persisted?: boolean;
46
+ /** Observability hook: a persisted hash was unknown to the server and the document was re-sent (APQ register). */
47
+ readonly onPersistedRetry?: (operationName: string) => void;
48
+ }
49
+ /** Minimal fetch transport. Context is used only to build headers; it is never serialized into the body. */
50
+ declare function createFetchAdapter(options: FetchAdapterOptions): GraphClientAdapter;
51
+ //#endregion
52
+ //#region src/cache.d.ts
53
+ /**
54
+ * Graph cache with two storage identities (per the brief):
55
+ * - Normalized entity storage, keyed by `__typename + id`.
56
+ * - Operation/path storage, keyed by `root + args + path`, for objects with
57
+ * no `id`.
58
+ * Two query paths returning the same `__typename + id` resolve to one record,
59
+ * so an update through any path is visible through all of them.
60
+ */
61
+ /** A reference to a cached record: an identified entity or a path-anchored object. */
62
+ interface GraphRef {
63
+ readonly __typename?: string;
64
+ readonly id?: string | number;
65
+ /** Path identity, e.g. `Query.product(handle).featuredImage`. */
66
+ readonly path?: string;
67
+ }
68
+ type FieldValue = unknown;
69
+ type FieldLookup = {
70
+ readonly status: "ready";
71
+ readonly value: FieldValue;
72
+ } | {
73
+ readonly status: "missing";
74
+ };
75
+ declare class GraphCache {
76
+ private readonly maxRecords?;
77
+ private readonly records;
78
+ /**
79
+ * Optional LRU cap. The client cache accumulates entities across navigations; a
80
+ * long session would otherwise grow without bound. When set, the least-recently
81
+ * used records are evicted past the cap. Unset (default) = unbounded, so the
82
+ * server's per-request cache and existing callers are unchanged.
83
+ */
84
+ constructor(maxRecords?: number | undefined);
85
+ /**
86
+ * Reactivity substrate. Every write bumps `version` and notifies listeners, so
87
+ * UI can re-render after a mutation, refetch, or peer-tab/subscription update.
88
+ * `version` + `subscribe` are exactly the `useSyncExternalStore` contract.
89
+ */
90
+ private _version;
91
+ private readonly listeners;
92
+ /**
93
+ * Version counters for fine-grained reactivity, at two granularities. A component
94
+ * tracks the keys it read during render; its `useSyncExternalStore` snapshot is a
95
+ * digest of those keys' versions, so a global notify only re-renders the components
96
+ * whose keys actually changed (valtio's approach — no per-key subscription fan-out).
97
+ *
98
+ * - `recordVersions` bumps on ANY write to a record (record-level trackers, e.g.
99
+ * `usePaginated` watching a connection).
100
+ * - `fieldVersions` bumps only the written field (field-level trackers, e.g.
101
+ * `useGlean`, so reading `product.title` ignores a write to `product.views`).
102
+ *
103
+ * The global `version`/`subscribe` stay the notify channel; both granularities are
104
+ * resolved through {@link trackedVersion}.
105
+ */
106
+ private readonly recordVersions;
107
+ private readonly fieldVersions;
108
+ /** Current version of a record (0 if never written). */
109
+ recordVersion(key: string): number;
110
+ /** Current version of a single field on a record (0 if never written). */
111
+ fieldVersion(recordKey: string, fieldKey: string): number;
112
+ /** The opaque tracking key a field read records; resolve it with {@link trackedVersion}. */
113
+ fieldTrackingKey(recordKey: string, fieldKey: string): string;
114
+ /** Version of a tracked key: a bare record key, or `record\0field` for a single field. */
115
+ trackedVersion(trackingKey: string): number;
116
+ /** Bump a record's version + (optionally) one of its fields' versions. */
117
+ private bumpRecord;
118
+ private bumpField;
119
+ get version(): number;
120
+ subscribe(listener: () => void): () => void;
121
+ private bump;
122
+ /** Public notify: bump the version + run listeners (e.g. after `absorbRecords`). */
123
+ notify(): void;
124
+ /** Stable storage key for a ref: entity identity wins over path identity. */
125
+ recordKey(ref: GraphRef): string;
126
+ /**
127
+ * Reference-counted retention (Relay-style). A mounted reader retains the
128
+ * records it displays; retained records are never LRU-evicted and survive
129
+ * {@link gc}. The tracking hooks do this automatically — each component
130
+ * retains what it read while mounted — so `gc()` is safe to call any time
131
+ * (e.g. on navigation): it can only drop records nothing on screen reads.
132
+ */
133
+ private readonly retainCounts;
134
+ /** Pin a record. Returns the matching release; calling it twice is a no-op. */
135
+ retain(key: string): () => void;
136
+ isRetained(key: string): boolean;
137
+ /** The record key a tracked key belongs to (strips the `\0field` part, if any). */
138
+ trackedRecordKey(trackingKey: string): string;
139
+ /**
140
+ * Generation clock for staleness-aware GC. The glue advances it on each page
141
+ * navigation; every read/write/retain stamps the record with the current
142
+ * epoch. "Unretained" alone is NOT a reason to drop data (a back-navigation
143
+ * should hit a warm cache) — `gc({ keepEpochs })` drops only records that are
144
+ * unretained AND haven't been touched for that many generations.
145
+ */
146
+ private epoch;
147
+ private readonly lastActive;
148
+ /** Advance the generation clock (call on navigation). Returns the new epoch. */
149
+ advanceEpoch(): number;
150
+ private stamp;
151
+ /**
152
+ * Drop unretained records; returns how many were dropped. Version counters
153
+ * survive, so if a dropped record is refetched its trackers still see
154
+ * monotonic versions.
155
+ *
156
+ * - `gc()` — drop EVERY unretained record (a full reset, e.g. logout).
157
+ * - `gc({ keepEpochs: N })` — drop only records also untouched for ≥ N
158
+ * generations (see {@link advanceEpoch}); recently-used data stays warm
159
+ * for back-navigation even though nothing on screen retains it.
160
+ */
161
+ gc(options?: {
162
+ keepEpochs?: number;
163
+ }): number;
164
+ hasRecord(ref: GraphRef): boolean;
165
+ /** Mark a key most-recently-used (Map keeps insertion order; re-insert to bump). No-op when unbounded. */
166
+ private touch;
167
+ /**
168
+ * Evict least-recently-used records past the cap (Map's first key is the
169
+ * oldest), skipping retained records — a record someone on screen reads is
170
+ * never the eviction victim, even if it's the coldest. If retained records
171
+ * alone exceed the cap, the cache temporarily runs over it.
172
+ */
173
+ private evict;
174
+ /** Get-or-create the record map for a storage key. */
175
+ private ensureRecord;
176
+ getField(ref: GraphRef, fieldKey: string): FieldLookup;
177
+ setField(ref: GraphRef, fieldKey: string, value: FieldValue): void;
178
+ /** Merge a flat record of fields into the entity/path record. */
179
+ merge(ref: GraphRef, fields: Readonly<Record<string, FieldValue>>): void;
180
+ /** Drop a whole record (mutation invalidation). */
181
+ invalidate(ref: GraphRef): void;
182
+ /** Drop a single field so the next read re-fetches it. */
183
+ invalidateField(ref: GraphRef, fieldKey: string): void;
184
+ /**
185
+ * Fold a serialized snapshot into THIS cache, field-by-field, WITHOUT replacing
186
+ * existing records and WITHOUT notifying. Returns whether anything was
187
+ * added/changed. The caller decides when to `notify()` — so a render-phase merge
188
+ * can write records (visible to synchronous reads) yet defer the subscriber bump
189
+ * to a commit-phase effect. Idempotent: re-absorbing the same snapshot is a no-op.
190
+ */
191
+ absorbRecords(snapshot: Record<string, Record<string, FieldValue>>): boolean;
192
+ /** Serialize the whole cache (for hydration). */
193
+ snapshot(): Record<string, Record<string, FieldValue>>;
194
+ static fromSnapshot(snapshot: Record<string, Record<string, FieldValue>>, maxRecords?: number): GraphCache;
195
+ }
196
+ //#endregion
197
+ //#region src/normalize.d.ts
198
+ /**
199
+ * Normalize a GraphQL JSON result into the cache.
200
+ *
201
+ * Every object selection includes `__typename` (and `id` when the type has one),
202
+ * so the result alone carries enough to choose an identity:
203
+ * - `__typename + id` -> normalized entity record, deduped across queries.
204
+ * - id-less object -> embedded under its *owning entity* at the field path
205
+ * since that entity (`Product:123.priceRange`), NOT the query path. So the same
206
+ * nested object reached through two different queries resolves to one record —
207
+ * update it once and every reader sees it, and a second query needn't refetch.
208
+ * - id-less with no entity ancestor (root objects) -> anchored at the operation
209
+ * path (`Query.search(q)`), which is the only correct identity for them.
210
+ * Scalars store inline; object fields store a `GraphRef`; lists store an array.
211
+ *
212
+ * `anchor` is the nearest owning entity's record key (or the root path); `field`
213
+ * is the path from that anchor.
214
+ */
215
+ /**
216
+ * Resolve an object's identity value from its `__typename`, or undefined when
217
+ * the object is id-less (and must be embedded). Defaults to the `id` field;
218
+ * supply a schema-derived resolver to key types by another field (`sku`, `slug`)
219
+ * or a composite.
220
+ */
221
+ type KeyOf = (typename: string, obj: Record<string, unknown>) => string | undefined;
222
+ declare function normalizeValue(cache: GraphCache, value: unknown, anchor: string, field: string, keyOf?: KeyOf, seen?: WeakSet<object>): FieldValue;
223
+ /** Seed an operation result; returns each root field's ref for reading. */
224
+ declare function seedResult(cache: GraphCache, data: Readonly<Record<string, unknown>>, options?: {
225
+ rootPath?: string;
226
+ keyOf?: KeyOf;
227
+ }): Record<string, FieldValue>;
228
+ //#endregion
229
+ //#region src/runtime.d.ts
230
+ /**
231
+ * Suspense-aware graph runtime.
232
+ *
233
+ * A field read is synchronous on a cache hit. On a miss it enqueues the missing
234
+ * (ref, field), creates exactly one cached promise for it, and throws that
235
+ * promise (the Suspense contract). Multiple misses in the same tick batch into
236
+ * a single `fetchMissing` call. Re-reading a pending field throws the same
237
+ * promise — no duplicate request, stable across React render retries.
238
+ */
239
+ interface MissingFieldRead {
240
+ readonly ref: GraphRef;
241
+ readonly fieldKey: string;
242
+ }
243
+ interface MissingFieldResult {
244
+ readonly ref: GraphRef;
245
+ readonly fieldKey: string;
246
+ readonly value: FieldValue;
247
+ }
248
+ type MissingFieldMode = "allow" | "warn" | "error";
249
+ interface GraphRuntimeOptions {
250
+ /** Batched fetcher for fields not present in the seeded operation. */
251
+ readonly fetchMissing: (misses: readonly MissingFieldRead[]) => Promise<readonly MissingFieldResult[]>;
252
+ readonly cache?: GraphCache;
253
+ /** How to identify entities during normalization (defaults to the `id` field). */
254
+ readonly keyOf?: KeyOf;
255
+ /** Behavior when a field absent from the compiled operation is read. */
256
+ readonly unexpectedMissingField?: MissingFieldMode;
257
+ /** dev-only: warn with component/field context. */
258
+ readonly onWarn?: (message: string) => void;
259
+ /** Microtask scheduler (overridable in tests). */
260
+ readonly schedule?: (cb: () => void) => void;
261
+ /** Optional LRU cap for the cache (least-recently-used records evicted past it). */
262
+ readonly maxCacheRecords?: number;
263
+ }
264
+ declare class GraphRuntime {
265
+ private readonly options;
266
+ readonly cache: GraphCache;
267
+ private readonly pending;
268
+ private queue;
269
+ private flushScheduled;
270
+ constructor(options: GraphRuntimeOptions);
271
+ /** Synchronous on hit; throws a (cached) promise on miss. */
272
+ readField(ref: GraphRef, fieldKey: string, debug?: {
273
+ component?: string;
274
+ }): FieldValue;
275
+ /** Seed a record's fields (e.g. from the compiled operation result). */
276
+ seed(ref: GraphRef, fields: Readonly<Record<string, FieldValue>>): void;
277
+ /** Normalize a full operation result into the cache; returns root refs. */
278
+ seedResult(data: Readonly<Record<string, unknown>>, options?: {
279
+ rootPath?: string;
280
+ }): Record<string, FieldValue>;
281
+ /**
282
+ * Low-level pagination primitive: append a freshly-fetched page onto a cached
283
+ * connection. Normalizes the page's `nodes` and concats them after the existing
284
+ * ones, and (if present) replaces `pageInfo`. Every reader of the connection
285
+ * re-renders with the longer `nodes` array. This makes no assumptions about HOW
286
+ * the page was fetched or which cursor convention the schema uses — the app fetches
287
+ * the next page however it likes (the connection's ref is available via
288
+ * `selectionOf(value)`), then hands the page object here to merge it in.
289
+ */
290
+ appendConnection(connectionRef: GraphRef, page: Record<string, unknown>, mergeRefs?: (existing: readonly FieldValue[], incoming: readonly FieldValue[]) => readonly FieldValue[]): void;
291
+ /** Invalidate a record (e.g. after a mutation) and clear its pending reads. */
292
+ invalidate(ref: GraphRef): void;
293
+ /** Serialize the cache for hydration across the server/client boundary. */
294
+ snapshot(): Record<string, Record<string, FieldValue>>;
295
+ /**
296
+ * Fold a snapshot into the live cache (write only, no notify); returns whether
297
+ * anything changed. Use for a per-navigation merge where the notify is deferred
298
+ * to a commit-phase effect (see `serialize.ts#absorbHydrationPayload`).
299
+ */
300
+ absorbRecords(snapshot: Record<string, Record<string, FieldValue>>): boolean;
301
+ /** Notify subscribers after one or more `absorbRecords` calls. */
302
+ notify(): void;
303
+ /** Convenience: absorb a snapshot and notify if it changed (non-React callers). */
304
+ absorb(snapshot: Record<string, Record<string, FieldValue>>): boolean;
305
+ static hydrate(snapshot: Record<string, Record<string, FieldValue>>, options: Omit<GraphRuntimeOptions, "cache">): GraphRuntime;
306
+ private reportMiss;
307
+ private scheduleFlush;
308
+ private flush;
309
+ /** Stable key for a pending (ref, field) read — also the `invalidate` prefix base. */
310
+ private pendingKey;
311
+ private makeDeferred;
312
+ }
313
+ //#endregion
314
+ //#region src/route.d.ts
315
+ /**
316
+ * Framework-integration seam. A compiled operation (the artifact the compiler
317
+ * emits) plus a client adapter and a request context is enough to drive a
318
+ * route: compute variables, execute, seed the cache. A framework adapter
319
+ * (RWSDK first) answers "which operation for this entrypoint?" and "how do I
320
+ * build the request context?".
321
+ */
322
+ interface CompiledOperation<RouteContext = unknown, TVariables = Record<string, unknown>> {
323
+ readonly name: string;
324
+ readonly kind: "query" | "mutation" | "subscription";
325
+ readonly document: string;
326
+ readonly hash?: string;
327
+ readonly variables: (ctx: RouteContext) => TVariables;
328
+ readonly readMap?: Record<string, readonly string[]>;
329
+ /** Merged selection tree; enables cache-first resolution when present. */
330
+ readonly selection?: SelectionSet;
331
+ }
332
+ interface RunRouteOptions {
333
+ /**
334
+ * Cache-first: if the cache already satisfies the operation's full selection,
335
+ * skip the network. Defaults to true; pass false to always fetch (e.g. an
336
+ * explicit refresh that must hit the server).
337
+ */
338
+ readonly cacheFirst?: boolean;
339
+ }
340
+ interface RunRouteResult<TVariables> {
341
+ readonly variables: TVariables;
342
+ readonly roots: Record<string, FieldValue>;
343
+ readonly errors?: ReadonlyArray<{
344
+ message: string;
345
+ }>;
346
+ }
347
+ /** Execute a compiled operation and seed the runtime cache (steps 2–5 of the route flow). */
348
+ declare function runRoute<RouteContext, TVariables extends Record<string, unknown>>(args: {
349
+ operation: CompiledOperation<RouteContext, TVariables>;
350
+ routeContext: RouteContext;
351
+ adapter: GraphClientAdapter;
352
+ context: GraphRequestContext;
353
+ runtime: GraphRuntime;
354
+ options?: RunRouteOptions;
355
+ }): Promise<RunRouteResult<TVariables>>;
356
+ /**
357
+ * Re-run an operation against the network, bypassing cache-first, and re-seed.
358
+ * The re-seed writes through the cache, bumping its version and notifying
359
+ * subscribers — so a `useSyncExternalStore` (`cache.subscribe`) re-renders the
360
+ * UI with the fresh data. Use for an explicit "Refresh" / post-mutation refetch.
361
+ */
362
+ declare function refetch<RouteContext, TVariables extends Record<string, unknown>>(args: {
363
+ operation: CompiledOperation<RouteContext, TVariables>;
364
+ routeContext: RouteContext;
365
+ adapter: GraphClientAdapter;
366
+ context: GraphRequestContext;
367
+ runtime: GraphRuntime;
368
+ }): Promise<RunRouteResult<TVariables>>;
369
+ //#endregion
370
+ //#region src/cache-resolve.d.ts
371
+ /** Record each root field's resolved ref so a later run finds the entity without a fetch. */
372
+ declare function persistRootLinks(cache: GraphCache, selection: SelectionSet, vars: Record<string, unknown>, roots: Record<string, FieldValue>, rootPath?: string): void;
373
+ interface CacheResolution {
374
+ readonly covered: boolean;
375
+ readonly roots: Record<string, FieldValue>;
376
+ }
377
+ /**
378
+ * Try to satisfy an operation entirely from cache. `covered` is true only when
379
+ * every root link exists and every selected field beneath it is present.
380
+ */
381
+ declare function resolveFromCache(cache: GraphCache, selection: SelectionSet, vars: Record<string, unknown>, rootPath?: string): CacheResolution;
382
+ //#endregion
383
+ //#region src/proxy.d.ts
384
+ /**
385
+ * Runtime graph proxies.
386
+ *
387
+ * The compiler statically infers what fields a route needs; this layer is what
388
+ * makes ordinary reads (`product.title`, `product.featuredImage?.url`,
389
+ * `collection.products({ first: 12 }).nodes`) actually *execute* at runtime.
390
+ *
391
+ * A graph value is a Proxy over a cache `GraphRef`. Property access routes
392
+ * through the Suspense-aware runtime:
393
+ * - scalar field -> `runtime.readField(ref, key)` (sync hit, or throws a promise)
394
+ * - object field -> the stored `GraphRef`, re-wrapped as a child proxy
395
+ * - list field -> an array of child proxies/scalars
396
+ * - callable field -> a function `(args) => value` (field arguments)
397
+ *
398
+ * The proxy is intentionally transparent: parent components pass it as a normal
399
+ * prop, child components read fields off it, and nothing in userland sees a ref,
400
+ * a selection object, or a promise (the promise is thrown to Suspense).
401
+ */
402
+ /** Escape-hatch / brand keys exposed on every graph proxy. */
403
+ declare const GRAPH_REF: unique symbol;
404
+ declare const GRAPH_TYPE: unique symbol;
405
+ declare const GRAPH_TRAIL: unique symbol;
406
+ /** Install (or clear) the active read tracker. Returns the previous one. */
407
+ declare function setReadTracker(tracker: Set<string> | null): Set<string> | null;
408
+ /** One object-field hop from a Query root: the field name + the args it was called with. */
409
+ interface PathStep {
410
+ readonly name: string;
411
+ readonly args?: Record<string, unknown>;
412
+ }
413
+ /** The hidden selection token (brief: `product.selection`). */
414
+ interface GraphSelection {
415
+ readonly ref: GraphRef;
416
+ readonly type: string;
417
+ }
418
+ /** A value is a `GraphRef` if it carries entity identity or a path. */
419
+ declare function isGraphRef(value: unknown): value is GraphRef;
420
+ /** Plain runtime args (`{ first: 12 }`) -> IR `ArgMap`, reusing core's canonicalization. */
421
+ declare function toArgMap(args: Record<string, unknown> | undefined): ArgMap;
422
+ /**
423
+ * Candidate response keys for a (possibly callable) field, most-specific first.
424
+ * A callable field that coexisted with a differently-argued sibling was aliased
425
+ * by the merger (`url_transformMaxWidth300`); a lone callable field keeps its
426
+ * plain name. The proxy tries the alias key first, then the plain name, which
427
+ * is correct for both shapes without the proxy having to know about conflicts.
428
+ */
429
+ declare function responseKeyCandidates(name: string, argMap: ArgMap): readonly string[];
430
+ interface GraphBinding {
431
+ readonly schema: SchemaModel;
432
+ /** Resolve the active runtime lazily (per request on the server). */
433
+ readonly getRuntime: () => GraphRuntime;
434
+ /**
435
+ * Per-binding read tracker. When a `useGlean` render binds the graph with its own
436
+ * `affected` set, every proxy created from this binding records reads into THAT set
437
+ * — not the ambient global — so concurrent/interleaved renders can't misattribute.
438
+ * Absent (server / isomorphic accessor) → reads fall back to the ambient tracker.
439
+ */
440
+ readonly tracker?: Set<string>;
441
+ }
442
+ declare function createGraphProxy(binding: GraphBinding, ref: GraphRef, type: string, trail?: readonly PathStep[]): unknown;
443
+ /** Read the hidden selection token off any graph proxy. */
444
+ declare function selectionOf(value: unknown): GraphSelection | undefined;
445
+ /** Read the root→value path off any graph proxy (for `usePaginated`/`fetchMore`). */
446
+ declare function trailOf(value: unknown): readonly PathStep[] | undefined;
447
+ /**
448
+ * The runtime `graph` object: one callable per Query root field, each returning
449
+ * a graph proxy. Root values resolve to the ref the operation seeded (via the
450
+ * `roots` map) so reads hit the warm cache; an unseeded root falls back to a
451
+ * path-identity ref and suspends.
452
+ */
453
+ interface BoundGraph {
454
+ readonly [rootField: string]: (args?: Record<string, unknown>) => unknown;
455
+ }
456
+ interface BindGraphOptions {
457
+ readonly schema: SchemaModel;
458
+ readonly getRuntime: () => GraphRuntime;
459
+ /**
460
+ * Root field -> seeded ref, from `runRoute`/`seedResult`. A function form is
461
+ * resolved per call, so the bound graph can follow page-current roots that
462
+ * change across client navigations (the RSC hydrator updates them per nav).
463
+ */
464
+ readonly roots?: Record<string, FieldValue> | (() => Record<string, FieldValue> | undefined);
465
+ /**
466
+ * A read tracker scoping this binding's reads to one render (see `GraphBinding`).
467
+ * `useGlean` passes its render's `affected` set so attribution is fiber-local;
468
+ * the server/isomorphic accessor omits it.
469
+ */
470
+ readonly tracker?: Set<string>;
471
+ }
472
+ declare function bindGraph(options: BindGraphOptions): BoundGraph;
473
+ //#endregion
474
+ //#region src/scope.d.ts
475
+ /**
476
+ * Request-scoped runtime resolution.
477
+ *
478
+ * A module-level `import { graph } from "~/graph"` must resolve to *the runtime
479
+ * for the current request* on the server (concurrent requests must not share a
480
+ * cache) and to a single client runtime in the browser. `GraphScope` is the tiny
481
+ * seam that makes that possible without threading a runtime through every prop.
482
+ *
483
+ * On the server, a framework adapter (RWSDK) wraps request handling in
484
+ * `scope.run(active, fn)`. If the host exposes AsyncLocalStorage, concurrent
485
+ * requests are isolated automatically; otherwise the adapter should resolve the
486
+ * runtime from its own per-request context (`getGraph(requestInfo)`), which never
487
+ * relies on a shared mutable global. On the client, `scope.set(active)` installs
488
+ * the singleton after hydration.
489
+ */
490
+ interface ActiveGraph {
491
+ readonly runtime: GraphRuntime;
492
+ readonly graph: BoundGraph;
493
+ }
494
+ interface AsyncLocalStorageLike<T> {
495
+ getStore(): T | undefined;
496
+ run<R>(store: T, fn: () => R): R;
497
+ }
498
+ declare class GraphScope {
499
+ private singleton;
500
+ private als;
501
+ constructor(als?: AsyncLocalStorageLike<ActiveGraph>);
502
+ /**
503
+ * Attach an AsyncLocalStorage after construction — for isomorphic frameworks
504
+ * (e.g. React Router) where the *same* `graph` accessor module loads in both
505
+ * bundles: construct `new GraphScope()` in a universal, client-safe module (no
506
+ * `node:async_hooks`), then a server-only module calls `attachAls(...)` to
507
+ * upgrade it to per-request ALS isolation. `run`/`current` read `als`
508
+ * dynamically, so the upgrade takes effect immediately; the client keeps using
509
+ * the singleton set by `set()`.
510
+ */
511
+ attachAls(als: AsyncLocalStorageLike<ActiveGraph>): void;
512
+ /** The active graph, or throw a clear error if read outside any scope. */
513
+ current(): ActiveGraph;
514
+ /** Run `fn` with `active` as the request-scoped runtime (server). */
515
+ run<R>(active: ActiveGraph, fn: () => R): R;
516
+ /** Install the active graph as a singleton (client, post-hydration). */
517
+ set(active: ActiveGraph): void;
518
+ }
519
+ /**
520
+ * Pair a {@link GraphScope} with a zero-arg resolver — the framework-agnostic
521
+ * binding for the generated accessor. An app exports `activeGraph` and points
522
+ * `@gleanql/vite`'s `requestScope: { import: "activeGraph", from: "..." }` at it,
523
+ * then wraps server rendering in `scope.run(active, fn)` (or
524
+ * `integration.runInScope`). Pass an `AsyncLocalStorage` to isolate concurrent
525
+ * server requests; omit it for the client singleton.
526
+ *
527
+ * ```ts
528
+ * import { AsyncLocalStorage } from "node:async_hooks";
529
+ * export const { scope, activeGraph } = bindScope(new AsyncLocalStorage());
530
+ * ```
531
+ */
532
+ declare function bindScope(als?: AsyncLocalStorageLike<ActiveGraph>): {
533
+ scope: GraphScope;
534
+ activeGraph: () => ActiveGraph;
535
+ };
536
+ //#endregion
537
+ //#region src/mutation.d.ts
538
+ /**
539
+ * Mutations + invalidation.
540
+ *
541
+ * Reads were the first milestone; this is the write side. A mutation is run
542
+ * through the same client adapter as a query, and its result is normalized into
543
+ * the cache — so any entity it returns (`__typename + id`) updates *in place* and
544
+ * every read of that entity, through any path, reflects the change for free.
545
+ * That is the payoff of the normalized cache the brief asked for.
546
+ *
547
+ * On top of that the engine adds: GraphQL-style `userErrors`, optimistic writes
548
+ * with automatic rollback, and invalidation of affected graph values/roots.
549
+ */
550
+ /** A GraphQL `userErrors` entry (Shopify-style mutation payloads). */
551
+ interface UserError {
552
+ readonly field?: readonly string[];
553
+ readonly message: string;
554
+ readonly code?: string;
555
+ }
556
+ interface MutationResult<TData = unknown> {
557
+ readonly data?: TData;
558
+ /** Logical, per-mutation errors returned in the payload (not transport errors). */
559
+ readonly userErrors: readonly UserError[];
560
+ /** Transport/GraphQL execution errors. */
561
+ readonly errors?: ReadonlyArray<{
562
+ message: string;
563
+ }>;
564
+ /** True when there were no transport errors and no userErrors. */
565
+ readonly ok: boolean;
566
+ }
567
+ /**
568
+ * A reversible batch of cache writes. Optimistic updates record the prior value
569
+ * of every field they touch so the whole batch can be rolled back if the
570
+ * mutation fails (transport error or `userErrors`).
571
+ */
572
+ declare class MutationTransaction {
573
+ private readonly cache;
574
+ private readonly undo;
575
+ constructor(cache: GraphCache);
576
+ /** Optimistically write a field, remembering how to undo it. */
577
+ set(ref: GraphRef, fieldKey: string, value: FieldValue): void;
578
+ /** Roll back every write in reverse order. */
579
+ rollback(): void;
580
+ }
581
+ interface RunMutationOptions<TData = unknown> {
582
+ readonly operation: CompiledOperation<unknown, Record<string, unknown>> | {
583
+ readonly name: string;
584
+ readonly kind: "mutation";
585
+ readonly document: string;
586
+ };
587
+ readonly variables: Record<string, unknown>;
588
+ readonly adapter: GraphClientAdapter;
589
+ readonly context: GraphRequestContext;
590
+ readonly runtime: GraphRuntime;
591
+ /** Optimistically patch the cache before the request; rolled back on failure. */
592
+ readonly optimistic?: (tx: MutationTransaction) => void;
593
+ /** Apply the server result (e.g. prepend to a connection) after normalization. */
594
+ readonly update?: (data: TData, tx: MutationTransaction) => void;
595
+ /** Graph values / refs to invalidate on success (refetch on next read). */
596
+ readonly invalidate?: (data: TData) => ReadonlyArray<GraphRef | unknown>;
597
+ }
598
+ /**
599
+ * Execute a mutation, normalize its result into the cache, surface userErrors,
600
+ * and apply optimistic/invalidation policy. The returned promise never rejects
601
+ * for logical failures — inspect `ok`/`userErrors`/`errors`.
602
+ */
603
+ declare function runMutation<TData = Record<string, unknown>>(options: RunMutationOptions<TData>): Promise<MutationResult<TData>>;
604
+ /** Invalidate a record by graph value (proxy) or raw ref — next read re-fetches. */
605
+ declare function invalidateValue(runtime: GraphRuntime, value: GraphRef | unknown): void;
606
+ /** One rule for stringifying unknown errors, shared across hooks and transports. */
607
+ declare function errorMessage(error: unknown): string;
608
+ //#endregion
609
+ //#region src/mutator.d.ts
610
+ /** The minimal operation shape the mutator needs (any CompiledOperation satisfies it). */
611
+ interface MutationOperationLike {
612
+ readonly name: string;
613
+ readonly kind: "query" | "mutation" | "subscription";
614
+ readonly document: string;
615
+ }
616
+ /**
617
+ * The `graph.mutate.*` namespace.
618
+ *
619
+ * Mutations are compiled operations like queries; this binds one callable per
620
+ * compiled mutation operation so app code writes:
621
+ *
622
+ * await graph.mutate.cartLinesAdd({ cartId, lines }, {
623
+ * optimistic: (tx) => tx.set(cartRef, "totalQuantity", n + 1),
624
+ * invalidate: (data) => [cartRef],
625
+ * });
626
+ *
627
+ * Each call runs the mutation, folds the result into the cache, and applies the
628
+ * optimistic/invalidation policy (see runMutation).
629
+ */
630
+ type MutateFn = (variables: Record<string, unknown>, options?: Omit<Parameters<typeof runMutation>[0], "operation" | "variables" | "adapter" | "context" | "runtime">) => Promise<MutationResult>;
631
+ type BoundMutations = Record<string, MutateFn>;
632
+ interface CreateMutatorOptions {
633
+ /** Compiled operations; only `kind: "mutation"` entries are bound. */
634
+ readonly operations: Record<string, MutationOperationLike>;
635
+ readonly adapter: GraphClientAdapter;
636
+ readonly runtime: GraphRuntime;
637
+ /** The request context (auth/locale/env) passed to the transport. */
638
+ readonly context: GraphRequestContext;
639
+ }
640
+ declare function createMutator(options: CreateMutatorOptions): BoundMutations;
641
+ //#endregion
642
+ //#region src/context.d.ts
643
+ /**
644
+ * Structural RWSDK types.
645
+ *
646
+ * This package never imports `rwsdk` — like `@gleanql/vite`, it is decoupled from
647
+ * the host framework and matches its shapes structurally, so it can be tested in
648
+ * isolation and won't pin a framework version. A RedwoodSDK route handler / Page
649
+ * receives a `RequestInfo`:
650
+ * route("/product/:handle", ({ request, params, ctx }) => <ProductRoute ... />)
651
+ */
652
+ interface RequestInfo<Ctx extends Record<string, unknown> = Record<string, unknown>> {
653
+ readonly request: Request;
654
+ /** Dynamic route segments, e.g. `params.handle`, `params.$0`. */
655
+ readonly params: Record<string, string>;
656
+ /** Per-request mutable app context populated by middleware. */
657
+ readonly ctx: Ctx;
658
+ /** RedwoodSDK-specific context (opaque here). */
659
+ readonly rw?: unknown;
660
+ /** Cloudflare ExecutionContext. */
661
+ readonly cf?: unknown;
662
+ /** Mutable ResponseInit (status/headers). */
663
+ readonly response?: ResponseInit;
664
+ }
665
+ /**
666
+ * The route context object handed to the compiled variables factory *and* used
667
+ * as the transport `GraphRequestContext`. The compiler emits factories that read
668
+ * `ctx.params.handle`, `ctx.search.get(...)`, etc., so this shape is the contract
669
+ * between the generated code and the adapter.
670
+ */
671
+ interface GraphRouteContext extends GraphRequestContext {
672
+ readonly params: Record<string, string>;
673
+ readonly search: URLSearchParams;
674
+ readonly request: Request;
675
+ /** Application context contributed by `options.context` (auth, locale, env, ...). */
676
+ readonly [key: string]: unknown;
677
+ }
678
+ interface BuildRouteContextOptions<Ctx extends Record<string, unknown>> {
679
+ /**
680
+ * Contribute application context (shop domain, access token, locale, market,
681
+ * preview mode, Cloudflare env). Anything returned here is available to the
682
+ * variables factory and to the transport adapter's header builder.
683
+ */
684
+ readonly context?: (requestInfo: RequestInfo<Ctx>) => Record<string, unknown>;
685
+ }
686
+ /**
687
+ * Build the route/request context from a RequestInfo. `params` and `search` come
688
+ * from the URL; everything else is contributed by `options.context`. The raw
689
+ * `request` is included for header derivation but is *not* serialized to the
690
+ * client (see `serializeGraph`).
691
+ */
692
+ declare function buildRouteContext<Ctx extends Record<string, unknown>>(requestInfo: RequestInfo<Ctx>, options?: BuildRouteContextOptions<Ctx>): GraphRouteContext;
693
+ //#endregion
694
+ //#region src/integration.d.ts
695
+ interface ActiveRequestGraph extends GraphRequestContext {
696
+ readonly runtime: GraphRuntime;
697
+ readonly graph: BoundGraph;
698
+ /** `graph.mutate.*` — one callable per compiled mutation operation. */
699
+ readonly mutate: BoundMutations;
700
+ readonly roots: Record<string, FieldValue>;
701
+ readonly operation: CompiledOperation<GraphRouteContext>;
702
+ readonly variables: Record<string, unknown>;
703
+ /** The route/request context (used for transport + missing-field fetches). */
704
+ readonly requestContext: GraphRouteContext;
705
+ readonly errors?: ReadonlyArray<{
706
+ message: string;
707
+ }>;
708
+ }
709
+ interface GraphIntegrationOptions<Ctx extends Record<string, unknown> = Record<string, unknown>> extends BuildRouteContextOptions<Ctx> {
710
+ readonly schema: SchemaModel;
711
+ /** Compiled operations, keyed by name (e.g. from `virtual:graph/operations`). */
712
+ readonly operations: Record<string, CompiledOperation<GraphRouteContext>>;
713
+ /** Transport: a fetch/graphql-request adapter. */
714
+ readonly adapter: GraphClientAdapter;
715
+ /** Map a request to its operation name when `preload` is called without one. */
716
+ readonly resolveOperationName?: (requestInfo: RequestInfo<Ctx>) => string | undefined;
717
+ /**
718
+ * Fetch fields absent from the compiled operation (lazy boundaries, dynamic
719
+ * misses). Receives the batched misses + request context; returns resolved
720
+ * values. If omitted, misses are allowed/warned per `unexpectedMissingField`
721
+ * (hybrid mode) and resolve to `undefined`.
722
+ */
723
+ readonly fetchMissing?: (misses: readonly MissingFieldRead[], context: GraphRequestContext) => Promise<readonly MissingFieldResult[]>;
724
+ readonly unexpectedMissingField?: MissingFieldMode;
725
+ readonly onWarn?: (message: string) => void;
726
+ /** Allow-list of `context` keys that are safe to serialize to the client. */
727
+ readonly clientSafeContext?: readonly string[];
728
+ /** Optional scope to make a module-level `graph` import resolve this runtime. */
729
+ readonly scope?: GraphScope;
730
+ }
731
+ interface GraphIntegration<Ctx extends Record<string, unknown>> {
732
+ /** Preload + seed the operation for a request; attaches the graph to `ctx`. */
733
+ preload(requestInfo: RequestInfo<Ctx>, operationName?: string): Promise<ActiveRequestGraph | undefined>;
734
+ /** Read the active graph attached to a request (throws if not preloaded). */
735
+ getGraph(requestInfo: RequestInfo<Ctx>): BoundGraph;
736
+ /** The `graph.mutate.*` namespace for this request (throws if not preloaded). */
737
+ getMutator(requestInfo: RequestInfo<Ctx>): BoundMutations;
738
+ /** Invalidate a graph value / ref in this request's cache (refetch on next read). */
739
+ invalidate(requestInfo: RequestInfo<Ctx>, value: GraphRef | unknown): void;
740
+ /** Read the full active request state (runtime, roots, variables, ...). */
741
+ getActive(requestInfo: RequestInfo<Ctx>): ActiveRequestGraph | undefined;
742
+ /** Re-run the active (or named) operation, bypassing cache-first, into the same cache. */
743
+ refetch(requestInfo: RequestInfo<Ctx>, operationName?: string): Promise<void>;
744
+ /** Run `fn` with this request's runtime installed on the scope (server render). */
745
+ runInScope<R>(requestInfo: RequestInfo<Ctx>, fn: () => R): R;
746
+ }
747
+ declare function createGraphIntegration<Ctx extends Record<string, unknown> = Record<string, unknown>>(options: GraphIntegrationOptions<Ctx>): GraphIntegration<Ctx>;
748
+ //#endregion
749
+ //#region src/serialize.d.ts
750
+ /**
751
+ * Server -> client serialization.
752
+ *
753
+ * Graph values are proxies, not JSON, so we don't serialize them. We serialize
754
+ * the *cache* (normalized records + path records) plus the root refs and the
755
+ * operation identity. On the client we rebuild a runtime from that snapshot and
756
+ * re-bind the graph, so client components read the same fields with cache hits;
757
+ * fields absent from the snapshot fetch through the client adapter.
758
+ *
759
+ * Secrets stay server-side: only `clientSafeContext` keys are serialized.
760
+ */
761
+ interface GraphHydrationPayload {
762
+ readonly operationName: string;
763
+ readonly variables: Record<string, unknown>;
764
+ readonly snapshot: Record<string, Record<string, FieldValue>>;
765
+ readonly roots: Record<string, FieldValue>;
766
+ /** Allow-listed, client-safe slice of the request context. */
767
+ readonly context: Record<string, unknown>;
768
+ }
769
+ interface SerializeGraphOptions {
770
+ /** Keys of the request context that are safe to ship to the client. */
771
+ readonly clientSafeContext?: readonly string[];
772
+ }
773
+ /** Build the JSON-safe hydration payload from an active request. */
774
+ declare function serializeGraph(active: ActiveRequestGraph, options?: SerializeGraphOptions): GraphHydrationPayload;
775
+ /**
776
+ * Render a `<script>` that publishes the payload on `window[globalKey]` for
777
+ * client hydration. JSON is escaped so it cannot break out of the script element
778
+ * or be interpreted as HTML (`<`, `>`, `&`, U+2028/U+2029).
779
+ */
780
+ declare function renderGraphHydrationScript(payload: GraphHydrationPayload, options?: {
781
+ globalKey?: string;
782
+ nonce?: string;
783
+ }): string;
784
+ /**
785
+ * Just the inner JS of the hydration script (no `<script>` wrapper), for JSX
786
+ * hosts that inject via `dangerouslySetInnerHTML` and set the nonce themselves.
787
+ * JSON is escaped so it can't break out of the script element or be parsed as HTML.
788
+ */
789
+ declare function graphHydrationScriptContent(payload: GraphHydrationPayload, globalKey?: string): string;
790
+ /** Read the hydration payload published by `renderGraphHydrationScript` (client). */
791
+ declare function readGraphHydrationPayload(globalKey?: string): GraphHydrationPayload | undefined;
792
+ interface HydrateGraphOptions {
793
+ readonly schema: SchemaModel;
794
+ readonly adapter: GraphClientAdapter;
795
+ /** Fetch fields absent from the hydrated snapshot (lazy/dynamic reads). */
796
+ readonly fetchMissing?: (misses: readonly MissingFieldRead[], context: GraphRequestContext) => Promise<readonly MissingFieldResult[]>;
797
+ readonly unexpectedMissingField?: MissingFieldMode;
798
+ readonly onWarn?: (message: string) => void;
799
+ /** Install the hydrated runtime on this scope as the client singleton. */
800
+ readonly scope?: GraphScope;
801
+ }
802
+ interface HydratedGraph {
803
+ readonly runtime: GraphRuntime;
804
+ readonly graph: BoundGraph;
805
+ readonly roots: Record<string, FieldValue>;
806
+ readonly context: Record<string, unknown>;
807
+ }
808
+ /**
809
+ * Rebuild the runtime + bound graph on the client from a hydration payload.
810
+ * Reads present in the snapshot resolve synchronously; missing fields suspend and
811
+ * fetch through the client adapter.
812
+ */
813
+ declare function hydrateGraph(payload: GraphHydrationPayload, options: HydrateGraphOptions): HydratedGraph;
814
+ /**
815
+ * RSC-native hydration (vs. the `<script>`/`window` model above).
816
+ *
817
+ * Under React Server Components the `Document` shell is rendered once, but each
818
+ * client navigation re-streams only the page subtree. So a one-shot global can't
819
+ * keep client islands warm across navigation. Instead the payload rides the RSC
820
+ * flight stream as a prop of a client component (it is plain JSON by
821
+ * construction), and on every (re)render that component folds it into a single
822
+ * long-lived client runtime — the cache accumulates across navigations.
823
+ */
824
+ /** The page-current pointer islands read for `refresh()` (which operation + vars). */
825
+ interface GraphPagePointer {
826
+ readonly operationName: string;
827
+ readonly variables: Record<string, unknown>;
828
+ readonly context: Record<string, unknown>;
829
+ readonly roots: Record<string, FieldValue>;
830
+ }
831
+ /**
832
+ * Render-phase merge: fold a payload's snapshot into a live runtime, write-only
833
+ * (no subscriber notify — the caller bumps in a commit-phase effect). Idempotent.
834
+ * Returns whether anything changed.
835
+ */
836
+ declare function absorbHydrationPayload(runtime: GraphRuntime, payload: GraphHydrationPayload): boolean;
837
+ /** Derive the current-page pointer from a payload (drives `refresh()`). */
838
+ declare function pagePointer(payload: GraphHydrationPayload): GraphPagePointer;
839
+ //#endregion
840
+ //#region src/adapter-shared.d.ts
841
+ /**
842
+ * Shared helpers for client adapters.
843
+ *
844
+ * Every adapter implements the same `GraphClientAdapter.execute` contract; the
845
+ * runtime owns cache identity and Suspense, so an adapter is *only* transport.
846
+ * These helpers keep error/response mapping consistent across clients.
847
+ */
848
+ interface GraphQLError {
849
+ readonly message: string;
850
+ }
851
+ /** Build a `GraphResult`, omitting empty `data`/`errors` keys. */
852
+ declare function result<TData>(data: TData | null | undefined, errors?: readonly GraphQLError[]): GraphResult<TData>;
853
+ /** A push→pull async iterator: the transport pushes, the consumer pulls. */
854
+ interface PushPullIterator<T> extends AsyncIterator<T> {
855
+ /** Deliver the next value to the consumer (ignored once finished). */
856
+ push(value: T): void;
857
+ /** End the stream; the consumer's next pull resolves `{ done: true }`. */
858
+ finish(): void;
859
+ }
860
+ /**
861
+ * Bridge a push-based transport (SSE `onmessage`, a graphql-ws sink) into the
862
+ * pull-based `AsyncIterator` the runtime consumes. The transport wires its callbacks
863
+ * to `push`/`finish`; the consumer's `return()` (cleanup) calls `onReturn` to tear the
864
+ * transport down. Values pushed with no pending pull queue up and drain in order.
865
+ */
866
+ declare function pushPullIterator<T>(onReturn?: () => void): PushPullIterator<T>;
867
+ //#endregion
868
+ //#region src/adapter-ws.d.ts
869
+ /**
870
+ * A `graphql-ws` transport for the same `GraphClientAdapter` seam the fetch
871
+ * adapter implements. WebSockets carry every operation kind — a query/mutation is
872
+ * a single-result stream that completes, a subscription is a long-lived one — so
873
+ * this adapter drives both `execute` and `subscribe` off one `graphql-ws` client.
874
+ *
875
+ * We do NOT bundle `graphql-ws`: the app installs it, calls its `createClient({ url })`,
876
+ * and passes the result here. The client is typed structurally (the subset we use)
877
+ * so the dependency stays optional and the adapter is trivially testable with a fake.
878
+ *
879
+ * import { createClient } from "graphql-ws";
880
+ * const adapter = createGraphWsAdapter({ client: createClient({ url: "wss://…/graphql" }) });
881
+ */
882
+ /** Sink graphql-ws pushes results into — mirrors its `Sink`. */
883
+ interface GraphWsSink<T = unknown> {
884
+ next: (value: T) => void;
885
+ error: (error: unknown) => void;
886
+ complete: () => void;
887
+ }
888
+ /** Operation payload graphql-ws subscribes with — mirrors its `SubscribePayload`. */
889
+ interface GraphWsPayload {
890
+ readonly query: string;
891
+ readonly variables?: Record<string, unknown> | null;
892
+ readonly operationName?: string | null;
893
+ readonly extensions?: Record<string, unknown>;
894
+ }
895
+ /** Structural subset of graphql-ws's `Client` we depend on. */
896
+ interface GraphWsClient {
897
+ subscribe<T = GraphResult<unknown>>(payload: GraphWsPayload, sink: GraphWsSink<T>): () => void;
898
+ dispose?: () => void | Promise<void>;
899
+ }
900
+ interface GraphWsAdapterOptions {
901
+ /** A graphql-ws client, e.g. `createClient({ url })`. */
902
+ readonly client: GraphWsClient;
903
+ /**
904
+ * Per-request `extensions` from context (auth token, shop domain, locale). The
905
+ * connection-level params belong on the client; this rides each operation.
906
+ */
907
+ readonly extensions?: (context: GraphRequestContext) => Record<string, unknown>;
908
+ }
909
+ declare function createGraphWsAdapter(options: GraphWsAdapterOptions): GraphClientAdapter;
910
+ //#endregion
911
+ //#region src/persisted.d.ts
912
+ /**
913
+ * Server-side persisted-operation allowlist.
914
+ *
915
+ * The build already knows every operation the app can send (the compiled
916
+ * `operations` map carries each document + its SHA-256 hash), so the server can
917
+ * refuse anything else. `createPersistedResolver(operations)` turns an incoming
918
+ * request body into the document to execute — by hash (`extensions.
919
+ * persistedQuery.sha256Hash`, the Apollo APQ wire shape the fetch adapter's
920
+ * `persisted: true` mode sends) or by exact-document match — and rejects
921
+ * free-form queries unless explicitly allowed.
922
+ */
923
+ /** The slice of a compiled operation the resolver needs (the generated `operations` map satisfies it). */
924
+ interface PersistedLookupOperation {
925
+ readonly document: string;
926
+ readonly hash?: string;
927
+ }
928
+ /** A GraphQL-over-HTTP request body, as parsed from JSON. */
929
+ interface PersistedRequestBody {
930
+ readonly query?: string;
931
+ readonly operationName?: string;
932
+ readonly variables?: unknown;
933
+ readonly extensions?: {
934
+ readonly persistedQuery?: {
935
+ readonly version?: number;
936
+ readonly sha256Hash?: string;
937
+ };
938
+ };
939
+ }
940
+ type PersistedResolution = /** Execute this document. */{
941
+ readonly kind: "ok";
942
+ readonly document: string;
943
+ } /** Unknown hash and no usable document — reply `PersistedQueryNotFound` so an APQ client retries with the query. */ | {
944
+ readonly kind: "not-found";
945
+ } /** Free-form (or mismatched) query outside the allowlist — reply 4xx. */ | {
946
+ readonly kind: "rejected";
947
+ };
948
+ interface PersistedResolverOptions {
949
+ /** Execute documents that aren't in the allowlist (turns the allowlist into hash-only transport compression). */
950
+ readonly allowUnpersisted?: boolean;
951
+ }
952
+ declare function createPersistedResolver(operations: Readonly<Record<string, PersistedLookupOperation>>, options?: PersistedResolverOptions): (body: PersistedRequestBody) => PersistedResolution;
953
+ //#endregion
954
+ export { ActiveGraph, ActiveRequestGraph, BindGraphOptions, BoundGraph, BoundMutations, BuildRouteContextOptions, CacheResolution, CompiledOperation, CreateMutatorOptions, FetchAdapterOptions, FieldLookup, FieldValue, GRAPH_REF, GRAPH_TRAIL, GRAPH_TYPE, GraphBinding, GraphCache, GraphClientAdapter, GraphHydrationPayload, GraphIntegration, GraphIntegrationOptions, GraphOperation, GraphPagePointer, GraphQLError, GraphRef, GraphRequestContext, GraphResult, GraphRouteContext, GraphRuntime, GraphRuntimeOptions, GraphScope, GraphSelection, GraphWsAdapterOptions, GraphWsClient, GraphWsPayload, GraphWsSink, HydrateGraphOptions, HydratedGraph, KeyOf, MissingFieldMode, MissingFieldRead, MissingFieldResult, MutateFn, MutationOperationLike, MutationResult, MutationTransaction, PathStep, PersistedLookupOperation, PersistedRequestBody, PersistedResolution, PersistedResolverOptions, PushPullIterator, RequestInfo, RunMutationOptions, RunRouteOptions, RunRouteResult, SerializeGraphOptions, UserError, absorbHydrationPayload, bindGraph, bindScope, buildRouteContext, createFetchAdapter, createGraphIntegration, createGraphProxy, createGraphWsAdapter, createMutator, createPersistedResolver, errorMessage, graphHydrationScriptContent, hydrateGraph, invalidateValue, isGraphRef, normalizeValue, pagePointer, persistRootLinks, pushPullIterator, readGraphHydrationPayload, refetch, renderGraphHydrationScript, resolveFromCache, responseKeyCandidates, result, runMutation, runRoute, seedResult, selectionOf, serializeGraph, setReadTracker, toArgMap, trailOf };