grist-widget-sdk 0.2.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +730 -0
  2. package/LICENSE +15 -0
  3. package/README.md +249 -0
  4. package/dist/advanced.cjs +2749 -0
  5. package/dist/advanced.cjs.map +1 -0
  6. package/dist/advanced.d.cts +790 -0
  7. package/dist/advanced.d.ts +790 -0
  8. package/dist/advanced.js +825 -0
  9. package/dist/advanced.js.map +1 -0
  10. package/dist/chunk-64SVRWW7.js +758 -0
  11. package/dist/chunk-64SVRWW7.js.map +1 -0
  12. package/dist/chunk-BNBV24WM.js +479 -0
  13. package/dist/chunk-BNBV24WM.js.map +1 -0
  14. package/dist/chunk-ELGVOHFD.js +32 -0
  15. package/dist/chunk-ELGVOHFD.js.map +1 -0
  16. package/dist/chunk-GTTD5HPO.js +36 -0
  17. package/dist/chunk-GTTD5HPO.js.map +1 -0
  18. package/dist/chunk-JASMM2YC.js +243 -0
  19. package/dist/chunk-JASMM2YC.js.map +1 -0
  20. package/dist/chunk-PM3B4WYV.js +12 -0
  21. package/dist/chunk-PM3B4WYV.js.map +1 -0
  22. package/dist/chunk-QKXKZ27D.js +1189 -0
  23. package/dist/chunk-QKXKZ27D.js.map +1 -0
  24. package/dist/chunk-QZG7SNUG.js +463 -0
  25. package/dist/chunk-QZG7SNUG.js.map +1 -0
  26. package/dist/chunk-REV5AUXD.js +25 -0
  27. package/dist/chunk-REV5AUXD.js.map +1 -0
  28. package/dist/chunk-XE2SKNLH.js +1641 -0
  29. package/dist/chunk-XE2SKNLH.js.map +1 -0
  30. package/dist/create-grist-emulator-C_0I4wax.d.ts +80 -0
  31. package/dist/create-grist-emulator-DBS6gmiI.d.cts +80 -0
  32. package/dist/emulator/devtools.cjs +1367 -0
  33. package/dist/emulator/devtools.cjs.map +1 -0
  34. package/dist/emulator/devtools.d.cts +46 -0
  35. package/dist/emulator/devtools.d.ts +46 -0
  36. package/dist/emulator/devtools.js +480 -0
  37. package/dist/emulator/devtools.js.map +1 -0
  38. package/dist/emulator/testing.cjs +2061 -0
  39. package/dist/emulator/testing.cjs.map +1 -0
  40. package/dist/emulator/testing.d.cts +50 -0
  41. package/dist/emulator/testing.d.ts +50 -0
  42. package/dist/emulator/testing.js +124 -0
  43. package/dist/emulator/testing.js.map +1 -0
  44. package/dist/emulator/transports.cjs +1132 -0
  45. package/dist/emulator/transports.cjs.map +1 -0
  46. package/dist/emulator/transports.d.cts +37 -0
  47. package/dist/emulator/transports.d.ts +37 -0
  48. package/dist/emulator/transports.js +12 -0
  49. package/dist/emulator/transports.js.map +1 -0
  50. package/dist/emulator.cjs +1796 -0
  51. package/dist/emulator.cjs.map +1 -0
  52. package/dist/emulator.d.cts +107 -0
  53. package/dist/emulator.d.ts +107 -0
  54. package/dist/emulator.js +26 -0
  55. package/dist/emulator.js.map +1 -0
  56. package/dist/index.cjs +5511 -0
  57. package/dist/index.cjs.map +1 -0
  58. package/dist/index.d.cts +825 -0
  59. package/dist/index.d.ts +825 -0
  60. package/dist/index.js +2447 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/inline-CRwQQ-GW.d.cts +311 -0
  63. package/dist/inline-QitZwXKP.d.ts +311 -0
  64. package/dist/templates-BC7FoSdW.d.ts +24 -0
  65. package/dist/templates-DBq719O_.d.cts +24 -0
  66. package/dist/types-Fb2wplkv.d.cts +228 -0
  67. package/dist/types-Fb2wplkv.d.ts +228 -0
  68. package/dist/use-grist-widget-options-from-context-BFhQBGRY.d.ts +522 -0
  69. package/dist/use-grist-widget-options-from-context-qpFMdoBJ.d.cts +522 -0
  70. package/package.json +131 -0
@@ -0,0 +1,790 @@
1
+ import { C as GristTerminationReason, i as GristGeneration, p as GristMappingSource, U as UseGristCoreOptions, E as GristWidgetSnapshot, I as GristWidgetStatus, b as GristCapabilities, D as GristUserActionTuple, G as GristAccessToken, f as GristColumnarTable } from './use-grist-widget-options-from-context-BFhQBGRY.js';
2
+ export { a as GristAuthz, d as GristColumnInfo, g as GristConfig, h as GristCurrentTableState, j as GristLifecycle, k as GristLifecyclePhase, l as GristLink, m as GristLinkState, n as GristMappingComplete, o as GristMappingIncomplete, q as GristMappingState, z as GristStreamFreshness, A as GristSync, L as ListColumnsOptions, J as ListTablesOptions, O as UseGristWidgetOptionsFromContextConfig, P as UseGristWidgetOptionsFromContextResult, aq as useGristWidgetOptionsFromContext } from './use-grist-widget-options-from-context-BFhQBGRY.js';
3
+ import { o as GristRequiredAccess, b as GristColumnsToMap, w as GristWidgetColumnMap, i as GristReadyOptions, d as GristDocApi, G as GristApplyUserActionsOptions, q as GristTableNewRecord, r as GristTableOpOptions, h as GristMinimalRecord, t as GristTableUpdateRecord, v as GristTableUpsertRecord, u as GristTableUpsertOptions, p as GristRowRecord, f as GristFetchSelectedOptions } from './types-Fb2wplkv.js';
4
+ import * as react_jsx_runtime from 'react/jsx-runtime';
5
+ import { ReactNode } from 'react';
6
+ import 'zod';
7
+
8
+ /**
9
+ * Action union for the handshake state machine.
10
+ *
11
+ * Every action that can mutate the snapshot is listed here as a discriminated
12
+ * union — exhaustiveness checks in the reducer rely on it.
13
+ *
14
+ * Actions either:
15
+ * - target a single axis (e.g. `RPC_SUCCESS` → link), OR
16
+ * - target multiple axes via a single transition (e.g. `NEGOTIATE_SUCCESS`
17
+ * advances lifecycle AND authz at once).
18
+ *
19
+ * Generation-carrying actions can be **dropped** by the reducer if their
20
+ * `generation` is older than the snapshot's current generation; this is the
21
+ * primary defence against stale events arriving after a reload/restart.
22
+ */
23
+
24
+ type GristLifecycleAction = {
25
+ type: "DETECT_START";
26
+ atMs: number;
27
+ } | {
28
+ type: "DETECT_TICK";
29
+ attempts: number;
30
+ atMs: number;
31
+ } | {
32
+ type: "DETECT_AVAILABLE";
33
+ atMs: number;
34
+ } | {
35
+ type: "DETECT_TIMEOUT";
36
+ attempts: number;
37
+ atMs: number;
38
+ } | {
39
+ type: "NOT_EMBEDDED";
40
+ atMs: number;
41
+ } | {
42
+ type: "NEGOTIATE_START";
43
+ access: GristRequiredAccess;
44
+ atMs: number;
45
+ } | {
46
+ type: "NEGOTIATE_SUCCESS";
47
+ access: GristRequiredAccess;
48
+ grantedLevel: GristRequiredAccess;
49
+ atMs: number;
50
+ } | {
51
+ type: "NEGOTIATE_FAIL";
52
+ reason: GristTerminationReason;
53
+ atMs: number;
54
+ } | {
55
+ type: "TERMINATE";
56
+ reason: GristTerminationReason;
57
+ atMs: number;
58
+ };
59
+ type GristLinkAction = {
60
+ type: "RPC_SUCCESS";
61
+ atMs: number;
62
+ } | {
63
+ type: "RPC_FAILURE";
64
+ atMs: number;
65
+ kind: "timeout" | "rejected" | "other";
66
+ } | {
67
+ type: "HEARTBEAT_OK";
68
+ atMs: number;
69
+ } | {
70
+ type: "HEARTBEAT_FAIL";
71
+ atMs: number;
72
+ };
73
+ type GristAuthzAction = {
74
+ type: "AUTHZ_REPORT";
75
+ /** Effective access level reported by Grist (`onOptions` / `interaction.access_level`). */
76
+ level: GristRequiredAccess;
77
+ atMs: number;
78
+ generation: GristGeneration;
79
+ };
80
+ type GristGenerationAction = {
81
+ type: "BUMP_GENERATION";
82
+ atMs: number;
83
+ reason: "reload" | "restart" | "link_recovery" | "widening";
84
+ };
85
+ type GristMappingAction = {
86
+ type: "MAPPING_DECLARE";
87
+ declared: GristColumnsToMap;
88
+ atMs: number;
89
+ generation: GristGeneration;
90
+ } | {
91
+ type: "MAPPING_REPORT";
92
+ source: GristMappingSource;
93
+ payload: GristWidgetColumnMap | null;
94
+ atMs: number;
95
+ generation: GristGeneration;
96
+ } | {
97
+ type: "MAPPING_INVALIDATE";
98
+ atMs: number;
99
+ generation: GristGeneration;
100
+ } | {
101
+ type: "MAPPING_INVALIDATION_RESOLVE";
102
+ atMs: number;
103
+ generation: GristGeneration;
104
+ } | {
105
+ type: "MAPPING_TIMEOUT";
106
+ atMs: number;
107
+ generation: GristGeneration;
108
+ };
109
+ type GristSyncStream = "record" | "records" | "options" | "newRecord";
110
+ type GristSyncAction = {
111
+ type: "STREAM_RECEIVED";
112
+ stream: GristSyncStream;
113
+ payloadHash: string;
114
+ atMs: number;
115
+ generation: GristGeneration;
116
+ } | {
117
+ type: "STREAM_WARMING";
118
+ stream: GristSyncStream;
119
+ atMs: number;
120
+ } | {
121
+ type: "STREAM_STALE";
122
+ stream: GristSyncStream;
123
+ atMs: number;
124
+ reason: "link_lost" | "tab_hidden" | "host_quiet";
125
+ } | {
126
+ type: "CURRENT_TABLE_KNOWN";
127
+ tableId: string;
128
+ atMs: number;
129
+ } | {
130
+ type: "CURRENT_TABLE_REFRESH_START";
131
+ atMs: number;
132
+ } | {
133
+ type: "CURRENT_TABLE_ERROR";
134
+ message: string;
135
+ atMs: number;
136
+ };
137
+ type GristAction = GristLifecycleAction | GristLinkAction | GristAuthzAction | GristGenerationAction | GristMappingAction | GristSyncAction;
138
+
139
+ /**
140
+ * Pure reducer for the handshake state machine.
141
+ *
142
+ * Contract
143
+ * --------
144
+ * - **Pure**: `(snapshot, action, ctx) => nextSnapshot`. No I/O, no globals,
145
+ * no Date.now() — callers pass timestamps via `action.atMs` and (optionally)
146
+ * a context object for the {@link MappingResolver}.
147
+ * - **Total**: every (snapshot.lifecycle.phase × action.type) pair returns
148
+ * either an updated snapshot or the identity snapshot.
149
+ * - **Idempotent for replays**: same generation-stamped action applied twice
150
+ * leaves the snapshot unchanged (`payloadHash` short-circuit on streams,
151
+ * resolver hash short-circuit on mappings).
152
+ * - **Generation-safe**: any action carrying a `generation` lower than the
153
+ * current snapshot generation is dropped silently — this is what makes
154
+ * the SDK robust to late events after reload/restart.
155
+ *
156
+ * The reducer never throws. Invariant violations are surfaced via
157
+ * `./invariants.ts` if the caller opts in.
158
+ */
159
+
160
+ /**
161
+ * Tuning knobs. Pass `Partial<ReducerConfig>` to override defaults; values
162
+ * are read by the reducer at each step.
163
+ */
164
+ type ReducerConfig = {
165
+ /** Stale-pings threshold before link transitions to `stale`. */
166
+ staleAfterMissed: number;
167
+ /** Stale-pings threshold before link transitions to `lost`. */
168
+ lostAfterMissed: number;
169
+ };
170
+
171
+ /**
172
+ * Environment abstraction for the handshake effects layer.
173
+ *
174
+ * Why an abstraction
175
+ * ------------------
176
+ * The detect / negotiate / heartbeat effects need access to:
177
+ * - the `grist` global (or absence thereof),
178
+ * - `window.self !== window.top` to detect iframe embedding,
179
+ * - timers (`setTimeout`, `clearTimeout`),
180
+ * - the wall clock (`performance.now()` or `Date.now()`),
181
+ * - `navigator.connection?.effectiveType` for adaptive policy.
182
+ *
183
+ * Putting these behind a small interface lets us test the manager and effects
184
+ * **without touching `window`**, using deterministic fake timers and stubbed
185
+ * detectors / negotiators.
186
+ *
187
+ * Production code calls {@link createBrowserEnvironment()} which returns an
188
+ * adapter against `globalThis`. Tests pass {@link createTestEnvironment} or a
189
+ * hand-rolled stub.
190
+ */
191
+
192
+ type GristEnvironment = {
193
+ /** Wall-clock ms (monotonic preferred but not required). */
194
+ now(): number;
195
+ /** Schedule a callback. */
196
+ setTimeout(cb: () => void, ms: number): TimerHandle;
197
+ /** Cancel a timer. */
198
+ clearTimeout(handle: TimerHandle): void;
199
+ /**
200
+ * Probe whether the `grist` global is present in this realm.
201
+ * Returns `{ embedded }` when the global exists; `null` otherwise.
202
+ */
203
+ probeGrist(): {
204
+ embedded: boolean;
205
+ } | null;
206
+ /**
207
+ * Snapshot of the `grist` global. Effects use this once detection has
208
+ * succeeded; callers must NOT cache the result across `restart()`.
209
+ */
210
+ getGrist(): GristLike | null;
211
+ /** Adaptive detection budget hint (ms). `null` if unknown. */
212
+ effectiveTypeHint(): EffectiveTypeHint | null;
213
+ /** Add a listener for `visibilitychange` / `online` / `focus`. Returns cleanup. */
214
+ onVisibilityChange?(listener: (visible: boolean) => void): () => void;
215
+ onOnline?(listener: () => void): () => void;
216
+ };
217
+ type TimerHandle = ReturnType<typeof setTimeout> | number | {
218
+ __opaque: true;
219
+ };
220
+ type EffectiveTypeHint = "slow-2g" | "2g" | "3g" | "4g" | "5g";
221
+ /**
222
+ * Subset of `window.grist` actually used by handshake effects. Mirrors the
223
+ * declared shape in `grist-global.d.ts` but only the pieces we need.
224
+ */
225
+ type GristLike = {
226
+ ready: (options?: GristReadyOptions) => Promise<void> | void;
227
+ docApi: GristDocApi | null | undefined;
228
+ onRecord?: (cb: (record: Record<string, unknown> | null, mappings?: GristWidgetColumnMap | null) => void, options?: {
229
+ keepEncoded?: boolean;
230
+ }) => void;
231
+ onRecords?: (cb: (records: Record<string, unknown>[], mappings?: GristWidgetColumnMap | null) => void, options?: {
232
+ keepEncoded?: boolean;
233
+ format?: string;
234
+ }) => void;
235
+ onOptions?: (cb: (options: Record<string, unknown> | null, interaction: unknown) => void) => void;
236
+ onNewRecord?: (cb: (mappings: GristWidgetColumnMap | null) => void) => void;
237
+ sectionApi?: {
238
+ mappings: () => Promise<GristWidgetColumnMap | null>;
239
+ };
240
+ };
241
+
242
+ /**
243
+ * Detect effect — poll for `window.grist` with exponential backoff inside a
244
+ * finite, optionally adaptive budget.
245
+ *
246
+ * Outputs (dispatched to the manager):
247
+ * - DETECT_START (once, at start)
248
+ * - DETECT_TICK (each unsuccessful poll, with attempts count)
249
+ * - DETECT_AVAILABLE (grist global present AND embedded)
250
+ * - NOT_EMBEDDED (grist global present but `self === top`)
251
+ * - DETECT_TIMEOUT (budget consumed without detection)
252
+ *
253
+ * The effect aborts on `signal.aborted` and clears its pending timer.
254
+ *
255
+ * Pure of `window`: relies on the {@link GristEnvironment} adapter so the
256
+ * effect is fully unit-testable with a virtual clock.
257
+ */
258
+
259
+ type DetectOptions = {
260
+ /** Custom poll sequence in ms. Index past end uses last value. */
261
+ pollSequenceMs?: readonly number[];
262
+ /** Total wall-clock budget across all polls. */
263
+ budgetMs?: number;
264
+ };
265
+
266
+ /**
267
+ * Negotiate effect — issue `grist.ready(payload)` race-d against a timeout
268
+ * and an external `AbortSignal`.
269
+ *
270
+ * Dispatches:
271
+ * - NEGOTIATE_START
272
+ * - NEGOTIATE_SUCCESS (grist.ready resolved AND `docApi` is present)
273
+ * - NEGOTIATE_FAIL (reason: ready_failed | ready_timeout | manual_terminate)
274
+ *
275
+ * The effect never throws to the caller. Aborts before resolution silently
276
+ * stop dispatching.
277
+ */
278
+
279
+ type NegotiateOptions = {
280
+ /** Max ms to wait for `grist.ready` to resolve. */
281
+ timeoutMs?: number;
282
+ /**
283
+ * Custom implementation of `grist.ready(payload)`. Defaults to calling
284
+ * `grist.ready` directly via the env. Production callers (the provider)
285
+ * inject a wrapper that routes through `ensureGristReady()` so the
286
+ * page-level singleton dedupes concurrent ready calls.
287
+ */
288
+ readyImpl?: (payload: GristReadyOptions) => Promise<void> | void;
289
+ };
290
+
291
+ /**
292
+ * Subscriptions effect — bind Grist event streams once and feed the handshake
293
+ * machine with generation-stamped, hash-deduplicated dispatches.
294
+ *
295
+ * Why a dedicated effect
296
+ * ----------------------
297
+ * The SDK already has a {@link "../../subscriptions"} module that owns the
298
+ * one-time binding to `grist.on*` and the BehaviorSubject-style cache. We do
299
+ * NOT re-bind here — instead, we install a single listener per stream that
300
+ * forwards into the handshake reducer.
301
+ *
302
+ * Generation discipline: every dispatched action carries the CURRENT manager
303
+ * generation read at the moment the event arrives. The reducer enforces
304
+ * `action.generation >= snapshot.generation`, so events arriving after a
305
+ * `reload()` / `restart()` that bumped generation are dropped automatically.
306
+ *
307
+ * Hash: payloads are reduced to a compact, deterministic string so duplicates
308
+ * (e.g. emulator triple-replay of initial state) become no-ops at the reducer.
309
+ */
310
+
311
+ type SubscriptionsHandle = {
312
+ cancel(): void;
313
+ };
314
+ /**
315
+ * Pluggable binder type. The default {@link defaultSubscriptionsBinder}
316
+ * subscribes to the SDK's singleton {@link GristEvent} instances. Tests can
317
+ * supply a stub that never touches the real `grist` global.
318
+ */
319
+ type SubscriptionsBinder = (ctx: SubscriptionsContext) => SubscriptionsHandle;
320
+ type SubscriptionsContext = {
321
+ dispatch: (action: GristAction) => void;
322
+ /**
323
+ * Snapshot generation provider. Must be read lazily (inside the callback)
324
+ * because `generation` is monotone but mutates over the lifetime of the
325
+ * subscription.
326
+ */
327
+ getGeneration: () => GristGeneration;
328
+ /** Hook for the mapping effect: receive payloads in arrival order. */
329
+ onRecordPayload?: (mappings: Record<string, unknown> | null | undefined) => void;
330
+ onRecordsPayload?: (mappings: Record<string, unknown> | null | undefined) => void;
331
+ onOptionsPayload?: (options: Record<string, unknown> | null) => void;
332
+ onNewRecordPayload?: (mappings: Record<string, unknown> | null | undefined) => void;
333
+ };
334
+
335
+ /**
336
+ * Mappings effect — feed column-mapping payloads into the reducer from the
337
+ * three Grist sources (`sectionApi.mappings()`, `onRecord`, `onRecords`,
338
+ * `onNewRecord`) and trigger the mapping timeout if nothing arrives in time.
339
+ *
340
+ * Inputs:
341
+ * - `declared` — `columns` from the provider options.
342
+ * - `getGeneration()` — current snapshot generation (for stale-drop).
343
+ * - `getGrist()` — environment accessor to `grist.sectionApi.mappings()`.
344
+ *
345
+ * Outputs:
346
+ * - `MAPPING_DECLARE` (once at start when `declared.length > 0`)
347
+ * - `MAPPING_REPORT` (one per source payload received)
348
+ * - `MAPPING_TIMEOUT` (if no payload after `timeoutMs`)
349
+ *
350
+ * The effect does NOT subscribe to grist events directly — it consumes them
351
+ * via the {@link SubscriptionsContext} payload callbacks. This keeps the
352
+ * single-binding contract of `subscriptions.ts` intact (one `grist.onRecord`
353
+ * binding per page).
354
+ */
355
+
356
+ type MappingsOptions = {
357
+ /**
358
+ * Time after `MAPPING_DECLARE` before falling back to `unreported` state.
359
+ * Default 5 000 ms.
360
+ */
361
+ timeoutMs?: number;
362
+ /**
363
+ * If true, dispatch `MAPPING_INVALIDATE` whenever a payload differs from
364
+ * the last applied mappings. Default false — graceful invalidation requires
365
+ * a `mappingsChange: true` envelope flag the SDK doesn't currently surface,
366
+ * so we leave it opt-in for tests.
367
+ */
368
+ invalidateOnDiff?: boolean;
369
+ };
370
+
371
+ /**
372
+ * Heartbeat effect — periodically verify the RPC channel is alive.
373
+ *
374
+ * Strategy
375
+ * --------
376
+ * 1. Schedule a ping every `intervalMs` (default 30 s).
377
+ * 2. The ping calls a cheap RPC (default `grist.docApi.getDocName()`).
378
+ * 3. Success → `HEARTBEAT_OK`. Failure / timeout → `HEARTBEAT_FAIL`.
379
+ * 4. Any externally-observed RPC success (the caller invokes
380
+ * {@link HeartbeatHandle.recordRpcSuccess}) coalesces with the heartbeat
381
+ * and reschedules the next ping — we don't burn a request when natural
382
+ * traffic already proves the link.
383
+ * 5. Tab hidden? Pause via `env.onVisibilityChange`. Resume on `visible`.
384
+ *
385
+ * The effect is **purely an observer**: it never mutates application state
386
+ * directly — it only dispatches actions the reducer consumes.
387
+ */
388
+
389
+ type HeartbeatOptions = {
390
+ /** How often to issue a probe when no natural RPC traffic occurred. */
391
+ intervalMs?: number;
392
+ /** Max time to wait for the probe before counting it as a failure. */
393
+ pingTimeoutMs?: number;
394
+ /** If true, pause the heartbeat when the document becomes hidden. */
395
+ pauseWhenHidden?: boolean;
396
+ /**
397
+ * Probe used when the interval elapses. Defaults to
398
+ * `() => env.getGrist().docApi.getDocName()`. Tests pass a controlled stub.
399
+ */
400
+ probe?: () => Promise<unknown>;
401
+ };
402
+
403
+ /**
404
+ * GristHandshakeManager — single owner of the handshake snapshot.
405
+ *
406
+ * Responsibilities
407
+ * ----------------
408
+ * 1. Hold the snapshot and the {@link GristReducerContext}.
409
+ * 2. Run effects (detect → negotiate → subscriptions + mappings) in order.
410
+ * 3. Bump generation on `reload()` / `restart()` and propagate cancellation
411
+ * through a per-generation {@link AbortController}.
412
+ * 4. Notify subscribers on each snapshot change (interface compatible with
413
+ * React's `useSyncExternalStore`).
414
+ *
415
+ * The manager is a **class**, not a hook. React hooks subscribe to it via the
416
+ * standard `subscribe` / `getSnapshot` pair so unit tests can drive the
417
+ * manager directly with a virtual clock — no React needed.
418
+ */
419
+
420
+ type ManagerOptions = GristReadyOptions & {
421
+ detect?: DetectOptions;
422
+ negotiate?: NegotiateOptions;
423
+ mappings?: MappingsOptions;
424
+ heartbeat?: HeartbeatOptions | false;
425
+ reducerConfig?: Partial<ReducerConfig>;
426
+ /**
427
+ * Where to read `grist`, timers, and platform hints from. Defaults to a
428
+ * browser adapter. Tests pass {@link createTestEnvironment}.
429
+ */
430
+ env?: GristEnvironment;
431
+ /**
432
+ * Binder for grist event streams. Defaults to
433
+ * {@link defaultSubscriptionsBinder} which wires the SDK's singleton
434
+ * {@link "../../subscriptions"} module. Tests can pass `noopSubscriptionsBinder`
435
+ * to avoid touching the real `grist` global.
436
+ */
437
+ subscriptionsBinder?: SubscriptionsBinder;
438
+ /**
439
+ * Called when the manager catches an unexpected internal error.
440
+ * Telemetry hook — does not affect reducer state.
441
+ */
442
+ onError?: (err: unknown, where: string) => void;
443
+ /**
444
+ * Called from inside {@link ManagerControls.reload} BEFORE generation bump.
445
+ * Production callers route `resetGristReadySingleton()` through here so
446
+ * the page-level ready singleton is cleared and the next negotiate effect
447
+ * issues a fresh `grist.ready` instead of re-using the cached promise.
448
+ */
449
+ onBeforeReload?: () => void;
450
+ };
451
+
452
+ type UseGristAvailabilityOptions = Pick<UseGristCoreOptions, "availabilityMaxAttempts" | "availabilityIntervalMs" | "logWhenNotEmbedded">;
453
+ /**
454
+ * Handshake-backed availability — projects `isAvailable` from the FSM.
455
+ */
456
+ declare function useGristAvailability(options?: UseGristAvailabilityOptions): boolean | null;
457
+
458
+ type UseGristReadyOptions = GristReadyOptions & {
459
+ availabilityMaxAttempts?: number;
460
+ availabilityIntervalMs?: number;
461
+ logWhenNotEmbedded?: boolean;
462
+ };
463
+ type UseGristReadyResult = {
464
+ isAvailable: boolean | null;
465
+ docApi: GristDocApi | null;
466
+ error: string | null;
467
+ reload: () => Promise<void>;
468
+ };
469
+ /**
470
+ * Handshake-backed `grist.ready` — delegates to {@link useGristCore} (FSM).
471
+ *
472
+ * Prefer `<GristWidgetProvider>` + `useGrist()` for full widget state; use this
473
+ * hook when you only need `docApi` and connection status.
474
+ */
475
+ declare function useGristReady(options?: UseGristReadyOptions): UseGristReadyResult;
476
+
477
+ /**
478
+ * `useGristHandshake()` — React adapter over {@link GristHandshakeManager}.
479
+ *
480
+ * Exposes the full {@link GristWidgetSnapshot} plus derived
481
+ * {@link GristCapabilities}, plus `reload()` / `restart()` controls.
482
+ *
483
+ * Identity stability
484
+ * ------------------
485
+ * The snapshot reference changes whenever the reducer produces a new
486
+ * snapshot. Capabilities are memoized by snapshot identity, so consumers
487
+ * branching on a single capability (e.g. `canWriteRecords`) avoid extra
488
+ * re-renders unless that boolean actually flips.
489
+ *
490
+ * SSR-safe: `getServerSnapshot` returns the initial snapshot deterministically.
491
+ */
492
+
493
+ type UseGristHandshakeOptions = ManagerOptions;
494
+ type UseGristHandshakeResult = {
495
+ snapshot: GristWidgetSnapshot;
496
+ status: GristWidgetStatus;
497
+ error: string | null;
498
+ capabilities: GristCapabilities;
499
+ reload: () => void;
500
+ restart: () => void;
501
+ };
502
+ /**
503
+ * Mount a handshake manager local to the component subtree. Multiple
504
+ * widgets in the same page should NOT call this in parallel — instead,
505
+ * mount a single `<GristWidgetProvider>` at the app root.
506
+ *
507
+ * In tests, pass `options.env` (see `createTestEnvironment`) to drive the
508
+ * machine with a virtual clock.
509
+ */
510
+ declare function useGristHandshake(options?: UseGristHandshakeOptions): UseGristHandshakeResult;
511
+ /**
512
+ * Convenience hook — equivalent to `useGristHandshake().capabilities` but
513
+ * skips returning the full snapshot. Intended for leaf components that only
514
+ * need to gate UI on `canRender` / `canWriteRecords` / etc.
515
+ */
516
+ declare function useGristCapabilities(options?: UseGristHandshakeOptions): GristCapabilities;
517
+
518
+ type GristHandshakeContextValue = {
519
+ snapshot: GristWidgetSnapshot;
520
+ status: GristWidgetStatus;
521
+ error: string | null;
522
+ capabilities: GristCapabilities;
523
+ reload: () => void;
524
+ restart: () => void;
525
+ recordRpcSuccess: () => void;
526
+ recordRpcFailure: () => void;
527
+ };
528
+ type GristHandshakeProviderProps = {
529
+ options?: ManagerOptions;
530
+ children: ReactNode;
531
+ };
532
+ /**
533
+ * Mount a single {@link GristHandshakeManager} for the React subtree and
534
+ * expose its snapshot via context.
535
+ *
536
+ * The manager is created **once** on first mount; the `options` prop is
537
+ * captured at that time. To re-create the manager with new options, unmount
538
+ * and re-mount the provider (e.g. via a `key`).
539
+ */
540
+ declare function GristHandshakeProvider({ options, children }: GristHandshakeProviderProps): react_jsx_runtime.JSX.Element;
541
+ /**
542
+ * Read the handshake context. Throws if no `<GristHandshakeProvider>` is
543
+ * mounted in the tree.
544
+ *
545
+ * Use {@link useGristHandshakeContextOptional} when the provider is optional
546
+ * (e.g. when the same component is sometimes used standalone).
547
+ */
548
+ declare function useGristHandshakeContext(): GristHandshakeContextValue;
549
+ /** Non-throwing variant. */
550
+ declare function useGristHandshakeContextOptional(): GristHandshakeContextValue | null;
551
+
552
+ type UseGristRecordSubscriptionResult = {
553
+ loading: boolean;
554
+ error: string | null;
555
+ /** Latest mappings from `grist.onRecord` (logical name → real column id, or `string[]` for `allowMultiple`). */
556
+ mappings: GristWidgetColumnMap | null;
557
+ };
558
+ /**
559
+ * Subscribes to the currently-selected row in the widget's section.
560
+ *
561
+ * The Grist event is shared across all hook instances (no leak).
562
+ */
563
+ declare function useGristRecord(cb: (record: Record<string, unknown> | null, mappings: GristWidgetColumnMap | null) => void, options?: GristReadyOptions): UseGristRecordSubscriptionResult;
564
+
565
+ /**
566
+ * Subscribes to the full row set visible in the widget's section
567
+ * (filters, sorts, and linked rows applied).
568
+ */
569
+ declare function useGristRecords(cb: (records: Record<string, unknown>[], mappings: GristWidgetColumnMap | null) => void, options?: GristReadyOptions): UseGristRecordSubscriptionResult;
570
+
571
+ type UseGristNewRecordResult = {
572
+ /** True when the user has selected the blank "new row" placeholder. */
573
+ isNewRecord: boolean;
574
+ /** Mappings reported by Grist alongside the new-record event. */
575
+ mappings: GristWidgetColumnMap | null;
576
+ loading: boolean;
577
+ error: string | null;
578
+ };
579
+ /**
580
+ * Subscribes to `grist.onNewRecord` — fires when the user selects the blank
581
+ * row in the linked section. Useful for form-style widgets that need to
582
+ * switch into "create" mode.
583
+ *
584
+ * `isNewRecord` is reset to `false` whenever a real `onRecord` payload arrives,
585
+ * so callers don't need to manage that themselves.
586
+ */
587
+ declare function useGristNewRecord(options?: GristReadyOptions): UseGristNewRecordResult;
588
+
589
+ type GristActionsStatus = "idle" | "running" | "error";
590
+ type UseGristActionsOptions = Pick<GristReadyOptions, "requiredAccess" | "columns" | "allowSelectBy" | "hasCustomOptions" | "onEditOptions">;
591
+ type UseGristActionsResult = {
592
+ status: GristActionsStatus;
593
+ error: string | null;
594
+ /** Apply raw user actions. `applyOptions.desc` is shown in the document undo stack. */
595
+ apply: (actions: GristUserActionTuple[], applyOptions?: GristApplyUserActionsOptions) => Promise<unknown>;
596
+ updateRecord: (tableId: string, recordId: number, patch: Record<string, unknown>, applyOptions?: GristApplyUserActionsOptions) => Promise<unknown>;
597
+ addRecord: (tableId: string, values: Record<string, unknown>, applyOptions?: GristApplyUserActionsOptions) => Promise<unknown>;
598
+ removeRecord: (tableId: string, recordId: number, applyOptions?: GristApplyUserActionsOptions) => Promise<unknown>;
599
+ bulkAddRecord: (tableId: string, values: Record<string, unknown[]>, applyOptions?: GristApplyUserActionsOptions) => Promise<unknown>;
600
+ bulkUpdateRecord: (tableId: string, rowIds: number[], columnarPatch: Record<string, unknown[]>, applyOptions?: GristApplyUserActionsOptions) => Promise<unknown>;
601
+ bulkRemoveRecord: (tableId: string, rowIds: number[], applyOptions?: GristApplyUserActionsOptions) => Promise<unknown>;
602
+ };
603
+ declare function useGristActions(options?: UseGristActionsOptions): UseGristActionsResult;
604
+
605
+ type UseGristMappedRecordResult<T> = {
606
+ rawRecord: Record<string, unknown> | null;
607
+ mappedRecord: T | null;
608
+ /** Current column mappings reported by Grist. */
609
+ mappings: GristWidgetColumnMap | null;
610
+ loading: boolean;
611
+ error: string | null;
612
+ /**
613
+ * Reverse-map a logical patch (`{ logicalName: value }`) into a real-column
614
+ * patch (`{ realColId: value }`) ready for `applyUserActions` /
615
+ * `useGristTableOps.update`.
616
+ * Falls back to `grist.mapColumnNamesBack` when present, otherwise resolves
617
+ * names manually from `mappings` (single-mapped columns only).
618
+ */
619
+ mapBack: (patch: Partial<T>) => Record<string, unknown>;
620
+ /** Resolve a single logical name to its current real column id. */
621
+ resolveColumnId: (logicalName: keyof T & string) => string | undefined;
622
+ };
623
+ /**
624
+ * Subscribes to the selected row, applies `grist.mapColumnNames` so widget code
625
+ * sees logical column names, and exposes `mapBack` for safe writes.
626
+ */
627
+ declare function useGristMappedRecord<T = Record<string, unknown>>(options?: GristReadyOptions): UseGristMappedRecordResult<T>;
628
+
629
+ type GristTableOpsStatus = "idle" | "running" | "error";
630
+ type UseGristTableOpsOptions = {
631
+ /**
632
+ * Table to operate on:
633
+ * - `"selected"` (default): use `grist.selectedTable` (the widget's current table).
634
+ * - any other string: use `grist.getTable(tableId)`.
635
+ */
636
+ tableId?: "selected" | string;
637
+ /** Defaults to `"full"` since these operations write to the document. */
638
+ requiredAccess?: GristReadyOptions["requiredAccess"];
639
+ } & Pick<GristReadyOptions, "columns" | "allowSelectBy" | "hasCustomOptions" | "onEditOptions">;
640
+ type UseGristTableOpsResult = {
641
+ status: GristTableOpsStatus;
642
+ error: string | null;
643
+ /** Resolves the runtime tableId (only meaningful when `tableId` is `"selected"`). */
644
+ getTableId: () => Promise<string>;
645
+ create: (records: GristTableNewRecord | GristTableNewRecord[], opOptions?: GristTableOpOptions) => Promise<GristMinimalRecord | GristMinimalRecord[]>;
646
+ update: (records: GristTableUpdateRecord | GristTableUpdateRecord[], opOptions?: GristTableOpOptions) => Promise<void>;
647
+ upsert: (records: GristTableUpsertRecord | GristTableUpsertRecord[], opOptions?: GristTableUpsertOptions) => Promise<void>;
648
+ destroy: (recordIds: number | number[]) => Promise<void>;
649
+ };
650
+ /**
651
+ * Strongly-typed CRUD wrapper around Grist's `TableOperations` (`grist.selectedTable`
652
+ * or `grist.getTable(tableId)`). Prefer this over hand-rolled `applyUserActions`.
653
+ *
654
+ * @see https://support.getgrist.com/code/interfaces/TableOperations.TableOperations/
655
+ */
656
+ declare function useGristTableOps(options?: UseGristTableOpsOptions): UseGristTableOpsResult;
657
+
658
+ type UseGristRowsFromTableOptions<T> = {
659
+ tableId: string;
660
+ /** Re-fetch on a fixed cadence (ms). Default: disabled. */
661
+ pollIntervalMs?: number;
662
+ /** Map raw rows to a domain shape (e.g. `decodeGristValue` your columns). */
663
+ transform?: (row: GristRowRecord) => GristRowRecord<T>;
664
+ requiredAccess?: GristReadyOptions["requiredAccess"];
665
+ } & Pick<GristReadyOptions, "columns" | "allowSelectBy" | "hasCustomOptions" | "onEditOptions">;
666
+ type UseGristRowsFromTableResult<T> = {
667
+ rows: GristRowRecord<T>[] | null;
668
+ loading: boolean;
669
+ error: string | null;
670
+ /** Force a re-fetch (call after writes that should be reflected immediately). */
671
+ refresh: () => Promise<void>;
672
+ };
673
+ /**
674
+ * Subscribes to a full table's rows via `docApi.fetchTable`.
675
+ *
676
+ * Unlike `useGristRecords` (which is bound to the widget's selected section),
677
+ * this hook works against any table id and **doesn't auto-update on Grist
678
+ * writes from elsewhere** — call `refresh()` after your own writes, or set
679
+ * `pollIntervalMs` for polling.
680
+ */
681
+ declare function useGristRowsFromTable<T = Record<string, unknown>>(options: UseGristRowsFromTableOptions<T>): UseGristRowsFromTableResult<T>;
682
+
683
+ type UseGristAccessTokenOptions = {
684
+ /** Default for all token requests issued by this hook. */
685
+ readOnly?: boolean;
686
+ /** Pre-fetch a token on mount so `token` is non-null when widgets render. */
687
+ prefetch?: boolean;
688
+ /** Defaults to `"full"` since tokens are used for REST calls. */
689
+ requiredAccess?: GristReadyOptions["requiredAccess"];
690
+ } & Pick<GristReadyOptions, "columns" | "allowSelectBy" | "hasCustomOptions" | "onEditOptions">;
691
+ type UseGristAccessTokenResult = {
692
+ token: GristAccessToken | null;
693
+ loading: boolean;
694
+ error: string | null;
695
+ /** Request a fresh token (Grist tokens are short-lived; do not cache aggressively). */
696
+ refresh: (overrides?: {
697
+ readOnly?: boolean;
698
+ }) => Promise<GristAccessToken>;
699
+ /** `fetch()` against Grist's REST API with auth attached. */
700
+ fetch: (path: string, init?: RequestInit & {
701
+ readOnly?: boolean;
702
+ }) => Promise<Response>;
703
+ };
704
+ /**
705
+ * Exposes `docApi.getAccessToken` plus a ready-to-use `fetch` for calling
706
+ * Grist's REST API (e.g. attachments, records, sql) from a widget.
707
+ */
708
+ declare function useGristAccessToken(options?: UseGristAccessTokenOptions): UseGristAccessTokenResult;
709
+
710
+ type UseGristSectionResult = {
711
+ /** Current widget ↔ section column mappings, refreshed on demand. */
712
+ mappings: GristWidgetColumnMap | null;
713
+ loading: boolean;
714
+ error: string | null;
715
+ /** Re-read mappings from `sectionApi`. */
716
+ refresh: () => Promise<void>;
717
+ /**
718
+ * Update the widget's declared `ColumnsToMap` (and other ready options) after
719
+ * mount. Use this to dynamically request more columns once the user has
720
+ * configured the widget.
721
+ */
722
+ configure: (options: GristReadyOptions) => Promise<void>;
723
+ };
724
+ /**
725
+ * Wraps Grist's `sectionApi` (`mappings()` + `configure()`) so widgets can
726
+ * inspect/refresh the column mapping and re-declare requirements without
727
+ * touching `grist` directly.
728
+ */
729
+ declare function useGristSection(options?: GristReadyOptions): UseGristSectionResult;
730
+
731
+ type UseGristWidgetOptionsConfig<T> = {
732
+ /** Default values returned before Grist responds and merged with stored options. */
733
+ defaults: T;
734
+ /** Debounce window for `setOptions` writes (ms). Default: 250. */
735
+ debounceMs?: number;
736
+ /**
737
+ * Optional namespace inside `grist.setOptions`. When set, this hook reads/writes
738
+ * `grist.setOption(namespace, value)` instead of the whole options blob — useful
739
+ * to share the options bag with `hasCustomOptions` / unrelated widget state.
740
+ */
741
+ namespace?: string;
742
+ requiredAccess?: GristReadyOptions["requiredAccess"];
743
+ } & Pick<GristReadyOptions, "columns" | "allowSelectBy" | "hasCustomOptions" | "onEditOptions">;
744
+ type UseGristWidgetOptionsResult<T> = {
745
+ options: T;
746
+ /** True until Grist has responded with the initial `onOptions` payload. */
747
+ loading: boolean;
748
+ error: string | null;
749
+ /** Replace the options object (debounced write to Grist). */
750
+ setOptions: (next: T) => void;
751
+ /** Patch a subset of keys (debounced write to Grist). */
752
+ patchOptions: (patch: Partial<T>) => void;
753
+ /** Reset to `defaults` (immediate write to Grist). */
754
+ reset: () => Promise<void>;
755
+ };
756
+ /**
757
+ * Persist a typed object of widget settings via `grist.onOptions` / `setOptions`.
758
+ *
759
+ * - Defaults are returned immediately (no flicker before Grist responds).
760
+ * - Writes are debounced and batched.
761
+ * - `hasCustomOptions` defaults to `true` so Grist exposes the gear icon — pass
762
+ * `hasCustomOptions: false` to opt out.
763
+ */
764
+ declare function useGristWidgetOptions<T extends Record<string, unknown>>(config: UseGristWidgetOptionsConfig<T>): UseGristWidgetOptionsResult<T>;
765
+
766
+ type UseGristTableOptions = {
767
+ requiredAccess?: GristRequiredAccess;
768
+ /**
769
+ * `'current'` (default): follow the widget’s selected table.
770
+ * Any other string: fixed table id (no `grist.selectedTable` polling).
771
+ */
772
+ tableId?: "current" | string;
773
+ } & Pick<GristReadyOptions, "columns" | "allowSelectBy" | "hasCustomOptions" | "onEditOptions">;
774
+ type UseGristTableResult = {
775
+ tableId: string | null;
776
+ loading: boolean;
777
+ error: string | null;
778
+ refresh: () => Promise<void>;
779
+ fetchTable: (targetTableId: string) => Promise<GristColumnarTable>;
780
+ listTables: () => Promise<string[]>;
781
+ getDocName: () => Promise<string>;
782
+ fetchSelectedTable: (fetchOptions?: GristFetchSelectedOptions) => Promise<GristColumnarTable>;
783
+ fetchSelectedRecord: (rowId: number, fetchOptions?: GristFetchSelectedOptions) => Promise<Record<string, unknown>>;
784
+ };
785
+ /**
786
+ * Selected or fixed table id plus document reads — no raw `docApi` on the result.
787
+ */
788
+ declare function useGristTable(options?: UseGristTableOptions): UseGristTableResult;
789
+
790
+ export { type GristActionsStatus, GristCapabilities, GristGeneration, type GristHandshakeContextValue, GristHandshakeProvider, type GristHandshakeProviderProps, GristMappingSource, type GristTableOpsStatus, GristTerminationReason, GristWidgetSnapshot, type UseGristAccessTokenOptions, type UseGristAccessTokenResult, type UseGristActionsOptions, type UseGristActionsResult, type UseGristHandshakeOptions, type UseGristHandshakeResult, type UseGristMappedRecordResult, type UseGristNewRecordResult, type UseGristReadyOptions, type UseGristReadyResult, type UseGristRecordSubscriptionResult, type UseGristRowsFromTableOptions, type UseGristRowsFromTableResult, type UseGristSectionResult, type UseGristTableOpsOptions, type UseGristTableOpsResult, type UseGristTableOptions, type UseGristTableResult, type UseGristWidgetOptionsConfig, type UseGristWidgetOptionsResult, useGristAccessToken, useGristActions, useGristAvailability, useGristCapabilities, useGristHandshake, useGristHandshakeContext, useGristHandshakeContextOptional, useGristMappedRecord, useGristNewRecord, useGristReady, useGristRecord, useGristRecords, useGristRowsFromTable, useGristSection, useGristTable, useGristTableOps, useGristWidgetOptions };