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