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,522 @@
1
+ import { j as GristReplicaColumn, p as GristRowRecord, d as GristDocApi, o as GristRequiredAccess, b as GristColumnsToMap, w as GristWidgetColumnMap, i as GristReadyOptions } from './types-Fb2wplkv.cjs';
2
+ import { z, ZodType } from 'zod';
3
+
4
+ type GristSafeParseIssueCode = "grist_error" | "invalid_format" | "type_mismatch" | "helper_mismatch" | "zod_error";
5
+ type GristSafeParseIssue = {
6
+ code: GristSafeParseIssueCode;
7
+ message: string;
8
+ };
9
+ type GristSafeRefValue = {
10
+ rowId: number;
11
+ display?: string | null;
12
+ };
13
+ type GristSafeRefListValue = {
14
+ rowIds: number[];
15
+ displays?: string[];
16
+ };
17
+ declare const gristSafeRefValueSchema: z.ZodObject<{
18
+ rowId: z.ZodNumber;
19
+ display: z.ZodOptional<z.ZodNullable<z.ZodString>>;
20
+ }, z.core.$strip>;
21
+ declare const gristSafeRefListValueSchema: z.ZodObject<{
22
+ rowIds: z.ZodArray<z.ZodNumber>;
23
+ displays: z.ZodOptional<z.ZodArray<z.ZodString>>;
24
+ }, z.core.$strip>;
25
+ declare const gristCellValueSchema: z.ZodUnion<readonly [z.ZodNull, z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodDate, z.ZodArray<z.ZodUnknown>, z.ZodObject<{
26
+ rowId: z.ZodNumber;
27
+ display: z.ZodOptional<z.ZodNullable<z.ZodString>>;
28
+ }, z.core.$strip>, z.ZodObject<{
29
+ rowIds: z.ZodArray<z.ZodNumber>;
30
+ displays: z.ZodOptional<z.ZodArray<z.ZodString>>;
31
+ }, z.core.$strip>]>;
32
+ type GristCellValue = z.infer<typeof gristCellValueSchema>;
33
+ type GristSafeParseCellResult<TValue = unknown> = {
34
+ ok: boolean;
35
+ typedValue: TValue | null;
36
+ displayValue: unknown;
37
+ raw: unknown;
38
+ issues: GristSafeParseIssue[];
39
+ };
40
+ type RefMode = "id" | "detailed";
41
+ type SafeParseGristValuesOptions<TValue = unknown> = {
42
+ values: readonly unknown[];
43
+ column?: GristReplicaColumn;
44
+ helperValues?: readonly unknown[];
45
+ schema?: ZodType<TValue>;
46
+ fallbackUntypedToString?: boolean;
47
+ refMode?: RefMode;
48
+ };
49
+ type SafeParseGristTableDataOptions = {
50
+ columns?: Record<string, GristReplicaColumn>;
51
+ displayHelpersByRef?: Record<string, string>;
52
+ order?: "none" | "id" | "grist";
53
+ fallbackUntypedToString?: boolean;
54
+ refMode?: RefMode;
55
+ };
56
+ type GristSafeTableCellIssue = GristSafeParseIssue & {
57
+ rowIndex: number;
58
+ colId: string;
59
+ };
60
+ type GristSafeChoiceTokenMeta = {
61
+ value: string;
62
+ label: string;
63
+ fillColor?: string;
64
+ textColor?: string;
65
+ valid: boolean;
66
+ };
67
+ type GristSafeTableCellMeta = {
68
+ rowIndex: number;
69
+ colId: string;
70
+ ok: boolean;
71
+ issues: GristSafeParseIssue[];
72
+ choiceTokens?: GristSafeChoiceTokenMeta[];
73
+ };
74
+ type SafeParseGristTableDataResult<TRow extends Record<string, unknown> = Record<string, unknown>> = {
75
+ rows: GristRowRecord<TRow>[];
76
+ cellIssues: GristSafeTableCellIssue[];
77
+ cellMeta: GristSafeTableCellMeta[];
78
+ cellMetaByKey: Record<string, GristSafeTableCellMeta>;
79
+ cellResultsByColumn: Record<string, GristSafeParseCellResult[]>;
80
+ };
81
+ /** Maps `Ref:*` / `RefList:*` column ids to their paired `gristHelper_*` column id (same rules as safe-parse). */
82
+ declare function inferDisplayHelpersByRef(columns: Record<string, GristReplicaColumn>): Record<string, string>;
83
+ declare function safeParseGristValues<TValue = unknown>(options: SafeParseGristValuesOptions<TValue>): GristSafeParseCellResult<TValue>[];
84
+ declare function safeParseGristTableData<TRow extends Record<string, unknown> = Record<string, unknown>>(data: GristColumnarTable, options?: SafeParseGristTableDataOptions): SafeParseGristTableDataResult<TRow>;
85
+
86
+ /**
87
+ * Column-oriented table payload from `grist.docApi.fetchTable`.
88
+ */
89
+ type GristColumnarTable = Record<string, unknown[]>;
90
+ type GristTableRowsOrder = "none" | "id" | "grist";
91
+ type FetchGristTableRowsOptions = {
92
+ /** Optional schema metadata map to decode cells (except `id`) with `decodeGristValue`. */
93
+ columns?: Record<string, GristReplicaColumn>;
94
+ /**
95
+ * Row ordering strategy.
96
+ * - `grist` (default): `manualSort` ascending when present, fallback `id`.
97
+ * - `id`: numeric `id` ascending.
98
+ * - `none`: preserve raw `fetchTable` row order.
99
+ */
100
+ order?: GristTableRowsOrder;
101
+ /**
102
+ * Enable safe parsing with issue reporting.
103
+ * - `true`: use safe parsing with defaults.
104
+ * - object: pass safe-parse options (`displayHelpersByRef`, `refMode`, ...).
105
+ */
106
+ safeParse?: boolean | Omit<SafeParseGristTableDataOptions, "columns" | "order">;
107
+ /** Optional callback fired with per-cell parse issues when `safeParse` is enabled. */
108
+ onSafeParseIssues?: (issues: GristSafeTableCellIssue[]) => void;
109
+ /** Optional callback fired with enriched per-cell safe-parse metadata. */
110
+ onSafeParseCellMeta?: (meta: GristSafeTableCellMeta[]) => void;
111
+ /** Optional callback fired with keyed per-cell metadata (`${rowId}:${colId}`). */
112
+ onSafeParseCellMetaByKey?: (metaByKey: Record<string, GristSafeTableCellMeta>) => void;
113
+ };
114
+ /**
115
+ * Project safe-parse rows to UI display rows by taking each cell's `displayValue`.
116
+ * Useful for grids that render human-friendly values while keeping safe-cell
117
+ * objects available for logic and metadata.
118
+ */
119
+ declare function safeParseRowsToDisplayRows(rows: ReadonlyArray<Record<string, unknown>>): GristRowRecord<Record<string, unknown>>[];
120
+ /** Row count from columnar `fetchTable` data (prefers `id` column length). */
121
+ declare function computeRowCount(data: Record<string, unknown[]>): number;
122
+ /**
123
+ * Decode columnar `fetchTable` into replica row objects (same convention as the playground grid).
124
+ */
125
+ declare function decodeColumnarToReplicaRows(data: GristColumnarTable, columnIds: string[], columns: Record<string, GristReplicaColumn>, maxRows?: number): GristRowRecord[];
126
+ /** Turn `fetchTable` output into row objects (order follows the `id` column). */
127
+ declare function tableDataToRows<T = Record<string, unknown>>(data: GristColumnarTable): GristRowRecord<T>[];
128
+ /**
129
+ * Sort rows by Grist's display order: `manualSort` ascending when present on
130
+ * any row, otherwise numeric `id` ascending.
131
+ */
132
+ declare function sortRowsByGristOrder<T = Record<string, unknown>>(rows: readonly GristRowRecord<T>[]): GristRowRecord<T>[];
133
+ /** Row index where `id` matches `rowId`, or `null`. */
134
+ declare function findRowIndexById(data: GristColumnarTable, rowId: number | string): number | null;
135
+ /** Single row at `index`, or `null` if out of range. */
136
+ declare function rowAtIndex<T = Record<string, unknown>>(data: GristColumnarTable, index: number): GristRowRecord<T> | null;
137
+ /**
138
+ * Fetch a single row from any table by id. Convenience around
139
+ * `docApi.fetchTable` + {@link findRowIndexById} + {@link rowAtIndex}.
140
+ */
141
+ declare function fetchGristRow<T = Record<string, unknown>>(docApi: GristDocApi, tableId: string, rowId: number): Promise<GristRowRecord<T> | null>;
142
+ /**
143
+ * Same row materialization as {@link fetchGristTableRows} after `fetchTable`
144
+ * returns columnar data. Hosts that already have columnar data (e.g. the
145
+ * emulator building from in-memory rows) can use this and skip `docApi.fetchTable`.
146
+ */
147
+ declare function materializeGristRowsFromColumnar<T extends Record<string, unknown> = Record<string, unknown>>(data: GristColumnarTable, options?: FetchGristTableRowsOptions): GristRowRecord<T>[];
148
+ /**
149
+ * Fetch a table and return rows as objects, optionally decoded from schema and sorted.
150
+ */
151
+ declare function fetchGristTableRows<T extends Record<string, unknown> = Record<string, unknown>>(docApi: GristDocApi, tableId: string, options: FetchGristTableRowsOptions & {
152
+ safeParse: true | Omit<SafeParseGristTableDataOptions, "columns" | "order">;
153
+ }): Promise<GristRowRecord<{
154
+ [K in keyof T]: GristSafeParseCellResult<T[K]> | null;
155
+ }>[]>;
156
+ declare function fetchGristTableRows<T extends Record<string, unknown> = Record<string, unknown>>(docApi: GristDocApi, tableId: string, options?: FetchGristTableRowsOptions): Promise<GristRowRecord<T>[]>;
157
+
158
+ /**
159
+ * Grist `docApi.applyUserActions` payloads are arrays of user-action tuples.
160
+ * @see https://support.getgrist.com/code/interfaces/grist_plugin_api.GristDocAPI/#applyuseractions
161
+ */
162
+ type GristUserActionTuple = unknown[];
163
+ /** `["UpdateRecord", tableId, rowId, { col: value, ... }]` */
164
+ declare function gristUpdateRecordAction(tableId: string, rowId: number, patch: Record<string, unknown>): GristUserActionTuple;
165
+ /** `["AddRecord", tableId, null, { col: value, ... }]` */
166
+ declare function gristAddRecordAction(tableId: string, values: Record<string, unknown>): GristUserActionTuple;
167
+ /** `["RemoveRecord", tableId, rowId]` */
168
+ declare function gristRemoveRecordAction(tableId: string, rowId: number): GristUserActionTuple;
169
+ /** `["BulkUpdateRecord", tableId, rowIds, { col: value[], ... }]` — column arrays must align with `rowIds`. */
170
+ declare function gristBulkUpdateRecordAction(tableId: string, rowIds: number[], columnarPatch: Record<string, unknown[]>): GristUserActionTuple;
171
+ /**
172
+ * `["BulkAddRecord", tableId, rowIds | nullArray, { col: value[], ... }]`.
173
+ * Pass `null` (or omit) for `rowIds` to let Grist allocate ids; in user-action
174
+ * format Grist expects an array of nulls of the same length as the value arrays.
175
+ */
176
+ declare function gristBulkAddRecordAction(tableId: string, values: Record<string, unknown[]>, rowIds?: Array<number | null>): GristUserActionTuple;
177
+ /** `["BulkRemoveRecord", tableId, rowIds]` */
178
+ declare function gristBulkRemoveRecordAction(tableId: string, rowIds: number[]): GristUserActionTuple;
179
+ /** `["AddColumn", tableId, colId, colInfo]` — `colInfo` shape depends on Grist version / column type. */
180
+ declare function gristAddColumnAction(tableId: string, colId: string, colInfo: Record<string, unknown>): GristUserActionTuple;
181
+ /** `["AddVisibleColumn", tableId, colId, colInfo]` — like `AddColumn` but also adds the column to the current view section. */
182
+ declare function gristAddVisibleColumnAction(tableId: string, colId: string, colInfo: Record<string, unknown>): GristUserActionTuple;
183
+ /** `["RenameColumn", tableId, colId, newColId]` */
184
+ declare function gristRenameColumnAction(tableId: string, colId: string, newColId: string): GristUserActionTuple;
185
+ /** `["ModifyColumn", tableId, colId, colInfo]` */
186
+ declare function gristModifyColumnAction(tableId: string, colId: string, colInfo: Record<string, unknown>): GristUserActionTuple;
187
+ /** `["RemoveColumn", tableId, colId]` */
188
+ declare function gristRemoveColumnAction(tableId: string, colId: string): GristUserActionTuple;
189
+ /** `["AddTable", tableId, tableInfo]` */
190
+ declare function gristAddTableAction(tableId: string, tableInfo: Record<string, unknown>): GristUserActionTuple;
191
+ /** `["RenameTable", tableId, newTableId]` */
192
+ declare function gristRenameTableAction(tableId: string, newTableId: string): GristUserActionTuple;
193
+ /** `["RemoveTable", tableId]` */
194
+ declare function gristRemoveTableAction(tableId: string): GristUserActionTuple;
195
+ /** `["DuplicateTable", tableId, newTableId, options?]` */
196
+ declare function gristDuplicateTableAction(tableId: string, newTableId: string, options?: Record<string, unknown>): GristUserActionTuple;
197
+ /** `["ReplaceTableData", tableId, rowIds, columnarData]` */
198
+ declare function gristReplaceTableDataAction(tableId: string, rowIds: Array<number | null>, columnarData: Record<string, unknown[]>): GristUserActionTuple;
199
+ /** `["SetDocumentInfo", info]` */
200
+ declare function gristSetDocumentInfoAction(info: Record<string, unknown>): GristUserActionTuple;
201
+
202
+ type GristColumnLike = {
203
+ formula?: string;
204
+ };
205
+ type GristNoiseColumnsOptions = {
206
+ excludeId?: boolean;
207
+ excludeGristHelpers?: boolean;
208
+ excludeLoggingColumns?: boolean;
209
+ /**
210
+ * When `true`, treat Grist's `manualSort` column as noise (omit from filtered replicas).
211
+ * Default `false` — `manualSort` is kept unless you opt in.
212
+ */
213
+ excludeManualSort?: boolean;
214
+ };
215
+ /**
216
+ * Grist-centric "technical column" detection used by document hooks and widgets.
217
+ *
218
+ * - `id` (when {@link GristNoiseColumnsOptions.excludeId} is true — default)
219
+ * - `manualSort` (only when {@link GristNoiseColumnsOptions.excludeManualSort} is true)
220
+ * - `gristHelper_*` (when `excludeGristHelpers` is true — default)
221
+ * - trigger/logging formulas (`NOW()`, `user.*`) (when `excludeLoggingColumns` is true — default)
222
+ */
223
+ declare function isGristNoiseColumn(colId: string, col?: GristColumnLike, options?: GristNoiseColumnsOptions): boolean;
224
+
225
+ type GristAccessToken = {
226
+ token: string;
227
+ baseUrl: string;
228
+ };
229
+ /**
230
+ * Returns an access token suitable for calling Grist's REST API directly.
231
+ * Tokens are short-lived; results are cached briefly and coalesced per readOnly flag.
232
+ */
233
+ declare function getGristAccessToken(docApi: GristDocApi, options?: {
234
+ readOnly?: boolean;
235
+ }): Promise<GristAccessToken>;
236
+ /**
237
+ * `fetch()` against Grist's REST API with the doc access token attached.
238
+ */
239
+ declare function fetchWithGristAuth(docApi: GristDocApi, path: string, init?: RequestInit & {
240
+ readOnly?: boolean;
241
+ }): Promise<Response>;
242
+
243
+ /**
244
+ * Pure type contracts for the handshake / synchronization state machine.
245
+ *
246
+ * The machine models five orthogonal axes that together describe the widget's
247
+ * relationship with the Grist host:
248
+ *
249
+ * 1. `LIFECYCLE` — protocol handshake (one-shot via `grist.ready`).
250
+ * 2. `LINK` — RPC channel health (continuous, post-online).
251
+ * 3. `AUTHZ` — effective access level granted by the host.
252
+ * 4. `CONFIG` — column mapping contract with the end-user.
253
+ * 5. `SYNC` — freshness of each event stream and ancillary RPCs.
254
+ *
255
+ * The legacy public `status` ("booting" / "ready" / "unavailable" / "error")
256
+ * is a **pure projection** of the snapshot, computed in `./derive.ts`.
257
+ *
258
+ * No runtime side effects in this file — types only.
259
+ */
260
+
261
+ /** Monotone counter bumped on reload / restart / link recovery / access widening. */
262
+ type GristGeneration = number & {
263
+ readonly __brand: "GristGeneration";
264
+ };
265
+ type GristLifecyclePhase = "idle" | "detecting" | "negotiating" | "online" | "terminated";
266
+ type GristTerminationReason = {
267
+ kind: "script_timeout";
268
+ attempts: number;
269
+ } | {
270
+ kind: "not_embedded";
271
+ } | {
272
+ kind: "ready_failed";
273
+ message: string;
274
+ } | {
275
+ kind: "ready_timeout";
276
+ afterMs: number;
277
+ } | {
278
+ kind: "host_rejected";
279
+ message: string;
280
+ } | {
281
+ kind: "manual_terminate";
282
+ };
283
+ type GristLifecycle = {
284
+ phase: "idle";
285
+ } | {
286
+ phase: "detecting";
287
+ attempts: number;
288
+ startedAtMs: number;
289
+ } | {
290
+ phase: "negotiating";
291
+ access: GristRequiredAccess;
292
+ startedAtMs: number;
293
+ } | {
294
+ phase: "online";
295
+ sinceMs: number;
296
+ declaredAccess: GristRequiredAccess;
297
+ } | {
298
+ phase: "terminated";
299
+ reason: GristTerminationReason;
300
+ atMs: number;
301
+ };
302
+ type GristLinkState = "connected" | "stale" | "lost";
303
+ type GristLink = {
304
+ state: GristLinkState;
305
+ /** ms timestamp of last successful RPC (or heartbeat). `null` before any. */
306
+ lastSuccessMs: number | null;
307
+ /** Consecutive RPC failures since the last success. */
308
+ missedPings: number;
309
+ };
310
+ type GristAuthz = {
311
+ state: "pending";
312
+ declared: GristRequiredAccess;
313
+ } | {
314
+ state: "granted";
315
+ /** Effective level granted by the host. May differ from `declared`. */
316
+ level: GristRequiredAccess;
317
+ declared: GristRequiredAccess;
318
+ /** True if granted level >= a previously granted level for the same widget. */
319
+ widened: boolean;
320
+ } | {
321
+ state: "denied";
322
+ declared: GristRequiredAccess;
323
+ message?: string;
324
+ };
325
+ /**
326
+ * Source that produced a mapping payload, used for deterministic merge by
327
+ * {@link MappingResolver} (see `./mapping-resolver.ts`).
328
+ */
329
+ type GristMappingSource = "section_api" | "stream_record" | "stream_records" | "stream_new_record";
330
+ type GristMappingComplete = {
331
+ state: "complete";
332
+ declared: GristColumnsToMap;
333
+ mappings: GristWidgetColumnMap;
334
+ /** `"merged"` is used internally when multiple sources contributed. */
335
+ source: GristMappingSource | "merged";
336
+ reportedAtMs: number;
337
+ };
338
+ type GristMappingIncomplete = {
339
+ state: "incomplete";
340
+ declared: GristColumnsToMap;
341
+ mappings: GristWidgetColumnMap;
342
+ missing: readonly string[];
343
+ emptyMultiples: readonly string[];
344
+ source: GristMappingSource | "merged";
345
+ reportedAtMs: number;
346
+ };
347
+ type GristMappingState =
348
+ /** Widget did not declare any `columns` in `grist.ready`. */
349
+ {
350
+ state: "not_declared";
351
+ }
352
+ /** Columns declared but no host report yet received. */
353
+ | {
354
+ state: "pending";
355
+ declared: GristColumnsToMap;
356
+ declaredAtMs: number;
357
+ } | GristMappingIncomplete | GristMappingComplete
358
+ /** User just modified the mapping; previous payload preserved to avoid UI flicker. */
359
+ | {
360
+ state: "invalidating";
361
+ previous: GristMappingIncomplete | GristMappingComplete;
362
+ sinceMs: number;
363
+ }
364
+ /** Declared but no host report after a timeout. Soft state — payload may still arrive. */
365
+ | {
366
+ state: "unreported";
367
+ declared: GristColumnsToMap;
368
+ afterMs: number;
369
+ };
370
+ type GristConfig = {
371
+ mappings: GristMappingState;
372
+ };
373
+ type GristStreamFreshness = {
374
+ state: "cold";
375
+ } | {
376
+ state: "warming";
377
+ sinceMs: number;
378
+ } | {
379
+ state: "hot";
380
+ lastEventMs: number;
381
+ generation: GristGeneration;
382
+ payloadHash: string;
383
+ } | {
384
+ state: "stale";
385
+ lastEventMs: number;
386
+ reason: "link_lost" | "tab_hidden" | "host_quiet";
387
+ };
388
+ type GristCurrentTableState = {
389
+ state: "unknown";
390
+ } | {
391
+ state: "known";
392
+ tableId: string;
393
+ refreshing: boolean;
394
+ }
395
+ /** Local error (e.g. `selectedTable.getTableId()` rejected) — does NOT propagate to global status. */
396
+ | {
397
+ state: "errored";
398
+ message: string;
399
+ sinceMs: number;
400
+ };
401
+ type GristSync = {
402
+ record: GristStreamFreshness;
403
+ records: GristStreamFreshness;
404
+ options: GristStreamFreshness;
405
+ newRecord: GristStreamFreshness;
406
+ currentTable: GristCurrentTableState;
407
+ };
408
+ type GristWidgetSnapshot = {
409
+ generation: GristGeneration;
410
+ lifecycle: GristLifecycle;
411
+ link: GristLink;
412
+ authz: GristAuthz;
413
+ config: GristConfig;
414
+ sync: GristSync;
415
+ };
416
+ /** Mirrors the long-standing public type, kept for compat. */
417
+ type GristWidgetStatus$1 = "booting" | "ready" | "unavailable" | "error";
418
+ /**
419
+ * Pre-computed booleans that widget code should consume directly instead of
420
+ * re-deriving from `status` + mapping flags + writes flags.
421
+ *
422
+ * Every field is a **pure function of the snapshot** and is referentially
423
+ * stable across renders that don't change the snapshot's relevant slices.
424
+ */
425
+ type GristCapabilities = {
426
+ /** `lifecycle.online` + `link != lost` + `authz.state === "granted"` with read or higher. */
427
+ canRead: boolean;
428
+ /** `canRead` + (`mappings.state` is `complete` or `not_declared`). */
429
+ canRender: boolean;
430
+ /** `canRender` + `authz.level === "full"`. Required before any write. */
431
+ canWriteRecords: boolean;
432
+ /** `canWriteRecords` + `sync.currentTable.state === "known"`. */
433
+ canWriteSchema: boolean;
434
+ /** `canRead` + `link === "connected"`. */
435
+ canFetchTable: boolean;
436
+ /** `canRead` + `sync.record.state === "hot"` + mappings not invalidating. */
437
+ hasFreshSelection: boolean;
438
+ /** `mappings.state === "complete" || "not_declared"`. */
439
+ hasUsableMappings: boolean;
440
+ /** Logical column names still missing from user mapping (incomplete only). */
441
+ missingMappings: readonly string[];
442
+ /** `allowMultiple` columns mapped to `[]` (incomplete only). */
443
+ emptyMultipleMappings: readonly string[];
444
+ };
445
+
446
+ /** Canonical 4-state status — projected from the handshake snapshot. */
447
+ type GristWidgetStatus = GristWidgetStatus$1;
448
+ type UseGristCoreOptions = GristReadyOptions & {
449
+ availabilityMaxAttempts?: number;
450
+ availabilityIntervalMs?: number;
451
+ logWhenNotEmbedded?: boolean;
452
+ };
453
+ type UseGristCoreResult = {
454
+ status: GristWidgetStatus;
455
+ isAvailable: boolean | null;
456
+ isReady: boolean;
457
+ error: string | null;
458
+ docApi: GristDocApi | null;
459
+ /** Derived from the handshake snapshot — same values as `useGristCapabilities()` on `/advanced`. */
460
+ capabilities: GristCapabilities;
461
+ reload: () => Promise<void>;
462
+ };
463
+
464
+ type ListTablesOptions = {
465
+ /** When true, include system tables (prefixed with `_grist` or `GristHidden_`). Default: false. */
466
+ includeSystem?: boolean;
467
+ };
468
+ type GristColumnInfo = {
469
+ colId: string;
470
+ type?: string;
471
+ label?: string;
472
+ isFormula?: boolean;
473
+ formula?: string;
474
+ description?: string;
475
+ };
476
+ type ListColumnsOptions = GristNoiseColumnsOptions & {
477
+ /** When true, include system tables' columns. Default: false. */
478
+ includeSystem?: boolean;
479
+ };
480
+
481
+ type UseGristWidgetOptionsFromContextConfig<T> = {
482
+ /** Default values returned before Grist responds and merged with stored options. */
483
+ defaults: T;
484
+ /** Debounce window for `setOptions` writes (ms). Default: 250. */
485
+ debounceMs?: number;
486
+ /**
487
+ * Optional namespace key. When set, this hook reads/writes
488
+ * `options[namespace]` instead of the whole options blob.
489
+ */
490
+ namespace?: string;
491
+ };
492
+ type UseGristWidgetOptionsFromContextResult<T> = {
493
+ options: T;
494
+ /** True until Grist has responded with the initial `onOptions` payload. */
495
+ loading: boolean;
496
+ /** Replace the options object (debounced write to Grist). */
497
+ setOptions: (next: T) => void;
498
+ /** Patch a subset of keys (debounced write to Grist). */
499
+ patchOptions: (patch: Partial<T>) => void;
500
+ /** Reset to `defaults` (immediate write to Grist). */
501
+ reset: () => Promise<void>;
502
+ };
503
+ /**
504
+ * Typed widget options hook designed for use **inside a `<GristWidgetProvider>`**.
505
+ *
506
+ * Unlike `useGristWidgetOptions()` (which calls `useGristReady()` and conflicts
507
+ * with the provider), this hook reads from the provider's existing context —
508
+ * `widgetOptions` for reactive reads, and `patchWidgetOptions` / `setWidgetOptions`
509
+ * for writes.
510
+ *
511
+ * ```tsx
512
+ * function MyConfigPanel() {
513
+ * const { options, patchOptions } = useGristWidgetOptionsFromContext({
514
+ * defaults: { color: '#FF0000', showLabels: true },
515
+ * })
516
+ * return <input value={options.color} onChange={e => patchOptions({ color: e.target.value })} />
517
+ * }
518
+ * ```
519
+ */
520
+ declare function useGristWidgetOptionsFromContext<T extends Record<string, unknown>>(config: UseGristWidgetOptionsFromContextConfig<T>): UseGristWidgetOptionsFromContextResult<T>;
521
+
522
+ export { gristAddTableAction as $, type GristSync as A, type GristTableRowsOrder as B, type GristTerminationReason as C, type GristUserActionTuple as D, type GristWidgetSnapshot as E, type FetchGristTableRowsOptions as F, type GristAccessToken as G, type GristWidgetStatus as H, type GristWidgetStatus$1 as I, type ListTablesOptions as J, type SafeParseGristTableDataResult as K, type ListColumnsOptions as L, type SafeParseGristValuesOptions as M, type UseGristCoreResult as N, type UseGristWidgetOptionsFromContextConfig as O, type UseGristWidgetOptionsFromContextResult as P, computeRowCount as Q, decodeColumnarToReplicaRows as R, type SafeParseGristTableDataOptions as S, fetchGristRow as T, type UseGristCoreOptions as U, fetchGristTableRows as V, fetchWithGristAuth as W, findRowIndexById as X, getGristAccessToken as Y, gristAddColumnAction as Z, gristAddRecordAction as _, type GristAuthz as a, gristAddVisibleColumnAction as a0, gristBulkAddRecordAction as a1, gristBulkRemoveRecordAction as a2, gristBulkUpdateRecordAction as a3, gristCellValueSchema as a4, gristDuplicateTableAction as a5, gristModifyColumnAction as a6, gristRemoveColumnAction as a7, gristRemoveRecordAction as a8, gristRemoveTableAction as a9, gristRenameColumnAction as aa, gristRenameTableAction as ab, gristReplaceTableDataAction as ac, gristSafeRefListValueSchema as ad, gristSafeRefValueSchema as ae, gristSetDocumentInfoAction as af, gristUpdateRecordAction as ag, inferDisplayHelpersByRef as ah, isGristNoiseColumn as ai, materializeGristRowsFromColumnar as aj, rowAtIndex as ak, safeParseGristTableData as al, safeParseGristValues as am, safeParseRowsToDisplayRows as an, sortRowsByGristOrder as ao, tableDataToRows as ap, useGristWidgetOptionsFromContext as aq, type GristCapabilities as b, type GristCellValue as c, type GristColumnInfo as d, type GristColumnLike as e, type GristColumnarTable as f, type GristConfig as g, type GristCurrentTableState as h, type GristGeneration as i, type GristLifecycle as j, type GristLifecyclePhase as k, type GristLink as l, type GristLinkState as m, type GristMappingComplete as n, type GristMappingIncomplete as o, type GristMappingSource as p, type GristMappingState as q, type GristNoiseColumnsOptions as r, type GristSafeParseCellResult as s, type GristSafeParseIssue as t, type GristSafeParseIssueCode as u, type GristSafeRefListValue as v, type GristSafeRefValue as w, type GristSafeTableCellIssue as x, type GristSafeTableCellMeta as y, type GristStreamFreshness as z };
package/package.json ADDED
@@ -0,0 +1,131 @@
1
+ {
2
+ "name": "grist-widget-sdk",
3
+ "version": "0.2.0",
4
+ "description": "React hooks and helpers for Grist custom widgets",
5
+ "license": "ISC",
6
+ "engines": {
7
+ "node": ">=18"
8
+ },
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/ArthurBlanchon/grist-widget-sdk.git"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/ArthurBlanchon/grist-widget-sdk/issues"
18
+ },
19
+ "homepage": "https://grist-widgets.com",
20
+ "keywords": [
21
+ "grist",
22
+ "widget",
23
+ "custom-widget",
24
+ "react",
25
+ "typescript"
26
+ ],
27
+ "sideEffects": false,
28
+ "type": "module",
29
+ "main": "./dist/index.cjs",
30
+ "module": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "import": {
35
+ "types": "./dist/index.d.ts",
36
+ "default": "./dist/index.js"
37
+ },
38
+ "require": {
39
+ "types": "./dist/index.d.cts",
40
+ "default": "./dist/index.cjs"
41
+ }
42
+ },
43
+ "./advanced": {
44
+ "import": {
45
+ "types": "./dist/advanced.d.ts",
46
+ "default": "./dist/advanced.js"
47
+ },
48
+ "require": {
49
+ "types": "./dist/advanced.d.cts",
50
+ "default": "./dist/advanced.cjs"
51
+ }
52
+ },
53
+ "./emulator": {
54
+ "import": {
55
+ "types": "./dist/emulator.d.ts",
56
+ "default": "./dist/emulator.js"
57
+ },
58
+ "require": {
59
+ "types": "./dist/emulator.d.cts",
60
+ "default": "./dist/emulator.cjs"
61
+ }
62
+ },
63
+ "./emulator/transports": {
64
+ "import": {
65
+ "types": "./dist/emulator/transports.d.ts",
66
+ "default": "./dist/emulator/transports.js"
67
+ },
68
+ "require": {
69
+ "types": "./dist/emulator/transports.d.cts",
70
+ "default": "./dist/emulator/transports.cjs"
71
+ }
72
+ },
73
+ "./emulator/testing": {
74
+ "import": {
75
+ "types": "./dist/emulator/testing.d.ts",
76
+ "default": "./dist/emulator/testing.js"
77
+ },
78
+ "require": {
79
+ "types": "./dist/emulator/testing.d.cts",
80
+ "default": "./dist/emulator/testing.cjs"
81
+ }
82
+ },
83
+ "./emulator/devtools": {
84
+ "import": {
85
+ "types": "./dist/emulator/devtools.d.ts",
86
+ "default": "./dist/emulator/devtools.js"
87
+ },
88
+ "require": {
89
+ "types": "./dist/emulator/devtools.d.cts",
90
+ "default": "./dist/emulator/devtools.cjs"
91
+ }
92
+ }
93
+ },
94
+ "files": [
95
+ "dist",
96
+ "CHANGELOG.md"
97
+ ],
98
+ "peerDependencies": {
99
+ "react": ">=18",
100
+ "react-dom": ">=18"
101
+ },
102
+ "devDependencies": {
103
+ "@testing-library/dom": "^10",
104
+ "@testing-library/jest-dom": "^6",
105
+ "@testing-library/react": "^16.3.2",
106
+ "@types/node": "^24.12.0",
107
+ "@types/react": "^19.2.14",
108
+ "@types/react-dom": "^19.2.3",
109
+ "@vitejs/plugin-react": "^6.0.1",
110
+ "fast-check": "^4.8.0",
111
+ "grist-plugin-api": "^0.0.1",
112
+ "jsdom": "^29.0.2",
113
+ "tsup": "^8",
114
+ "typescript": "^5.9.3",
115
+ "vite": "^8.0.9",
116
+ "vitest": "^4.1.4"
117
+ },
118
+ "dependencies": {
119
+ "grain-rpc": "^0.1.7",
120
+ "zod": "^4.3.6"
121
+ },
122
+ "scripts": {
123
+ "build": "tsup",
124
+ "dev": "tsup --watch",
125
+ "test": "vitest --run --project unit --project emulator --project docs",
126
+ "test:unit": "vitest --run --project unit",
127
+ "test:emulator": "vitest --run --project emulator --passWithNoTests",
128
+ "bench": "vitest run --project bench",
129
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.vitest.json --noEmit"
130
+ }
131
+ }