grist-widget-sdk 0.3.27 → 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.
- package/CHANGELOG.md +45 -0
- package/README.md +1 -1
- package/dist/app.cjs +1139 -4336
- package/dist/app.cjs.map +1 -1
- package/dist/app.d.cts +1 -3
- package/dist/app.d.ts +1 -3
- package/dist/app.js +3 -7
- package/dist/chunk-4ZGVKGDF.js +204 -0
- package/dist/chunk-4ZGVKGDF.js.map +1 -0
- package/dist/{chunk-YQNMIL33.js → chunk-B5ZZNLVS.js} +528 -181
- package/dist/chunk-B5ZZNLVS.js.map +1 -0
- package/dist/chunk-O7NY46QY.js +1830 -0
- package/dist/chunk-O7NY46QY.js.map +1 -0
- package/dist/{chunk-6Y2IONGJ.js → chunk-P6VTEKWO.js} +113 -14
- package/dist/chunk-P6VTEKWO.js.map +1 -0
- package/dist/emulator/testing.cjs +1504 -4822
- package/dist/emulator/testing.cjs.map +1 -1
- package/dist/emulator/testing.d.cts +3 -6
- package/dist/emulator/testing.d.ts +3 -6
- package/dist/emulator/testing.js +7 -20
- package/dist/emulator/testing.js.map +1 -1
- package/dist/emulator.cjs +59 -249
- package/dist/emulator.cjs.map +1 -1
- package/dist/emulator.d.cts +3 -5
- package/dist/emulator.d.ts +3 -5
- package/dist/emulator.js +5 -11
- package/dist/index.cjs +1373 -4859
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -260
- package/dist/index.d.ts +81 -260
- package/dist/index.js +8 -158
- package/dist/{inline-DpcT8tV9.d.ts → templates-CGmRIQBV.d.cts} +89 -35
- package/dist/{inline-C7DkvuET.d.cts → templates-Cq94qOcf.d.ts} +89 -35
- package/dist/{types-D3JxUCfL.d.cts → types-DBStn0Dk.d.cts} +2 -26
- package/dist/{types-D3JxUCfL.d.ts → types-DBStn0Dk.d.ts} +2 -26
- package/dist/use-grist-BK0eke9K.d.cts +358 -0
- package/dist/use-grist-C5DMZ3wU.d.ts +358 -0
- package/package.json +8 -37
- package/dist/advanced.cjs +0 -2799
- package/dist/advanced.cjs.map +0 -1
- package/dist/advanced.d.cts +0 -822
- package/dist/advanced.d.ts +0 -822
- package/dist/advanced.js +0 -872
- package/dist/advanced.js.map +0 -1
- package/dist/chunk-22U7WAY3.js +0 -463
- package/dist/chunk-22U7WAY3.js.map +0 -1
- package/dist/chunk-64SVRWW7.js +0 -758
- package/dist/chunk-64SVRWW7.js.map +0 -1
- package/dist/chunk-6Y2IONGJ.js.map +0 -1
- package/dist/chunk-B4AKVSWV.js +0 -2362
- package/dist/chunk-B4AKVSWV.js.map +0 -1
- package/dist/chunk-ELGVOHFD.js +0 -32
- package/dist/chunk-ELGVOHFD.js.map +0 -1
- package/dist/chunk-GTTD5HPO.js +0 -36
- package/dist/chunk-GTTD5HPO.js.map +0 -1
- package/dist/chunk-NO6FYX33.js +0 -2084
- package/dist/chunk-NO6FYX33.js.map +0 -1
- package/dist/chunk-OPTOYD2Y.js +0 -277
- package/dist/chunk-OPTOYD2Y.js.map +0 -1
- package/dist/chunk-PM3B4WYV.js +0 -12
- package/dist/chunk-PM3B4WYV.js.map +0 -1
- package/dist/chunk-REV5AUXD.js +0 -25
- package/dist/chunk-REV5AUXD.js.map +0 -1
- package/dist/chunk-YQNMIL33.js.map +0 -1
- package/dist/create-grist-emulator-BCqp1BF9.d.ts +0 -81
- package/dist/create-grist-emulator-DBM8OSIR.d.cts +0 -81
- package/dist/emulator/devtools.cjs +0 -1404
- package/dist/emulator/devtools.cjs.map +0 -1
- package/dist/emulator/devtools.d.cts +0 -48
- package/dist/emulator/devtools.d.ts +0 -48
- package/dist/emulator/devtools.js +0 -506
- package/dist/emulator/devtools.js.map +0 -1
- package/dist/emulator/transports.cjs +0 -1132
- package/dist/emulator/transports.cjs.map +0 -1
- package/dist/emulator/transports.d.cts +0 -38
- package/dist/emulator/transports.d.ts +0 -38
- package/dist/emulator/transports.js +0 -12
- package/dist/emulator/transports.js.map +0 -1
- package/dist/templates-8k2LKoO-.d.cts +0 -24
- package/dist/templates-DIwi_mvI.d.ts +0 -24
- package/dist/use-grist-reads-DUMvJgV7.d.ts +0 -480
- package/dist/use-grist-reads-DpjVuI0H.d.cts +0 -480
- package/dist/use-grist-types-DdOLEJXN.d.cts +0 -499
- package/dist/use-grist-types-kwxoFeub.d.ts +0 -499
- package/dist/use-grist-widget-options-from-context-CGOlQ5Vc.d.cts +0 -43
- package/dist/use-grist-widget-options-from-context-CGOlQ5Vc.d.ts +0 -43
package/dist/advanced.cjs
DELETED
|
@@ -1,2799 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/sdk/advanced/index.ts
|
|
21
|
-
var advanced_exports = {};
|
|
22
|
-
__export(advanced_exports, {
|
|
23
|
-
GristHandshakeProvider: () => GristHandshakeProvider,
|
|
24
|
-
useAmbientGristHandshake: () => useAmbientGristHandshake,
|
|
25
|
-
useGristAccessToken: () => useGristAccessToken,
|
|
26
|
-
useGristActions: () => useGristActions,
|
|
27
|
-
useGristAvailability: () => useGristAvailability,
|
|
28
|
-
useGristCapabilities: () => useGristCapabilities,
|
|
29
|
-
useGristHandshake: () => useGristHandshake,
|
|
30
|
-
useGristHandshakeContext: () => useGristHandshakeContext,
|
|
31
|
-
useGristHandshakeContextOptional: () => useGristHandshakeContextOptional,
|
|
32
|
-
useGristMappedRecord: () => useGristMappedRecord,
|
|
33
|
-
useGristNewRecord: () => useGristNewRecord,
|
|
34
|
-
useGristReady: () => useGristReady,
|
|
35
|
-
useGristRecord: () => useGristRecord,
|
|
36
|
-
useGristRecords: () => useGristRecords,
|
|
37
|
-
useGristRowsFromTable: () => useGristRowsFromTable,
|
|
38
|
-
useGristSection: () => useGristSection,
|
|
39
|
-
useGristTable: () => useGristTable,
|
|
40
|
-
useGristTableOps: () => useGristTableOps,
|
|
41
|
-
useGristWidgetOptions: () => useGristWidgetOptions,
|
|
42
|
-
useGristWidgetOptionsFromContext: () => useGristWidgetOptionsFromContext
|
|
43
|
-
});
|
|
44
|
-
module.exports = __toCommonJS(advanced_exports);
|
|
45
|
-
|
|
46
|
-
// src/sdk/internal/hooks/use-grist-core.ts
|
|
47
|
-
var import_react2 = require("react");
|
|
48
|
-
|
|
49
|
-
// src/sdk/internal/handshake/types.ts
|
|
50
|
-
var ZERO_GENERATION = 0;
|
|
51
|
-
function nextGeneration(current) {
|
|
52
|
-
return current + 1;
|
|
53
|
-
}
|
|
54
|
-
var MAPPING_SOURCE_PRIORITY = [
|
|
55
|
-
"section_api",
|
|
56
|
-
"stream_record",
|
|
57
|
-
"stream_records",
|
|
58
|
-
"stream_new_record"
|
|
59
|
-
];
|
|
60
|
-
function initialGristSnapshot() {
|
|
61
|
-
return {
|
|
62
|
-
generation: ZERO_GENERATION,
|
|
63
|
-
lifecycle: { phase: "idle" },
|
|
64
|
-
link: { state: "connected", lastSuccessMs: null, missedPings: 0 },
|
|
65
|
-
authz: { state: "pending", declared: "read table" },
|
|
66
|
-
config: { mappings: { state: "not_declared" } },
|
|
67
|
-
sync: {
|
|
68
|
-
record: { state: "cold" },
|
|
69
|
-
records: { state: "cold" },
|
|
70
|
-
options: { state: "cold" },
|
|
71
|
-
newRecord: { state: "cold" },
|
|
72
|
-
currentTable: { state: "unknown" }
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// src/sdk/internal/handshake/actions.ts
|
|
78
|
-
function hasGenerationStamp(action) {
|
|
79
|
-
return "generation" in action && typeof action.generation === "number";
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// src/sdk/helpers/grist-mappings.ts
|
|
83
|
-
function asColumnSpec(entry) {
|
|
84
|
-
return typeof entry === "string" ? { name: entry } : entry;
|
|
85
|
-
}
|
|
86
|
-
function validateColumnMappings(spec, mappings) {
|
|
87
|
-
const missing = [];
|
|
88
|
-
const emptyMultiples = [];
|
|
89
|
-
if (!spec) return { ok: true, pending: false, missing, emptyMultiples };
|
|
90
|
-
const m = mappings ?? {};
|
|
91
|
-
for (const raw of spec) {
|
|
92
|
-
const col = asColumnSpec(raw);
|
|
93
|
-
if (col.optional) continue;
|
|
94
|
-
const value = m[col.name];
|
|
95
|
-
if (col.allowMultiple) {
|
|
96
|
-
if (value === void 0) {
|
|
97
|
-
missing.push(col.name);
|
|
98
|
-
} else if (!Array.isArray(value) || value.length === 0) {
|
|
99
|
-
emptyMultiples.push(col.name);
|
|
100
|
-
}
|
|
101
|
-
} else {
|
|
102
|
-
if (value === void 0 || value === null || value === "") missing.push(col.name);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return {
|
|
106
|
-
ok: missing.length === 0 && emptyMultiples.length === 0,
|
|
107
|
-
pending: false,
|
|
108
|
-
missing,
|
|
109
|
-
emptyMultiples
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// src/sdk/internal/handshake/mapping-resolver.ts
|
|
114
|
-
var MappingResolver = class {
|
|
115
|
-
constructor(declared, generation) {
|
|
116
|
-
/** Latest accepted payload per source. */
|
|
117
|
-
this.payloads = /* @__PURE__ */ new Map();
|
|
118
|
-
/** Last resolved state, used to detect duplicate ingests. */
|
|
119
|
-
this.lastResolvedHash = null;
|
|
120
|
-
this.declared = declared;
|
|
121
|
-
this.currentGeneration = generation;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Replace the declared columns (e.g. user passed a new `columns` array to
|
|
125
|
-
* the provider). Clears accumulated payloads — they were validated against
|
|
126
|
-
* the previous declaration.
|
|
127
|
-
*/
|
|
128
|
-
redeclare(declared, atGeneration) {
|
|
129
|
-
this.declared = declared;
|
|
130
|
-
this.currentGeneration = atGeneration;
|
|
131
|
-
this.payloads.clear();
|
|
132
|
-
this.lastResolvedHash = null;
|
|
133
|
-
}
|
|
134
|
-
/** Bump the active generation. Payloads from older generations are dropped. */
|
|
135
|
-
bumpGeneration(next) {
|
|
136
|
-
if (next <= this.currentGeneration) return;
|
|
137
|
-
this.currentGeneration = next;
|
|
138
|
-
for (const [src, p] of this.payloads) {
|
|
139
|
-
if (p.generation < next) this.payloads.delete(src);
|
|
140
|
-
}
|
|
141
|
-
this.lastResolvedHash = null;
|
|
142
|
-
}
|
|
143
|
-
/** Forget every payload and reset the hash. The declaration is preserved. */
|
|
144
|
-
reset() {
|
|
145
|
-
this.payloads.clear();
|
|
146
|
-
this.lastResolvedHash = null;
|
|
147
|
-
}
|
|
148
|
-
/** Cheap, deterministic snapshot for tests/debug. */
|
|
149
|
-
knownSources() {
|
|
150
|
-
return [...this.payloads.keys()];
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Ingest one payload. Returns a {@link ResolveOutcome}:
|
|
154
|
-
* - `still_pending` — no payloads accepted yet.
|
|
155
|
-
* - `no_change/stale_*` — input generation is older than current.
|
|
156
|
-
* - `no_change/duplicate_payload` — input did not move the resolution.
|
|
157
|
-
* - `resolved` — new resolution differs from the previous one.
|
|
158
|
-
*
|
|
159
|
-
* Call {@link current} to read the latest resolution without affecting
|
|
160
|
-
* the duplicate-detection hash.
|
|
161
|
-
*/
|
|
162
|
-
ingest(input) {
|
|
163
|
-
if (input.generation < this.currentGeneration) {
|
|
164
|
-
return { kind: "no_change", reason: "stale_generation" };
|
|
165
|
-
}
|
|
166
|
-
if (input.generation > this.currentGeneration) {
|
|
167
|
-
this.bumpGeneration(input.generation);
|
|
168
|
-
}
|
|
169
|
-
this.payloads.set(input.source, input);
|
|
170
|
-
const computed = this.compute();
|
|
171
|
-
if (computed === null) return { kind: "still_pending" };
|
|
172
|
-
const hash = computeMappingHash(
|
|
173
|
-
computed.mappings,
|
|
174
|
-
computed.state === "complete",
|
|
175
|
-
computed.source
|
|
176
|
-
);
|
|
177
|
-
if (hash === this.lastResolvedHash) {
|
|
178
|
-
return { kind: "no_change", reason: "duplicate_payload" };
|
|
179
|
-
}
|
|
180
|
-
this.lastResolvedHash = hash;
|
|
181
|
-
return { kind: "resolved", state: computed };
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Return the resolver's current resolution (pure read). Equivalent to
|
|
185
|
-
* `ingest(...)` without mutating any state — used by tests and by the
|
|
186
|
-
* reducer when re-applying after `MAPPING_INVALIDATION_RESOLVE`.
|
|
187
|
-
*/
|
|
188
|
-
current() {
|
|
189
|
-
const computed = this.compute();
|
|
190
|
-
if (computed === null) return { kind: "still_pending" };
|
|
191
|
-
return { kind: "resolved", state: computed };
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Compute the current best mapping from accumulated payloads (pure).
|
|
195
|
-
*
|
|
196
|
-
* Priority: higher-priority sources **override** specific keys from
|
|
197
|
-
* lower-priority sources. Keys absent from a higher-priority source fall
|
|
198
|
-
* back to lower-priority sources. This matches the production behaviour
|
|
199
|
-
* where `sectionApi.mappings()` is authoritative for the keys it reports,
|
|
200
|
-
* but stream payloads can fill in keys the section_api hasn't reported yet.
|
|
201
|
-
*/
|
|
202
|
-
compute() {
|
|
203
|
-
if (this.payloads.size === 0) return null;
|
|
204
|
-
const merged = {};
|
|
205
|
-
let latestReportedAt = 0;
|
|
206
|
-
const contributors = [];
|
|
207
|
-
for (const src of [...MAPPING_SOURCE_PRIORITY].reverse()) {
|
|
208
|
-
const p = this.payloads.get(src);
|
|
209
|
-
if (!p) continue;
|
|
210
|
-
contributors.push(src);
|
|
211
|
-
if (p.receivedAtMs > latestReportedAt) latestReportedAt = p.receivedAtMs;
|
|
212
|
-
if (p.payload === null) continue;
|
|
213
|
-
for (const [k, v] of Object.entries(p.payload)) {
|
|
214
|
-
merged[k] = v;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
if (contributors.length === 0) return null;
|
|
218
|
-
const validation = validateColumnMappings(this.declared, merged);
|
|
219
|
-
const source = contributors.length === 1 ? contributors[0] : "merged";
|
|
220
|
-
if (validation.ok) {
|
|
221
|
-
return {
|
|
222
|
-
state: "complete",
|
|
223
|
-
declared: this.declared,
|
|
224
|
-
mappings: merged,
|
|
225
|
-
source,
|
|
226
|
-
reportedAtMs: latestReportedAt
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
return {
|
|
230
|
-
state: "incomplete",
|
|
231
|
-
declared: this.declared,
|
|
232
|
-
mappings: merged,
|
|
233
|
-
missing: validation.missing,
|
|
234
|
-
emptyMultiples: validation.emptyMultiples,
|
|
235
|
-
source,
|
|
236
|
-
reportedAtMs: latestReportedAt
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
function computeMappingHash(mappings, ok, source) {
|
|
241
|
-
const keys = Object.keys(mappings).sort();
|
|
242
|
-
const body = keys.map((k) => {
|
|
243
|
-
const v = mappings[k];
|
|
244
|
-
if (Array.isArray(v)) return `${k}=[${v.join(",")}]`;
|
|
245
|
-
return `${k}=${v ?? ""}`;
|
|
246
|
-
}).join("|");
|
|
247
|
-
return `${ok ? "OK" : "KO"}::${source}::${body}`;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// src/sdk/internal/handshake/access.ts
|
|
251
|
-
var ACCESS_RANK = {
|
|
252
|
-
none: 0,
|
|
253
|
-
"read table": 1,
|
|
254
|
-
full: 2
|
|
255
|
-
};
|
|
256
|
-
var GRIST_ACCESS_LEVELS = ["none", "read table", "full"];
|
|
257
|
-
function parseGristRequiredAccess(raw) {
|
|
258
|
-
if (!raw) return null;
|
|
259
|
-
return GRIST_ACCESS_LEVELS.includes(raw) ? raw : null;
|
|
260
|
-
}
|
|
261
|
-
function isAccessSufficient(granted, required) {
|
|
262
|
-
return ACCESS_RANK[granted] >= ACCESS_RANK[required];
|
|
263
|
-
}
|
|
264
|
-
function gristAccessLevelLabel(level) {
|
|
265
|
-
switch (level) {
|
|
266
|
-
case "none":
|
|
267
|
-
return "No document access";
|
|
268
|
-
case "read table":
|
|
269
|
-
return "Read selected table";
|
|
270
|
-
case "full":
|
|
271
|
-
return "Full document access";
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
function formatInsufficientAccessMessage(required, _granted) {
|
|
275
|
-
const need = gristAccessLevelLabel(required);
|
|
276
|
-
return `Set Access level to at least "${need}" in the Custom widget settings.`;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// src/sdk/internal/handshake/reducer.ts
|
|
280
|
-
function createReducerContext() {
|
|
281
|
-
return { resolver: null };
|
|
282
|
-
}
|
|
283
|
-
var DEFAULT_REDUCER_CONFIG = {
|
|
284
|
-
staleAfterMissed: 1,
|
|
285
|
-
lostAfterMissed: 3
|
|
286
|
-
};
|
|
287
|
-
function gristReducer(snapshot, action, ctx, config = DEFAULT_REDUCER_CONFIG) {
|
|
288
|
-
if (hasGenerationStamp(action) && action.generation < snapshot.generation) {
|
|
289
|
-
return snapshot;
|
|
290
|
-
}
|
|
291
|
-
switch (action.type) {
|
|
292
|
-
// -- Lifecycle --------------------------------------------------------
|
|
293
|
-
case "DETECT_START":
|
|
294
|
-
return reduceDetectStart(snapshot, action.atMs);
|
|
295
|
-
case "DETECT_TICK":
|
|
296
|
-
return reduceDetectTick(snapshot, action.attempts);
|
|
297
|
-
case "DETECT_AVAILABLE":
|
|
298
|
-
return reduceDetectAvailable(snapshot, action.atMs);
|
|
299
|
-
case "DETECT_TIMEOUT":
|
|
300
|
-
return reduceTerminate(snapshot, {
|
|
301
|
-
kind: "script_timeout",
|
|
302
|
-
attempts: action.attempts
|
|
303
|
-
}, action.atMs);
|
|
304
|
-
case "NOT_EMBEDDED":
|
|
305
|
-
return reduceTerminate(snapshot, { kind: "not_embedded" }, action.atMs);
|
|
306
|
-
case "NEGOTIATE_START":
|
|
307
|
-
return reduceNegotiateStart(snapshot, action.access, action.atMs);
|
|
308
|
-
case "NEGOTIATE_SUCCESS":
|
|
309
|
-
return reduceNegotiateSuccess(snapshot, action.access, action.grantedLevel, action.atMs);
|
|
310
|
-
case "NEGOTIATE_FAIL":
|
|
311
|
-
return reduceTerminate(snapshot, action.reason, action.atMs);
|
|
312
|
-
case "TERMINATE":
|
|
313
|
-
return reduceTerminate(snapshot, action.reason, action.atMs);
|
|
314
|
-
case "AUTHZ_REPORT":
|
|
315
|
-
return reduceAuthzReport(snapshot, action.level, action.atMs);
|
|
316
|
-
// -- Link -------------------------------------------------------------
|
|
317
|
-
case "RPC_SUCCESS":
|
|
318
|
-
case "HEARTBEAT_OK":
|
|
319
|
-
return reduceLinkSuccess(snapshot, action.atMs);
|
|
320
|
-
case "RPC_FAILURE":
|
|
321
|
-
case "HEARTBEAT_FAIL":
|
|
322
|
-
return reduceLinkFailure(snapshot, action.atMs, config);
|
|
323
|
-
// -- Generation -------------------------------------------------------
|
|
324
|
-
case "BUMP_GENERATION":
|
|
325
|
-
return reduceBumpGeneration(snapshot, ctx);
|
|
326
|
-
// -- Config / mappings -----------------------------------------------
|
|
327
|
-
case "MAPPING_DECLARE":
|
|
328
|
-
case "MAPPING_REPORT":
|
|
329
|
-
case "MAPPING_INVALIDATE":
|
|
330
|
-
case "MAPPING_INVALIDATION_RESOLVE":
|
|
331
|
-
case "MAPPING_TIMEOUT":
|
|
332
|
-
return reduceMapping(snapshot, action, ctx);
|
|
333
|
-
// -- Sync -------------------------------------------------------------
|
|
334
|
-
case "STREAM_RECEIVED":
|
|
335
|
-
case "STREAM_WARMING":
|
|
336
|
-
case "STREAM_STALE":
|
|
337
|
-
return reduceStream(snapshot, action);
|
|
338
|
-
case "CURRENT_TABLE_KNOWN":
|
|
339
|
-
return updateSync(snapshot, {
|
|
340
|
-
...snapshot.sync,
|
|
341
|
-
currentTable: { state: "known", tableId: action.tableId, refreshing: false }
|
|
342
|
-
});
|
|
343
|
-
case "CURRENT_TABLE_REFRESH_START":
|
|
344
|
-
if (snapshot.sync.currentTable.state !== "known") return snapshot;
|
|
345
|
-
return updateSync(snapshot, {
|
|
346
|
-
...snapshot.sync,
|
|
347
|
-
currentTable: { ...snapshot.sync.currentTable, refreshing: true }
|
|
348
|
-
});
|
|
349
|
-
case "CURRENT_TABLE_ERROR":
|
|
350
|
-
return updateSync(snapshot, {
|
|
351
|
-
...snapshot.sync,
|
|
352
|
-
currentTable: { state: "errored", message: action.message, sinceMs: action.atMs }
|
|
353
|
-
});
|
|
354
|
-
default: {
|
|
355
|
-
const _exhaustive = action;
|
|
356
|
-
void _exhaustive;
|
|
357
|
-
return snapshot;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
function reduceDetectStart(snapshot, atMs) {
|
|
362
|
-
if (snapshot.lifecycle.phase !== "idle") return snapshot;
|
|
363
|
-
return {
|
|
364
|
-
...snapshot,
|
|
365
|
-
lifecycle: { phase: "detecting", attempts: 0, startedAtMs: atMs }
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
function reduceDetectTick(snapshot, attempts) {
|
|
369
|
-
if (snapshot.lifecycle.phase !== "detecting") return snapshot;
|
|
370
|
-
if (attempts === snapshot.lifecycle.attempts) return snapshot;
|
|
371
|
-
return {
|
|
372
|
-
...snapshot,
|
|
373
|
-
lifecycle: { ...snapshot.lifecycle, attempts }
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
function reduceDetectAvailable(snapshot, atMs) {
|
|
377
|
-
if (snapshot.lifecycle.phase !== "detecting" && snapshot.lifecycle.phase !== "idle") {
|
|
378
|
-
return snapshot;
|
|
379
|
-
}
|
|
380
|
-
return {
|
|
381
|
-
...snapshot,
|
|
382
|
-
lifecycle: {
|
|
383
|
-
phase: "negotiating",
|
|
384
|
-
access: snapshot.authz.declared,
|
|
385
|
-
startedAtMs: atMs
|
|
386
|
-
}
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
function reduceNegotiateStart(snapshot, access, atMs) {
|
|
390
|
-
if (snapshot.lifecycle.phase === "terminated") return snapshot;
|
|
391
|
-
return {
|
|
392
|
-
...snapshot,
|
|
393
|
-
authz: { state: "pending", declared: access },
|
|
394
|
-
lifecycle: snapshot.lifecycle.phase === "online" ? snapshot.lifecycle : { phase: "negotiating", access, startedAtMs: atMs }
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
function authzDeclared(snapshot) {
|
|
398
|
-
return snapshot.authz.declared;
|
|
399
|
-
}
|
|
400
|
-
function reduceAuthzReport(snapshot, level, _atMs) {
|
|
401
|
-
if (snapshot.lifecycle.phase !== "online") return snapshot;
|
|
402
|
-
const declared = authzDeclared(snapshot);
|
|
403
|
-
if (isAccessSufficient(level, declared)) {
|
|
404
|
-
const previousLevel = snapshot.authz.state === "granted" ? snapshot.authz.level : null;
|
|
405
|
-
const widened = previousLevel !== null && ACCESS_RANK[level] > ACCESS_RANK[previousLevel];
|
|
406
|
-
const nextAuthz2 = {
|
|
407
|
-
state: "granted",
|
|
408
|
-
level,
|
|
409
|
-
declared,
|
|
410
|
-
widened
|
|
411
|
-
};
|
|
412
|
-
if (snapshot.authz.state === "granted" && snapshot.authz.level === nextAuthz2.level && snapshot.authz.declared === nextAuthz2.declared && snapshot.authz.widened === nextAuthz2.widened) {
|
|
413
|
-
return snapshot;
|
|
414
|
-
}
|
|
415
|
-
return { ...snapshot, authz: nextAuthz2 };
|
|
416
|
-
}
|
|
417
|
-
const message = formatInsufficientAccessMessage(declared, level);
|
|
418
|
-
const nextAuthz = { state: "denied", declared, message };
|
|
419
|
-
if (snapshot.authz.state === "denied" && snapshot.authz.declared === nextAuthz.declared && snapshot.authz.message === nextAuthz.message) {
|
|
420
|
-
return snapshot;
|
|
421
|
-
}
|
|
422
|
-
return { ...snapshot, authz: nextAuthz };
|
|
423
|
-
}
|
|
424
|
-
function reduceNegotiateSuccess(snapshot, access, grantedLevel, atMs) {
|
|
425
|
-
if (snapshot.lifecycle.phase === "terminated") return snapshot;
|
|
426
|
-
const previousLevel = snapshot.authz.state === "granted" ? snapshot.authz.level : null;
|
|
427
|
-
const widened = previousLevel !== null && ACCESS_RANK[grantedLevel] > ACCESS_RANK[previousLevel];
|
|
428
|
-
return {
|
|
429
|
-
...snapshot,
|
|
430
|
-
authz: {
|
|
431
|
-
state: "granted",
|
|
432
|
-
level: grantedLevel,
|
|
433
|
-
declared: access,
|
|
434
|
-
widened
|
|
435
|
-
},
|
|
436
|
-
lifecycle: snapshot.lifecycle.phase === "online" ? snapshot.lifecycle : { phase: "online", sinceMs: atMs, declaredAccess: access },
|
|
437
|
-
link: { state: "connected", lastSuccessMs: atMs, missedPings: 0 }
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
function reduceTerminate(snapshot, reason, atMs) {
|
|
441
|
-
if (snapshot.lifecycle.phase === "terminated") return snapshot;
|
|
442
|
-
const lifecycle = { phase: "terminated", reason, atMs };
|
|
443
|
-
return { ...snapshot, lifecycle };
|
|
444
|
-
}
|
|
445
|
-
function reduceLinkSuccess(snapshot, atMs) {
|
|
446
|
-
if (snapshot.lifecycle.phase !== "online") return snapshot;
|
|
447
|
-
const link = { state: "connected", lastSuccessMs: atMs, missedPings: 0 };
|
|
448
|
-
if (snapshot.link.state === link.state && snapshot.link.lastSuccessMs === link.lastSuccessMs && snapshot.link.missedPings === link.missedPings) {
|
|
449
|
-
return snapshot;
|
|
450
|
-
}
|
|
451
|
-
return { ...snapshot, link };
|
|
452
|
-
}
|
|
453
|
-
function reduceLinkFailure(snapshot, _atMs, config) {
|
|
454
|
-
if (snapshot.lifecycle.phase !== "online") return snapshot;
|
|
455
|
-
const missed = snapshot.link.missedPings + 1;
|
|
456
|
-
let state = snapshot.link.state;
|
|
457
|
-
if (missed >= config.lostAfterMissed) state = "lost";
|
|
458
|
-
else if (missed >= config.staleAfterMissed) state = "stale";
|
|
459
|
-
return {
|
|
460
|
-
...snapshot,
|
|
461
|
-
link: { state, lastSuccessMs: snapshot.link.lastSuccessMs, missedPings: missed }
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
function reduceBumpGeneration(snapshot, ctx) {
|
|
465
|
-
const next = nextGeneration(snapshot.generation);
|
|
466
|
-
ctx.resolver?.bumpGeneration(next);
|
|
467
|
-
return {
|
|
468
|
-
...snapshot,
|
|
469
|
-
generation: next,
|
|
470
|
-
// Marking streams as warming again ensures hooks treat the data as
|
|
471
|
-
// "in transit" until a fresh event arrives under the new generation.
|
|
472
|
-
sync: {
|
|
473
|
-
...snapshot.sync,
|
|
474
|
-
record: degradeForRefresh(snapshot.sync.record, snapshot.generation),
|
|
475
|
-
records: degradeForRefresh(snapshot.sync.records, snapshot.generation),
|
|
476
|
-
options: degradeForRefresh(snapshot.sync.options, snapshot.generation),
|
|
477
|
-
newRecord: degradeForRefresh(snapshot.sync.newRecord, snapshot.generation)
|
|
478
|
-
}
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
function degradeForRefresh(freshness, prevGeneration) {
|
|
482
|
-
if (freshness.state === "hot" && freshness.generation === prevGeneration) {
|
|
483
|
-
return { state: "stale", lastEventMs: freshness.lastEventMs, reason: "host_quiet" };
|
|
484
|
-
}
|
|
485
|
-
return freshness;
|
|
486
|
-
}
|
|
487
|
-
function reduceMapping(snapshot, action, ctx) {
|
|
488
|
-
switch (action.type) {
|
|
489
|
-
case "MAPPING_DECLARE": {
|
|
490
|
-
if (action.declared.length === 0) {
|
|
491
|
-
if (snapshot.config.mappings.state === "not_declared") return snapshot;
|
|
492
|
-
ctx.resolver = null;
|
|
493
|
-
return {
|
|
494
|
-
...snapshot,
|
|
495
|
-
config: { mappings: { state: "not_declared" } }
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
if (!ctx.resolver) {
|
|
499
|
-
ctx.resolver = new MappingResolver(action.declared, snapshot.generation);
|
|
500
|
-
} else {
|
|
501
|
-
ctx.resolver.redeclare(action.declared, snapshot.generation);
|
|
502
|
-
}
|
|
503
|
-
return {
|
|
504
|
-
...snapshot,
|
|
505
|
-
config: {
|
|
506
|
-
mappings: {
|
|
507
|
-
state: "pending",
|
|
508
|
-
declared: action.declared,
|
|
509
|
-
declaredAtMs: action.atMs
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
case "MAPPING_REPORT": {
|
|
515
|
-
if (!ctx.resolver) return snapshot;
|
|
516
|
-
const outcome = ctx.resolver.ingest({
|
|
517
|
-
source: action.source,
|
|
518
|
-
payload: action.payload,
|
|
519
|
-
receivedAtMs: action.atMs,
|
|
520
|
-
generation: action.generation
|
|
521
|
-
});
|
|
522
|
-
if (outcome.kind !== "resolved") return snapshot;
|
|
523
|
-
return applyResolved(snapshot, outcome.state);
|
|
524
|
-
}
|
|
525
|
-
case "MAPPING_INVALIDATE": {
|
|
526
|
-
const previous = snapshot.config.mappings;
|
|
527
|
-
if (previous.state !== "complete" && previous.state !== "incomplete") {
|
|
528
|
-
return snapshot;
|
|
529
|
-
}
|
|
530
|
-
return {
|
|
531
|
-
...snapshot,
|
|
532
|
-
config: {
|
|
533
|
-
mappings: {
|
|
534
|
-
state: "invalidating",
|
|
535
|
-
previous,
|
|
536
|
-
sinceMs: action.atMs
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
case "MAPPING_INVALIDATION_RESOLVE": {
|
|
542
|
-
if (snapshot.config.mappings.state !== "invalidating") return snapshot;
|
|
543
|
-
if (!ctx.resolver) {
|
|
544
|
-
return {
|
|
545
|
-
...snapshot,
|
|
546
|
-
config: { mappings: snapshot.config.mappings.previous }
|
|
547
|
-
};
|
|
548
|
-
}
|
|
549
|
-
const outcome = ctx.resolver.current();
|
|
550
|
-
if (outcome.kind !== "resolved") {
|
|
551
|
-
return {
|
|
552
|
-
...snapshot,
|
|
553
|
-
config: { mappings: snapshot.config.mappings.previous }
|
|
554
|
-
};
|
|
555
|
-
}
|
|
556
|
-
return applyResolved(snapshot, outcome.state);
|
|
557
|
-
}
|
|
558
|
-
case "MAPPING_TIMEOUT": {
|
|
559
|
-
const previous = snapshot.config.mappings;
|
|
560
|
-
if (previous.state !== "pending") return snapshot;
|
|
561
|
-
return {
|
|
562
|
-
...snapshot,
|
|
563
|
-
config: {
|
|
564
|
-
mappings: {
|
|
565
|
-
state: "unreported",
|
|
566
|
-
declared: previous.declared,
|
|
567
|
-
afterMs: action.atMs - previous.declaredAtMs
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
function applyResolved(snapshot, resolved) {
|
|
575
|
-
const existing = snapshot.config.mappings;
|
|
576
|
-
if ((existing.state === "complete" || existing.state === "incomplete") && sameResolved(existing, resolved)) {
|
|
577
|
-
return snapshot;
|
|
578
|
-
}
|
|
579
|
-
return { ...snapshot, config: { mappings: resolved } };
|
|
580
|
-
}
|
|
581
|
-
function sameResolved(a, b) {
|
|
582
|
-
if (a.state !== b.state) return false;
|
|
583
|
-
if (a.state !== "complete" && a.state !== "incomplete") return false;
|
|
584
|
-
if (a.source !== b.source) return false;
|
|
585
|
-
const ak = Object.keys(a.mappings);
|
|
586
|
-
const bk = Object.keys(b.mappings);
|
|
587
|
-
if (ak.length !== bk.length) return false;
|
|
588
|
-
for (const k of ak) {
|
|
589
|
-
const av = a.mappings[k];
|
|
590
|
-
const bv = b.mappings[k];
|
|
591
|
-
if (Array.isArray(av) && Array.isArray(bv)) {
|
|
592
|
-
if (av.length !== bv.length) return false;
|
|
593
|
-
for (let i = 0; i < av.length; i++) if (av[i] !== bv[i]) return false;
|
|
594
|
-
} else if (av !== bv) {
|
|
595
|
-
return false;
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
return true;
|
|
599
|
-
}
|
|
600
|
-
function reduceStream(snapshot, action) {
|
|
601
|
-
if (action.type === "CURRENT_TABLE_KNOWN" || action.type === "CURRENT_TABLE_REFRESH_START" || action.type === "CURRENT_TABLE_ERROR") {
|
|
602
|
-
return snapshot;
|
|
603
|
-
}
|
|
604
|
-
const stream = action.stream;
|
|
605
|
-
const current = snapshot.sync[stream];
|
|
606
|
-
if (action.type === "STREAM_WARMING") {
|
|
607
|
-
if (current.state === "warming" || current.state === "hot") return snapshot;
|
|
608
|
-
return updateStream(snapshot, stream, { state: "warming", sinceMs: action.atMs });
|
|
609
|
-
}
|
|
610
|
-
if (action.type === "STREAM_STALE") {
|
|
611
|
-
if (current.state !== "hot") return snapshot;
|
|
612
|
-
return updateStream(snapshot, stream, {
|
|
613
|
-
state: "stale",
|
|
614
|
-
lastEventMs: current.lastEventMs,
|
|
615
|
-
reason: action.reason
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
if (current.state === "hot" && current.generation === action.generation && current.payloadHash === action.payloadHash) {
|
|
619
|
-
return snapshot;
|
|
620
|
-
}
|
|
621
|
-
return updateStream(snapshot, stream, {
|
|
622
|
-
state: "hot",
|
|
623
|
-
lastEventMs: action.atMs,
|
|
624
|
-
generation: action.generation,
|
|
625
|
-
payloadHash: action.payloadHash
|
|
626
|
-
});
|
|
627
|
-
}
|
|
628
|
-
function updateStream(snapshot, stream, next) {
|
|
629
|
-
return {
|
|
630
|
-
...snapshot,
|
|
631
|
-
sync: { ...snapshot.sync, [stream]: next }
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
function updateSync(snapshot, sync) {
|
|
635
|
-
return { ...snapshot, sync };
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// src/sdk/internal/handshake/derive.ts
|
|
639
|
-
function deriveStatus(snapshot) {
|
|
640
|
-
const { lifecycle, authz, link } = snapshot;
|
|
641
|
-
if (lifecycle.phase === "idle" || lifecycle.phase === "detecting") return "booting";
|
|
642
|
-
if (lifecycle.phase === "negotiating") return "booting";
|
|
643
|
-
if (lifecycle.phase === "terminated") {
|
|
644
|
-
switch (lifecycle.reason.kind) {
|
|
645
|
-
case "script_timeout":
|
|
646
|
-
case "not_embedded":
|
|
647
|
-
return "unavailable";
|
|
648
|
-
case "host_rejected":
|
|
649
|
-
case "ready_failed":
|
|
650
|
-
case "ready_timeout":
|
|
651
|
-
case "manual_terminate":
|
|
652
|
-
return "error";
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
if (authz.state === "denied") return "error";
|
|
656
|
-
if (link.state === "lost") return "error";
|
|
657
|
-
return "ready";
|
|
658
|
-
}
|
|
659
|
-
function deriveErrorMessage(snapshot) {
|
|
660
|
-
if (snapshot.lifecycle.phase === "terminated") {
|
|
661
|
-
switch (snapshot.lifecycle.reason.kind) {
|
|
662
|
-
case "ready_failed":
|
|
663
|
-
return snapshot.lifecycle.reason.message;
|
|
664
|
-
case "ready_timeout":
|
|
665
|
-
return `Grist handshake timed out after ${snapshot.lifecycle.reason.afterMs}ms`;
|
|
666
|
-
case "host_rejected":
|
|
667
|
-
return snapshot.lifecycle.reason.message;
|
|
668
|
-
case "script_timeout":
|
|
669
|
-
return `Grist plugin API never loaded (${snapshot.lifecycle.reason.attempts} attempts)`;
|
|
670
|
-
case "not_embedded":
|
|
671
|
-
return "This page is not embedded in a Grist iframe";
|
|
672
|
-
case "manual_terminate":
|
|
673
|
-
return null;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
if (snapshot.authz.state === "denied") {
|
|
677
|
-
return snapshot.authz.message ?? `Grist denied requested access (${snapshot.authz.declared})`;
|
|
678
|
-
}
|
|
679
|
-
if (snapshot.lifecycle.phase === "online" && snapshot.link.state === "lost") {
|
|
680
|
-
return `Grist link lost (${snapshot.link.missedPings} consecutive failures)`;
|
|
681
|
-
}
|
|
682
|
-
return null;
|
|
683
|
-
}
|
|
684
|
-
function deriveCapabilities(snapshot) {
|
|
685
|
-
const { lifecycle, authz, link, config, sync } = snapshot;
|
|
686
|
-
const online = lifecycle.phase === "online";
|
|
687
|
-
const granted = authz.state === "granted";
|
|
688
|
-
const grantedReadOrMore = granted && ACCESS_RANK[authz.level] >= ACCESS_RANK["read table"];
|
|
689
|
-
const grantedFull = granted && authz.level === "full";
|
|
690
|
-
const linkUsable = link.state !== "lost";
|
|
691
|
-
const canRead = online && linkUsable && grantedReadOrMore;
|
|
692
|
-
const canFetchTable = canRead && link.state === "connected";
|
|
693
|
-
const mappingsUsable = hasUsableMappings(config.mappings);
|
|
694
|
-
const missing = mappingsMissing(config.mappings);
|
|
695
|
-
const emptyMultiples = mappingsEmptyMultiples(config.mappings);
|
|
696
|
-
const canRender = canRead && mappingsUsable;
|
|
697
|
-
const canWriteRecords = canRender && grantedFull;
|
|
698
|
-
const canWriteSchema = canWriteRecords && sync.currentTable.state === "known";
|
|
699
|
-
const hasFreshSelection = canRead && sync.record.state === "hot" && config.mappings.state !== "invalidating";
|
|
700
|
-
return {
|
|
701
|
-
canRead,
|
|
702
|
-
canRender,
|
|
703
|
-
canWriteRecords,
|
|
704
|
-
canWriteSchema,
|
|
705
|
-
canFetchTable,
|
|
706
|
-
hasFreshSelection,
|
|
707
|
-
hasUsableMappings: mappingsUsable,
|
|
708
|
-
missingMappings: missing,
|
|
709
|
-
emptyMultipleMappings: emptyMultiples
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
function hasUsableMappings(state) {
|
|
713
|
-
if (state.state === "not_declared") return true;
|
|
714
|
-
if (state.state === "complete") return true;
|
|
715
|
-
if (state.state === "invalidating") {
|
|
716
|
-
return state.previous.state === "complete";
|
|
717
|
-
}
|
|
718
|
-
return false;
|
|
719
|
-
}
|
|
720
|
-
function mappingsMissing(state) {
|
|
721
|
-
if (state.state === "incomplete") return state.missing;
|
|
722
|
-
if (state.state === "invalidating" && state.previous.state === "incomplete") {
|
|
723
|
-
return state.previous.missing;
|
|
724
|
-
}
|
|
725
|
-
return EMPTY;
|
|
726
|
-
}
|
|
727
|
-
function mappingsEmptyMultiples(state) {
|
|
728
|
-
if (state.state === "incomplete") return state.emptyMultiples;
|
|
729
|
-
if (state.state === "invalidating" && state.previous.state === "incomplete") {
|
|
730
|
-
return state.previous.emptyMultiples;
|
|
731
|
-
}
|
|
732
|
-
return EMPTY;
|
|
733
|
-
}
|
|
734
|
-
var EMPTY = Object.freeze([]);
|
|
735
|
-
|
|
736
|
-
// src/sdk/internal/handshake-manager-context.ts
|
|
737
|
-
var import_react = require("react");
|
|
738
|
-
|
|
739
|
-
// src/sdk/internal/ready-singleton.ts
|
|
740
|
-
var ACCESS_RANK2 = {
|
|
741
|
-
none: 0,
|
|
742
|
-
"read table": 1,
|
|
743
|
-
full: 2
|
|
744
|
-
};
|
|
745
|
-
var inFlight = null;
|
|
746
|
-
var lastAccess = null;
|
|
747
|
-
var lastColumnNames = null;
|
|
748
|
-
function columnNamesOf(columns) {
|
|
749
|
-
return new Set((columns ?? []).map((c) => typeof c === "string" ? c : c.name));
|
|
750
|
-
}
|
|
751
|
-
function shouldReinit(next, nextColumns) {
|
|
752
|
-
if (!lastAccess) return true;
|
|
753
|
-
if (ACCESS_RANK2[next] > ACCESS_RANK2[lastAccess]) return true;
|
|
754
|
-
for (const name of nextColumns) {
|
|
755
|
-
if (!lastColumnNames?.has(name)) return true;
|
|
756
|
-
}
|
|
757
|
-
return false;
|
|
758
|
-
}
|
|
759
|
-
function ensureGristReady(payload) {
|
|
760
|
-
const access = payload.requiredAccess ?? "read table";
|
|
761
|
-
const columns = columnNamesOf(payload.columns);
|
|
762
|
-
if (inFlight && !shouldReinit(access, columns)) return inFlight;
|
|
763
|
-
lastAccess = access;
|
|
764
|
-
lastColumnNames = columns;
|
|
765
|
-
const next = Promise.resolve().then(() => {
|
|
766
|
-
const result = grist.ready(payload);
|
|
767
|
-
return result instanceof Promise ? result : Promise.resolve();
|
|
768
|
-
});
|
|
769
|
-
inFlight = next.catch((err) => {
|
|
770
|
-
inFlight = null;
|
|
771
|
-
lastAccess = null;
|
|
772
|
-
lastColumnNames = null;
|
|
773
|
-
throw err;
|
|
774
|
-
});
|
|
775
|
-
return inFlight;
|
|
776
|
-
}
|
|
777
|
-
function resetGristReadySingleton() {
|
|
778
|
-
inFlight = null;
|
|
779
|
-
lastAccess = null;
|
|
780
|
-
lastColumnNames = null;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
// src/sdk/internal/build-manager-options.ts
|
|
784
|
-
function buildManagerOptionsFromGristOptions(options) {
|
|
785
|
-
const opts = {
|
|
786
|
-
requiredAccess: options?.requiredAccess ?? "read table",
|
|
787
|
-
columns: options?.columns ?? [],
|
|
788
|
-
allowSelectBy: options?.allowSelectBy,
|
|
789
|
-
hasCustomOptions: options?.hasCustomOptions,
|
|
790
|
-
onEditOptions: options?.onEditOptions,
|
|
791
|
-
negotiate: {
|
|
792
|
-
readyImpl: (payload) => ensureGristReady(payload)
|
|
793
|
-
},
|
|
794
|
-
onBeforeReload: () => resetGristReadySingleton()
|
|
795
|
-
};
|
|
796
|
-
if (options?.availabilityMaxAttempts !== void 0 || options?.availabilityIntervalMs !== void 0) {
|
|
797
|
-
const interval = options?.availabilityIntervalMs ?? 1e3;
|
|
798
|
-
const max = options?.availabilityMaxAttempts ?? 10;
|
|
799
|
-
opts.detect = {
|
|
800
|
-
pollSequenceMs: Array.from({ length: max }, () => interval),
|
|
801
|
-
budgetMs: interval * max
|
|
802
|
-
};
|
|
803
|
-
}
|
|
804
|
-
return opts;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
// src/sdk/internal/handshake/effects/detect.ts
|
|
808
|
-
var DEFAULT_POLL_SEQUENCE_MS = [
|
|
809
|
-
200,
|
|
810
|
-
200,
|
|
811
|
-
400,
|
|
812
|
-
400,
|
|
813
|
-
800,
|
|
814
|
-
1600,
|
|
815
|
-
3200,
|
|
816
|
-
5e3,
|
|
817
|
-
5e3,
|
|
818
|
-
5e3,
|
|
819
|
-
5e3,
|
|
820
|
-
5e3,
|
|
821
|
-
5e3,
|
|
822
|
-
5e3
|
|
823
|
-
];
|
|
824
|
-
function defaultBudgetMs(hint) {
|
|
825
|
-
if (hint === "slow-2g" || hint === "2g") return 12e4;
|
|
826
|
-
if (hint === "3g") return 6e4;
|
|
827
|
-
return 3e4;
|
|
828
|
-
}
|
|
829
|
-
function startDetectEffect(env, dispatch, signal, options = {}) {
|
|
830
|
-
const sequence = options.pollSequenceMs ?? DEFAULT_POLL_SEQUENCE_MS;
|
|
831
|
-
const budgetMs = options.budgetMs ?? defaultBudgetMs(env.effectiveTypeHint());
|
|
832
|
-
const startedAt = env.now();
|
|
833
|
-
let attempts = 0;
|
|
834
|
-
let timer = null;
|
|
835
|
-
let cancelled = false;
|
|
836
|
-
signal.addEventListener("abort", () => {
|
|
837
|
-
cancelled = true;
|
|
838
|
-
if (timer !== null) env.clearTimeout(timer);
|
|
839
|
-
});
|
|
840
|
-
dispatch({ type: "DETECT_START", atMs: startedAt });
|
|
841
|
-
const tick = () => {
|
|
842
|
-
if (cancelled) return;
|
|
843
|
-
timer = null;
|
|
844
|
-
const probe = env.probeGrist();
|
|
845
|
-
if (probe !== null) {
|
|
846
|
-
if (probe.embedded) {
|
|
847
|
-
dispatch({ type: "DETECT_AVAILABLE", atMs: env.now() });
|
|
848
|
-
} else {
|
|
849
|
-
dispatch({ type: "NOT_EMBEDDED", atMs: env.now() });
|
|
850
|
-
}
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
|
-
attempts += 1;
|
|
854
|
-
dispatch({ type: "DETECT_TICK", attempts, atMs: env.now() });
|
|
855
|
-
const elapsed = env.now() - startedAt;
|
|
856
|
-
if (elapsed >= budgetMs) {
|
|
857
|
-
dispatch({ type: "DETECT_TIMEOUT", attempts, atMs: env.now() });
|
|
858
|
-
return;
|
|
859
|
-
}
|
|
860
|
-
const next = sequence[Math.min(attempts - 1, sequence.length - 1)] ?? 1e3;
|
|
861
|
-
const remaining = budgetMs - elapsed;
|
|
862
|
-
timer = env.setTimeout(tick, Math.max(0, Math.min(next, remaining)));
|
|
863
|
-
};
|
|
864
|
-
tick();
|
|
865
|
-
return {
|
|
866
|
-
cancel() {
|
|
867
|
-
cancelled = true;
|
|
868
|
-
if (timer !== null) {
|
|
869
|
-
env.clearTimeout(timer);
|
|
870
|
-
timer = null;
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
};
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
// src/sdk/internal/handshake/effects/negotiate.ts
|
|
877
|
-
var DEFAULT_NEGOTIATE_TIMEOUT_MS = 3e4;
|
|
878
|
-
function startNegotiateEffect(env, dispatch, signal, payload, options = {}) {
|
|
879
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_NEGOTIATE_TIMEOUT_MS;
|
|
880
|
-
const access = payload.requiredAccess ?? "read table";
|
|
881
|
-
let settled = false;
|
|
882
|
-
let timer = null;
|
|
883
|
-
const settle = (next) => {
|
|
884
|
-
if (settled) return;
|
|
885
|
-
settled = true;
|
|
886
|
-
if (timer !== null) env.clearTimeout(timer);
|
|
887
|
-
if (signal.aborted) return;
|
|
888
|
-
next();
|
|
889
|
-
};
|
|
890
|
-
dispatch({ type: "NEGOTIATE_START", access, atMs: env.now() });
|
|
891
|
-
const grist2 = env.getGrist();
|
|
892
|
-
if (!grist2) {
|
|
893
|
-
settle(
|
|
894
|
-
() => dispatch({
|
|
895
|
-
type: "NEGOTIATE_FAIL",
|
|
896
|
-
reason: { kind: "ready_failed", message: "grist global missing at negotiate" },
|
|
897
|
-
atMs: env.now()
|
|
898
|
-
})
|
|
899
|
-
);
|
|
900
|
-
return { cancel: () => settle(() => {
|
|
901
|
-
}) };
|
|
902
|
-
}
|
|
903
|
-
timer = env.setTimeout(() => {
|
|
904
|
-
if (settled) return;
|
|
905
|
-
settle(
|
|
906
|
-
() => dispatch({
|
|
907
|
-
type: "NEGOTIATE_FAIL",
|
|
908
|
-
reason: { kind: "ready_timeout", afterMs: timeoutMs },
|
|
909
|
-
atMs: env.now()
|
|
910
|
-
})
|
|
911
|
-
);
|
|
912
|
-
}, timeoutMs);
|
|
913
|
-
const handleSuccess = (g) => {
|
|
914
|
-
if (!g.docApi) {
|
|
915
|
-
dispatch({
|
|
916
|
-
type: "NEGOTIATE_FAIL",
|
|
917
|
-
reason: { kind: "ready_failed", message: "grist.docApi missing after ready" },
|
|
918
|
-
atMs: env.now()
|
|
919
|
-
});
|
|
920
|
-
return;
|
|
921
|
-
}
|
|
922
|
-
dispatch({
|
|
923
|
-
type: "NEGOTIATE_SUCCESS",
|
|
924
|
-
access,
|
|
925
|
-
grantedLevel: access,
|
|
926
|
-
// host doesn't report effective level; widen later via authz events
|
|
927
|
-
atMs: env.now()
|
|
928
|
-
});
|
|
929
|
-
};
|
|
930
|
-
const handleFailure = (err) => {
|
|
931
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
932
|
-
const reason = { kind: "ready_failed", message };
|
|
933
|
-
dispatch({ type: "NEGOTIATE_FAIL", reason, atMs: env.now() });
|
|
934
|
-
};
|
|
935
|
-
const readyImpl = options.readyImpl ?? ((p) => grist2.ready(p));
|
|
936
|
-
try {
|
|
937
|
-
const result = readyImpl(payload);
|
|
938
|
-
if (result && typeof result.then === "function") {
|
|
939
|
-
;
|
|
940
|
-
result.then(
|
|
941
|
-
() => settle(() => handleSuccess(env.getGrist() ?? grist2)),
|
|
942
|
-
(err) => settle(() => handleFailure(err))
|
|
943
|
-
);
|
|
944
|
-
} else {
|
|
945
|
-
settle(() => handleSuccess(env.getGrist() ?? grist2));
|
|
946
|
-
}
|
|
947
|
-
} catch (err) {
|
|
948
|
-
settle(() => handleFailure(err));
|
|
949
|
-
}
|
|
950
|
-
signal.addEventListener("abort", () => settle(() => {
|
|
951
|
-
}));
|
|
952
|
-
return { cancel: () => settle(() => {
|
|
953
|
-
}) };
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
// src/sdk/helpers/grist-widget-interaction.ts
|
|
957
|
-
function parseGristWidgetLinkingInfo(raw) {
|
|
958
|
-
if (!raw || typeof raw !== "object" || !("asTarget" in raw)) return void 0;
|
|
959
|
-
const linking = raw;
|
|
960
|
-
return {
|
|
961
|
-
asTarget: linking.asTarget == null ? null : String(linking.asTarget),
|
|
962
|
-
asSource: Boolean(linking.asSource)
|
|
963
|
-
};
|
|
964
|
-
}
|
|
965
|
-
function normalizeWidgetInteraction(interaction) {
|
|
966
|
-
if (!interaction || typeof interaction !== "object") return interaction;
|
|
967
|
-
const raw = interaction;
|
|
968
|
-
const access_level = typeof raw.access_level === "string" ? raw.access_level : typeof raw.accessLevel === "string" ? raw.accessLevel : void 0;
|
|
969
|
-
const linking = parseGristWidgetLinkingInfo(raw.linking);
|
|
970
|
-
return {
|
|
971
|
-
...interaction,
|
|
972
|
-
...access_level ? { access_level } : {},
|
|
973
|
-
...linking ? { linking } : {}
|
|
974
|
-
};
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
// src/sdk/internal/subscriptions.ts
|
|
978
|
-
var GristEvent = class {
|
|
979
|
-
constructor(bindOnce) {
|
|
980
|
-
this.bindOnce = bindOnce;
|
|
981
|
-
this.listeners = /* @__PURE__ */ new Set();
|
|
982
|
-
this.hasValue = false;
|
|
983
|
-
this.bind = null;
|
|
984
|
-
}
|
|
985
|
-
subscribe(listener) {
|
|
986
|
-
this.listeners.add(listener);
|
|
987
|
-
if (!this.bind) {
|
|
988
|
-
this.bind = () => {
|
|
989
|
-
this.bind = () => {
|
|
990
|
-
};
|
|
991
|
-
try {
|
|
992
|
-
this.bindOnce((value) => {
|
|
993
|
-
this.hasValue = true;
|
|
994
|
-
this.last = value;
|
|
995
|
-
for (const l of this.listeners) l(value);
|
|
996
|
-
});
|
|
997
|
-
} catch (err) {
|
|
998
|
-
if (typeof console !== "undefined") {
|
|
999
|
-
console.error("[lib/grist] failed to subscribe to Grist event:", err);
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
};
|
|
1003
|
-
this.bind();
|
|
1004
|
-
}
|
|
1005
|
-
if (this.hasValue && this.last !== void 0) {
|
|
1006
|
-
listener(this.last);
|
|
1007
|
-
}
|
|
1008
|
-
return () => {
|
|
1009
|
-
this.listeners.delete(listener);
|
|
1010
|
-
};
|
|
1011
|
-
}
|
|
1012
|
-
/** Last cached value (or `undefined` if no event has fired yet). */
|
|
1013
|
-
peek() {
|
|
1014
|
-
return this.last;
|
|
1015
|
-
}
|
|
1016
|
-
/** True once at least one event has fired. */
|
|
1017
|
-
get ready() {
|
|
1018
|
-
return this.hasValue;
|
|
1019
|
-
}
|
|
1020
|
-
/**
|
|
1021
|
-
* Test helper: fan out a payload without calling `grist.on*`.
|
|
1022
|
-
* Used to simulate hosts that reuse one record object and mutate it in place.
|
|
1023
|
-
*/
|
|
1024
|
-
deliverForTests(value) {
|
|
1025
|
-
this.hasValue = true;
|
|
1026
|
-
this.last = value;
|
|
1027
|
-
for (const l of this.listeners) l(value);
|
|
1028
|
-
}
|
|
1029
|
-
/**
|
|
1030
|
-
* Test helper: forget the bound state and cached value. Lets a test swap
|
|
1031
|
-
* `window.grist` (e.g. via the emulator's `dispose()` + `createGristEmulator()`)
|
|
1032
|
-
* and re-bind cleanly on the next subscribe.
|
|
1033
|
-
*/
|
|
1034
|
-
_resetForTests() {
|
|
1035
|
-
this.listeners.clear();
|
|
1036
|
-
this.hasValue = false;
|
|
1037
|
-
this.last = void 0;
|
|
1038
|
-
this.bind = null;
|
|
1039
|
-
}
|
|
1040
|
-
};
|
|
1041
|
-
var recordEvent = new GristEvent((emit) => {
|
|
1042
|
-
const deliver = (record, mappings) => {
|
|
1043
|
-
emit({ record, mappings });
|
|
1044
|
-
};
|
|
1045
|
-
grist.onRecord(
|
|
1046
|
-
(record, mappings) => {
|
|
1047
|
-
deliver(record, mappings ?? null);
|
|
1048
|
-
},
|
|
1049
|
-
{ keepEncoded: false }
|
|
1050
|
-
);
|
|
1051
|
-
const fetchSelectedRecord = grist.fetchSelectedRecord ?? grist.docApi?.fetchSelectedRecord;
|
|
1052
|
-
if (typeof grist.on !== "function" || typeof fetchSelectedRecord !== "function") {
|
|
1053
|
-
return;
|
|
1054
|
-
}
|
|
1055
|
-
let lastRowId;
|
|
1056
|
-
grist.on("message", async (msg) => {
|
|
1057
|
-
if (!msg || typeof msg !== "object") return;
|
|
1058
|
-
const host = msg;
|
|
1059
|
-
if (!host.tableId || typeof host.rowId !== "number") return;
|
|
1060
|
-
if (host.rowId === lastRowId) return;
|
|
1061
|
-
lastRowId = host.rowId;
|
|
1062
|
-
try {
|
|
1063
|
-
const record = await fetchSelectedRecord(host.rowId, { keepEncoded: false });
|
|
1064
|
-
deliver(record, recordEvent.peek()?.mappings ?? null);
|
|
1065
|
-
} catch (err) {
|
|
1066
|
-
if (typeof console !== "undefined") {
|
|
1067
|
-
console.error("[grist-widget-sdk] fetchSelectedRecord on cursor message failed:", err);
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
});
|
|
1071
|
-
});
|
|
1072
|
-
var recordsEvent = new GristEvent((emit) => {
|
|
1073
|
-
grist.onRecords(
|
|
1074
|
-
(records, mappings) => {
|
|
1075
|
-
emit({ records, mappings: mappings ?? null });
|
|
1076
|
-
},
|
|
1077
|
-
{ keepEncoded: false, format: "rows" }
|
|
1078
|
-
);
|
|
1079
|
-
});
|
|
1080
|
-
var optionsEvent = new GristEvent((emit) => {
|
|
1081
|
-
grist.onOptions((options, interaction) => {
|
|
1082
|
-
const normalized = normalizeWidgetInteraction(
|
|
1083
|
-
interaction ?? null
|
|
1084
|
-
);
|
|
1085
|
-
emit({
|
|
1086
|
-
options: options ? { ...options } : null,
|
|
1087
|
-
interaction: normalized
|
|
1088
|
-
});
|
|
1089
|
-
});
|
|
1090
|
-
});
|
|
1091
|
-
var newRecordEvent = new GristEvent((emit) => {
|
|
1092
|
-
if (typeof grist.onNewRecord !== "function") return;
|
|
1093
|
-
grist.onNewRecord((mappings) => {
|
|
1094
|
-
emit({ mappings: mappings ?? null });
|
|
1095
|
-
});
|
|
1096
|
-
});
|
|
1097
|
-
|
|
1098
|
-
// src/sdk/internal/handshake/effects/subscriptions.ts
|
|
1099
|
-
function defaultSubscriptionsBinder(ctx) {
|
|
1100
|
-
const offs = [];
|
|
1101
|
-
offs.push(
|
|
1102
|
-
recordEvent.subscribe((payload) => {
|
|
1103
|
-
const hash = quickHash("record", payload);
|
|
1104
|
-
dispatchStream(ctx, "record", hash);
|
|
1105
|
-
ctx.onRecordPayload?.(payload.mappings ?? null);
|
|
1106
|
-
})
|
|
1107
|
-
);
|
|
1108
|
-
offs.push(
|
|
1109
|
-
recordsEvent.subscribe((payload) => {
|
|
1110
|
-
const hash = quickHash("records", payload);
|
|
1111
|
-
dispatchStream(ctx, "records", hash);
|
|
1112
|
-
ctx.onRecordsPayload?.(payload.mappings ?? null);
|
|
1113
|
-
})
|
|
1114
|
-
);
|
|
1115
|
-
offs.push(
|
|
1116
|
-
optionsEvent.subscribe((payload) => {
|
|
1117
|
-
const hash = quickHash("options", payload);
|
|
1118
|
-
dispatchStream(ctx, "options", hash);
|
|
1119
|
-
const level = parseGristRequiredAccess(payload.interaction?.access_level);
|
|
1120
|
-
if (level !== null) {
|
|
1121
|
-
ctx.dispatch({
|
|
1122
|
-
type: "AUTHZ_REPORT",
|
|
1123
|
-
level,
|
|
1124
|
-
atMs: Date.now(),
|
|
1125
|
-
generation: ctx.getGeneration()
|
|
1126
|
-
});
|
|
1127
|
-
}
|
|
1128
|
-
ctx.onOptionsPayload?.(payload.options ?? null);
|
|
1129
|
-
})
|
|
1130
|
-
);
|
|
1131
|
-
offs.push(
|
|
1132
|
-
newRecordEvent.subscribe((payload) => {
|
|
1133
|
-
const hash = quickHash("newRecord", payload);
|
|
1134
|
-
dispatchStream(ctx, "newRecord", hash);
|
|
1135
|
-
ctx.onNewRecordPayload?.(payload.mappings ?? null);
|
|
1136
|
-
})
|
|
1137
|
-
);
|
|
1138
|
-
return {
|
|
1139
|
-
cancel() {
|
|
1140
|
-
for (const off of offs.splice(0)) off();
|
|
1141
|
-
}
|
|
1142
|
-
};
|
|
1143
|
-
}
|
|
1144
|
-
function dispatchStream(ctx, stream, payloadHash) {
|
|
1145
|
-
ctx.dispatch({
|
|
1146
|
-
type: "STREAM_RECEIVED",
|
|
1147
|
-
stream,
|
|
1148
|
-
payloadHash,
|
|
1149
|
-
atMs: Date.now(),
|
|
1150
|
-
generation: ctx.getGeneration()
|
|
1151
|
-
});
|
|
1152
|
-
}
|
|
1153
|
-
function quickHash(kind, payload) {
|
|
1154
|
-
return `${kind}::${stableSerialize(payload)}`;
|
|
1155
|
-
}
|
|
1156
|
-
function stableSerialize(value) {
|
|
1157
|
-
if (value === null) return "null";
|
|
1158
|
-
if (value === void 0) return "undefined";
|
|
1159
|
-
const t = typeof value;
|
|
1160
|
-
if (t === "number" || t === "string" || t === "boolean") return JSON.stringify(value);
|
|
1161
|
-
if (Array.isArray(value)) {
|
|
1162
|
-
return `[${value.map(stableSerialize).join(",")}]`;
|
|
1163
|
-
}
|
|
1164
|
-
if (t === "object") {
|
|
1165
|
-
const obj = value;
|
|
1166
|
-
const keys = Object.keys(obj).sort();
|
|
1167
|
-
const body = keys.map((k) => `${JSON.stringify(k)}:${stableSerialize(obj[k])}`).join(",");
|
|
1168
|
-
return `{${body}}`;
|
|
1169
|
-
}
|
|
1170
|
-
return String(value);
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
// src/sdk/internal/handshake/effects/mappings.ts
|
|
1174
|
-
var DEFAULT_MAPPING_TIMEOUT_MS = 5e3;
|
|
1175
|
-
function startMappingsEffect(env, dispatch, signal, declared, getGeneration, options = {}) {
|
|
1176
|
-
let timeoutHandle = null;
|
|
1177
|
-
let cancelled = false;
|
|
1178
|
-
const startMs = env.now();
|
|
1179
|
-
dispatch({ type: "MAPPING_DECLARE", declared, atMs: startMs, generation: getGeneration() });
|
|
1180
|
-
if (declared.length === 0) {
|
|
1181
|
-
return {
|
|
1182
|
-
ingestStreamPayload: () => {
|
|
1183
|
-
},
|
|
1184
|
-
refetchSectionApi: async () => {
|
|
1185
|
-
},
|
|
1186
|
-
cancel: () => {
|
|
1187
|
-
}
|
|
1188
|
-
};
|
|
1189
|
-
}
|
|
1190
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_MAPPING_TIMEOUT_MS;
|
|
1191
|
-
timeoutHandle = env.setTimeout(() => {
|
|
1192
|
-
if (cancelled || signal.aborted) return;
|
|
1193
|
-
dispatch({ type: "MAPPING_TIMEOUT", atMs: env.now(), generation: getGeneration() });
|
|
1194
|
-
}, timeoutMs);
|
|
1195
|
-
signal.addEventListener("abort", () => {
|
|
1196
|
-
cancelled = true;
|
|
1197
|
-
if (timeoutHandle !== null) env.clearTimeout(timeoutHandle);
|
|
1198
|
-
});
|
|
1199
|
-
const ingestPayload = (source, raw) => {
|
|
1200
|
-
if (cancelled || signal.aborted) return;
|
|
1201
|
-
const payload = coerceMappingsPayload(raw);
|
|
1202
|
-
dispatch({
|
|
1203
|
-
type: "MAPPING_REPORT",
|
|
1204
|
-
source,
|
|
1205
|
-
payload,
|
|
1206
|
-
atMs: env.now(),
|
|
1207
|
-
generation: getGeneration()
|
|
1208
|
-
});
|
|
1209
|
-
};
|
|
1210
|
-
const refetchSectionApi = async () => {
|
|
1211
|
-
if (cancelled || signal.aborted) return;
|
|
1212
|
-
const grist2 = env.getGrist();
|
|
1213
|
-
const api = grist2?.sectionApi;
|
|
1214
|
-
if (!api || typeof api.mappings !== "function") return;
|
|
1215
|
-
try {
|
|
1216
|
-
const payload = await api.mappings();
|
|
1217
|
-
ingestPayload("section_api", payload);
|
|
1218
|
-
} catch {
|
|
1219
|
-
ingestPayload("section_api", null);
|
|
1220
|
-
}
|
|
1221
|
-
};
|
|
1222
|
-
void refetchSectionApi();
|
|
1223
|
-
return {
|
|
1224
|
-
ingestStreamPayload: (source, payload) => ingestPayload(source, payload),
|
|
1225
|
-
refetchSectionApi,
|
|
1226
|
-
cancel() {
|
|
1227
|
-
cancelled = true;
|
|
1228
|
-
if (timeoutHandle !== null) env.clearTimeout(timeoutHandle);
|
|
1229
|
-
}
|
|
1230
|
-
};
|
|
1231
|
-
}
|
|
1232
|
-
function coerceMappingsPayload(raw) {
|
|
1233
|
-
if (raw === null || raw === void 0) return null;
|
|
1234
|
-
if (typeof raw !== "object") return null;
|
|
1235
|
-
return raw;
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
// src/sdk/internal/handshake/effects/heartbeat.ts
|
|
1239
|
-
var DEFAULT_HEARTBEAT_INTERVAL_MS = 3e4;
|
|
1240
|
-
var DEFAULT_HEARTBEAT_PING_TIMEOUT_MS = 1e4;
|
|
1241
|
-
function startHeartbeatEffect(env, dispatch, signal, options = {}) {
|
|
1242
|
-
const intervalMs = options.intervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
1243
|
-
const pingTimeoutMs = options.pingTimeoutMs ?? DEFAULT_HEARTBEAT_PING_TIMEOUT_MS;
|
|
1244
|
-
const pauseWhenHidden = options.pauseWhenHidden ?? true;
|
|
1245
|
-
const probe = options.probe ?? defaultProbe(env);
|
|
1246
|
-
let cancelled = false;
|
|
1247
|
-
let paused = false;
|
|
1248
|
-
let intervalTimer = null;
|
|
1249
|
-
let inFlight3 = false;
|
|
1250
|
-
let inFlightTimeoutTimer = null;
|
|
1251
|
-
const clearInterval = () => {
|
|
1252
|
-
if (intervalTimer !== null) {
|
|
1253
|
-
env.clearTimeout(intervalTimer);
|
|
1254
|
-
intervalTimer = null;
|
|
1255
|
-
}
|
|
1256
|
-
};
|
|
1257
|
-
const clearInFlightTimeout = () => {
|
|
1258
|
-
if (inFlightTimeoutTimer !== null) {
|
|
1259
|
-
env.clearTimeout(inFlightTimeoutTimer);
|
|
1260
|
-
inFlightTimeoutTimer = null;
|
|
1261
|
-
}
|
|
1262
|
-
};
|
|
1263
|
-
const scheduleNext = (delay = intervalMs) => {
|
|
1264
|
-
clearInterval();
|
|
1265
|
-
if (cancelled || paused) return;
|
|
1266
|
-
intervalTimer = env.setTimeout(ping, delay);
|
|
1267
|
-
};
|
|
1268
|
-
const ping = () => {
|
|
1269
|
-
if (cancelled || paused) return;
|
|
1270
|
-
intervalTimer = null;
|
|
1271
|
-
if (inFlight3) {
|
|
1272
|
-
return;
|
|
1273
|
-
}
|
|
1274
|
-
inFlight3 = true;
|
|
1275
|
-
inFlightTimeoutTimer = env.setTimeout(() => {
|
|
1276
|
-
if (!inFlight3) return;
|
|
1277
|
-
inFlight3 = false;
|
|
1278
|
-
clearInFlightTimeout();
|
|
1279
|
-
if (!cancelled) {
|
|
1280
|
-
dispatch({ type: "HEARTBEAT_FAIL", atMs: env.now() });
|
|
1281
|
-
scheduleNext();
|
|
1282
|
-
}
|
|
1283
|
-
}, pingTimeoutMs);
|
|
1284
|
-
const handleOk = () => {
|
|
1285
|
-
if (!inFlight3) return;
|
|
1286
|
-
inFlight3 = false;
|
|
1287
|
-
clearInFlightTimeout();
|
|
1288
|
-
if (cancelled) return;
|
|
1289
|
-
dispatch({ type: "HEARTBEAT_OK", atMs: env.now() });
|
|
1290
|
-
scheduleNext();
|
|
1291
|
-
};
|
|
1292
|
-
const handleFail = () => {
|
|
1293
|
-
if (!inFlight3) return;
|
|
1294
|
-
inFlight3 = false;
|
|
1295
|
-
clearInFlightTimeout();
|
|
1296
|
-
if (cancelled) return;
|
|
1297
|
-
dispatch({ type: "HEARTBEAT_FAIL", atMs: env.now() });
|
|
1298
|
-
scheduleNext();
|
|
1299
|
-
};
|
|
1300
|
-
let result;
|
|
1301
|
-
try {
|
|
1302
|
-
result = probe();
|
|
1303
|
-
} catch {
|
|
1304
|
-
handleFail();
|
|
1305
|
-
return;
|
|
1306
|
-
}
|
|
1307
|
-
if (result && typeof result.then === "function") {
|
|
1308
|
-
;
|
|
1309
|
-
result.then(handleOk, handleFail);
|
|
1310
|
-
} else {
|
|
1311
|
-
handleOk();
|
|
1312
|
-
}
|
|
1313
|
-
};
|
|
1314
|
-
const offVisibility = pauseWhenHidden && env.onVisibilityChange ? env.onVisibilityChange((visible) => {
|
|
1315
|
-
if (cancelled) return;
|
|
1316
|
-
if (visible) {
|
|
1317
|
-
if (paused) {
|
|
1318
|
-
paused = false;
|
|
1319
|
-
ping();
|
|
1320
|
-
}
|
|
1321
|
-
} else {
|
|
1322
|
-
paused = true;
|
|
1323
|
-
clearInterval();
|
|
1324
|
-
}
|
|
1325
|
-
}) : null;
|
|
1326
|
-
const offOnline = env.onOnline?.(() => {
|
|
1327
|
-
if (cancelled || paused) return;
|
|
1328
|
-
ping();
|
|
1329
|
-
}) ?? null;
|
|
1330
|
-
signal.addEventListener("abort", () => {
|
|
1331
|
-
cancelled = true;
|
|
1332
|
-
clearInterval();
|
|
1333
|
-
clearInFlightTimeout();
|
|
1334
|
-
inFlight3 = false;
|
|
1335
|
-
offVisibility?.();
|
|
1336
|
-
offOnline?.();
|
|
1337
|
-
});
|
|
1338
|
-
scheduleNext();
|
|
1339
|
-
return {
|
|
1340
|
-
cancel() {
|
|
1341
|
-
cancelled = true;
|
|
1342
|
-
clearInterval();
|
|
1343
|
-
clearInFlightTimeout();
|
|
1344
|
-
inFlight3 = false;
|
|
1345
|
-
offVisibility?.();
|
|
1346
|
-
offOnline?.();
|
|
1347
|
-
},
|
|
1348
|
-
recordRpcSuccess() {
|
|
1349
|
-
if (cancelled || paused) return;
|
|
1350
|
-
dispatch({ type: "RPC_SUCCESS", atMs: env.now() });
|
|
1351
|
-
scheduleNext();
|
|
1352
|
-
},
|
|
1353
|
-
recordRpcFailure() {
|
|
1354
|
-
if (cancelled || paused) return;
|
|
1355
|
-
dispatch({ type: "RPC_FAILURE", atMs: env.now(), kind: "other" });
|
|
1356
|
-
scheduleNext(Math.min(intervalMs, 1e3));
|
|
1357
|
-
},
|
|
1358
|
-
pingNow() {
|
|
1359
|
-
if (cancelled) return;
|
|
1360
|
-
ping();
|
|
1361
|
-
}
|
|
1362
|
-
};
|
|
1363
|
-
}
|
|
1364
|
-
function defaultProbe(env) {
|
|
1365
|
-
return async () => {
|
|
1366
|
-
const g = env.getGrist();
|
|
1367
|
-
if (!g) throw new Error("grist global missing");
|
|
1368
|
-
const docApi = g.docApi;
|
|
1369
|
-
if (!docApi || typeof docApi.getDocName !== "function") {
|
|
1370
|
-
throw new Error("grist.docApi.getDocName unavailable");
|
|
1371
|
-
}
|
|
1372
|
-
return docApi.getDocName();
|
|
1373
|
-
};
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
// src/sdk/internal/handshake/authz-from-options-cache.ts
|
|
1377
|
-
function denyAuthzFromCachedOptionsIfInsufficient(snapshot, dispatch, env) {
|
|
1378
|
-
if (snapshot.lifecycle.phase !== "online") return;
|
|
1379
|
-
if (snapshot.authz.state === "denied") return;
|
|
1380
|
-
const payload = optionsEvent.peek();
|
|
1381
|
-
const level = parseGristRequiredAccess(payload?.interaction?.access_level);
|
|
1382
|
-
if (level === null) return;
|
|
1383
|
-
const declared = snapshot.authz.declared;
|
|
1384
|
-
if (isAccessSufficient(level, declared)) return;
|
|
1385
|
-
dispatch({
|
|
1386
|
-
type: "AUTHZ_REPORT",
|
|
1387
|
-
level,
|
|
1388
|
-
atMs: env.now(),
|
|
1389
|
-
generation: snapshot.generation
|
|
1390
|
-
});
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
// src/sdk/internal/handshake/environment.ts
|
|
1394
|
-
function createBrowserEnvironment() {
|
|
1395
|
-
return {
|
|
1396
|
-
now: () => typeof performance !== "undefined" ? performance.now() : Date.now(),
|
|
1397
|
-
setTimeout: (cb, ms) => globalThis.setTimeout(cb, ms),
|
|
1398
|
-
clearTimeout: (h) => globalThis.clearTimeout(h),
|
|
1399
|
-
probeGrist() {
|
|
1400
|
-
if (typeof globalThis === "undefined") return null;
|
|
1401
|
-
const g = globalThis.grist;
|
|
1402
|
-
if (typeof g === "undefined") return null;
|
|
1403
|
-
const embedded2 = typeof window === "undefined" ? false : window.self !== window.top;
|
|
1404
|
-
return { embedded: embedded2 };
|
|
1405
|
-
},
|
|
1406
|
-
getGrist() {
|
|
1407
|
-
const g = globalThis.grist;
|
|
1408
|
-
return g ?? null;
|
|
1409
|
-
},
|
|
1410
|
-
effectiveTypeHint() {
|
|
1411
|
-
if (typeof navigator === "undefined") return null;
|
|
1412
|
-
const conn = navigator.connection;
|
|
1413
|
-
const t = conn?.effectiveType;
|
|
1414
|
-
if (t === "slow-2g" || t === "2g" || t === "3g" || t === "4g" || t === "5g") return t;
|
|
1415
|
-
return null;
|
|
1416
|
-
},
|
|
1417
|
-
onVisibilityChange(listener) {
|
|
1418
|
-
if (typeof document === "undefined") return () => {
|
|
1419
|
-
};
|
|
1420
|
-
const handler = () => listener(document.visibilityState === "visible");
|
|
1421
|
-
document.addEventListener("visibilitychange", handler);
|
|
1422
|
-
return () => document.removeEventListener("visibilitychange", handler);
|
|
1423
|
-
},
|
|
1424
|
-
onOnline(listener) {
|
|
1425
|
-
if (typeof window === "undefined") return () => {
|
|
1426
|
-
};
|
|
1427
|
-
const handler = () => listener();
|
|
1428
|
-
window.addEventListener("online", handler);
|
|
1429
|
-
return () => window.removeEventListener("online", handler);
|
|
1430
|
-
}
|
|
1431
|
-
};
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
// src/sdk/internal/handshake/manager.ts
|
|
1435
|
-
function createGristHandshakeManager(options = {}) {
|
|
1436
|
-
const env = options.env ?? createBrowserEnvironment();
|
|
1437
|
-
const reducerConfig = {
|
|
1438
|
-
...DEFAULT_REDUCER_CONFIG,
|
|
1439
|
-
...options.reducerConfig ?? {}
|
|
1440
|
-
};
|
|
1441
|
-
let snapshot = initialGristSnapshot();
|
|
1442
|
-
const ctx = createReducerContext();
|
|
1443
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
1444
|
-
let started = false;
|
|
1445
|
-
let abort = null;
|
|
1446
|
-
let detectHandle = null;
|
|
1447
|
-
let negotiateHandle = null;
|
|
1448
|
-
let subscriptionsHandle = null;
|
|
1449
|
-
let mappingsHandle = null;
|
|
1450
|
-
let heartbeatHandle = null;
|
|
1451
|
-
let recheckAuthzFromCacheAfterOnline = false;
|
|
1452
|
-
const declared = options.columns ?? [];
|
|
1453
|
-
const subscriptionsBinder = options.subscriptionsBinder ?? defaultSubscriptionsBinder;
|
|
1454
|
-
const notify = () => {
|
|
1455
|
-
for (const l of listeners) {
|
|
1456
|
-
try {
|
|
1457
|
-
l();
|
|
1458
|
-
} catch (err) {
|
|
1459
|
-
options.onError?.(err, "listener");
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
};
|
|
1463
|
-
const dispatch = (action) => {
|
|
1464
|
-
const prev = snapshot;
|
|
1465
|
-
const next = gristReducer(prev, action, ctx, reducerConfig);
|
|
1466
|
-
if (next !== prev) {
|
|
1467
|
-
snapshot = next;
|
|
1468
|
-
onTransition(prev, next);
|
|
1469
|
-
notify();
|
|
1470
|
-
}
|
|
1471
|
-
};
|
|
1472
|
-
const onTransition = (prev, next) => {
|
|
1473
|
-
if (prev.lifecycle.phase !== "negotiating" && next.lifecycle.phase === "negotiating") {
|
|
1474
|
-
launchNegotiate();
|
|
1475
|
-
launchSubscriptions();
|
|
1476
|
-
}
|
|
1477
|
-
if (prev.lifecycle.phase !== "online" && next.lifecycle.phase === "online") {
|
|
1478
|
-
launchMappings();
|
|
1479
|
-
launchHeartbeat();
|
|
1480
|
-
if (recheckAuthzFromCacheAfterOnline) {
|
|
1481
|
-
recheckAuthzFromCacheAfterOnline = false;
|
|
1482
|
-
denyAuthzFromCachedOptionsIfInsufficient(next, dispatch, env);
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
if (prev.lifecycle.phase === "online" && next.lifecycle.phase !== "online") {
|
|
1486
|
-
heartbeatHandle?.cancel();
|
|
1487
|
-
heartbeatHandle = null;
|
|
1488
|
-
}
|
|
1489
|
-
};
|
|
1490
|
-
const launchDetect = () => {
|
|
1491
|
-
if (!abort) return;
|
|
1492
|
-
detectHandle?.cancel();
|
|
1493
|
-
detectHandle = startDetectEffect(env, dispatch, abort.signal, options.detect);
|
|
1494
|
-
};
|
|
1495
|
-
const launchNegotiate = () => {
|
|
1496
|
-
if (!abort) return;
|
|
1497
|
-
negotiateHandle?.cancel();
|
|
1498
|
-
negotiateHandle = startNegotiateEffect(
|
|
1499
|
-
env,
|
|
1500
|
-
dispatch,
|
|
1501
|
-
abort.signal,
|
|
1502
|
-
buildReadyPayload(options),
|
|
1503
|
-
options.negotiate
|
|
1504
|
-
);
|
|
1505
|
-
};
|
|
1506
|
-
const launchSubscriptions = () => {
|
|
1507
|
-
if (!abort) return;
|
|
1508
|
-
if (subscriptionsHandle) return;
|
|
1509
|
-
subscriptionsHandle = subscriptionsBinder({
|
|
1510
|
-
dispatch,
|
|
1511
|
-
getGeneration: () => snapshot.generation,
|
|
1512
|
-
onRecordPayload: (m) => mappingsHandle?.ingestStreamPayload("stream_record", m),
|
|
1513
|
-
onRecordsPayload: (m) => mappingsHandle?.ingestStreamPayload("stream_records", m),
|
|
1514
|
-
onNewRecordPayload: (m) => mappingsHandle?.ingestStreamPayload("stream_new_record", m)
|
|
1515
|
-
});
|
|
1516
|
-
};
|
|
1517
|
-
const launchMappings = () => {
|
|
1518
|
-
if (!abort) return;
|
|
1519
|
-
launchSubscriptions();
|
|
1520
|
-
if (mappingsHandle) return;
|
|
1521
|
-
mappingsHandle = startMappingsEffect(
|
|
1522
|
-
env,
|
|
1523
|
-
dispatch,
|
|
1524
|
-
abort.signal,
|
|
1525
|
-
declared,
|
|
1526
|
-
() => snapshot.generation,
|
|
1527
|
-
options.mappings
|
|
1528
|
-
);
|
|
1529
|
-
};
|
|
1530
|
-
const launchHeartbeat = () => {
|
|
1531
|
-
if (!abort) return;
|
|
1532
|
-
if (options.heartbeat === false) return;
|
|
1533
|
-
if (heartbeatHandle) return;
|
|
1534
|
-
heartbeatHandle = startHeartbeatEffect(
|
|
1535
|
-
env,
|
|
1536
|
-
dispatch,
|
|
1537
|
-
abort.signal,
|
|
1538
|
-
options.heartbeat ?? {}
|
|
1539
|
-
);
|
|
1540
|
-
};
|
|
1541
|
-
const cancelAllEffects = () => {
|
|
1542
|
-
detectHandle?.cancel();
|
|
1543
|
-
detectHandle = null;
|
|
1544
|
-
negotiateHandle?.cancel();
|
|
1545
|
-
negotiateHandle = null;
|
|
1546
|
-
mappingsHandle?.cancel();
|
|
1547
|
-
mappingsHandle = null;
|
|
1548
|
-
heartbeatHandle?.cancel();
|
|
1549
|
-
heartbeatHandle = null;
|
|
1550
|
-
if (abort && !abort.signal.aborted) abort.abort();
|
|
1551
|
-
};
|
|
1552
|
-
const controls = {
|
|
1553
|
-
start() {
|
|
1554
|
-
if (started) return;
|
|
1555
|
-
started = true;
|
|
1556
|
-
abort = new AbortController();
|
|
1557
|
-
launchDetect();
|
|
1558
|
-
},
|
|
1559
|
-
stop() {
|
|
1560
|
-
if (!started) return;
|
|
1561
|
-
started = false;
|
|
1562
|
-
cancelAllEffects();
|
|
1563
|
-
subscriptionsHandle?.cancel();
|
|
1564
|
-
subscriptionsHandle = null;
|
|
1565
|
-
snapshot = initialGristSnapshot();
|
|
1566
|
-
notify();
|
|
1567
|
-
},
|
|
1568
|
-
reload() {
|
|
1569
|
-
if (!started) {
|
|
1570
|
-
controls.start();
|
|
1571
|
-
return;
|
|
1572
|
-
}
|
|
1573
|
-
recheckAuthzFromCacheAfterOnline = true;
|
|
1574
|
-
try {
|
|
1575
|
-
options.onBeforeReload?.();
|
|
1576
|
-
} catch (err) {
|
|
1577
|
-
options.onError?.(err, "onBeforeReload");
|
|
1578
|
-
}
|
|
1579
|
-
cancelAllEffects();
|
|
1580
|
-
dispatch({ type: "BUMP_GENERATION", atMs: env.now(), reason: "reload" });
|
|
1581
|
-
snapshot = {
|
|
1582
|
-
...initialGristSnapshot(),
|
|
1583
|
-
generation: nextGeneration(snapshot.generation)
|
|
1584
|
-
};
|
|
1585
|
-
ctx.resolver = null;
|
|
1586
|
-
abort = new AbortController();
|
|
1587
|
-
launchDetect();
|
|
1588
|
-
notify();
|
|
1589
|
-
},
|
|
1590
|
-
restart() {
|
|
1591
|
-
controls.reload();
|
|
1592
|
-
if (typeof window !== "undefined" && typeof window.location?.reload === "function") {
|
|
1593
|
-
try {
|
|
1594
|
-
window.location.reload();
|
|
1595
|
-
} catch (err) {
|
|
1596
|
-
options.onError?.(err, "restart");
|
|
1597
|
-
}
|
|
1598
|
-
}
|
|
1599
|
-
},
|
|
1600
|
-
subscribe(listener) {
|
|
1601
|
-
listeners.add(listener);
|
|
1602
|
-
return () => listeners.delete(listener);
|
|
1603
|
-
},
|
|
1604
|
-
getSnapshot: () => snapshot,
|
|
1605
|
-
dispatch,
|
|
1606
|
-
recordRpcSuccess() {
|
|
1607
|
-
heartbeatHandle?.recordRpcSuccess();
|
|
1608
|
-
},
|
|
1609
|
-
recordRpcFailure() {
|
|
1610
|
-
heartbeatHandle?.recordRpcFailure();
|
|
1611
|
-
}
|
|
1612
|
-
};
|
|
1613
|
-
return controls;
|
|
1614
|
-
}
|
|
1615
|
-
function buildReadyPayload(options) {
|
|
1616
|
-
const payload = {
|
|
1617
|
-
requiredAccess: options.requiredAccess ?? "read table",
|
|
1618
|
-
columns: options.columns ?? []
|
|
1619
|
-
};
|
|
1620
|
-
if (options.allowSelectBy !== void 0) payload.allowSelectBy = options.allowSelectBy;
|
|
1621
|
-
if (options.hasCustomOptions !== void 0) payload.hasCustomOptions = options.hasCustomOptions;
|
|
1622
|
-
if (options.onEditOptions) payload.onEditOptions = options.onEditOptions;
|
|
1623
|
-
return payload;
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
// src/sdk/internal/embedded-handshake-manager.ts
|
|
1627
|
-
var embedded = null;
|
|
1628
|
-
function acquireEmbeddedHandshakeManager(options) {
|
|
1629
|
-
if (!embedded) {
|
|
1630
|
-
embedded = {
|
|
1631
|
-
manager: createGristHandshakeManager(options),
|
|
1632
|
-
refCount: 0
|
|
1633
|
-
};
|
|
1634
|
-
embedded.manager.start();
|
|
1635
|
-
}
|
|
1636
|
-
embedded.refCount += 1;
|
|
1637
|
-
return embedded.manager;
|
|
1638
|
-
}
|
|
1639
|
-
function releaseEmbeddedHandshakeManager() {
|
|
1640
|
-
if (!embedded) return;
|
|
1641
|
-
embedded.refCount -= 1;
|
|
1642
|
-
if (embedded.refCount <= 0) {
|
|
1643
|
-
embedded.manager.stop();
|
|
1644
|
-
embedded = null;
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
|
-
// src/sdk/internal/handshake-manager-context.ts
|
|
1649
|
-
var GristHandshakeManagerContext = (0, import_react.createContext)(null);
|
|
1650
|
-
function useGristManagerOptional() {
|
|
1651
|
-
return (0, import_react.useContext)(GristHandshakeManagerContext);
|
|
1652
|
-
}
|
|
1653
|
-
function useGristManager(options) {
|
|
1654
|
-
const fromProvider = (0, import_react.useContext)(GristHandshakeManagerContext);
|
|
1655
|
-
const optionsRef = (0, import_react.useRef)(options);
|
|
1656
|
-
optionsRef.current = options;
|
|
1657
|
-
const embeddedRef = (0, import_react.useRef)(null);
|
|
1658
|
-
const ownsEmbeddedRef = (0, import_react.useRef)(false);
|
|
1659
|
-
if (fromProvider) {
|
|
1660
|
-
return fromProvider;
|
|
1661
|
-
}
|
|
1662
|
-
if (embeddedRef.current === null) {
|
|
1663
|
-
embeddedRef.current = acquireEmbeddedHandshakeManager(
|
|
1664
|
-
buildManagerOptionsFromGristOptions(optionsRef.current)
|
|
1665
|
-
);
|
|
1666
|
-
ownsEmbeddedRef.current = true;
|
|
1667
|
-
}
|
|
1668
|
-
(0, import_react.useEffect)(() => {
|
|
1669
|
-
if (!ownsEmbeddedRef.current) return;
|
|
1670
|
-
return () => {
|
|
1671
|
-
releaseEmbeddedHandshakeManager();
|
|
1672
|
-
embeddedRef.current = null;
|
|
1673
|
-
ownsEmbeddedRef.current = false;
|
|
1674
|
-
};
|
|
1675
|
-
}, []);
|
|
1676
|
-
return embeddedRef.current;
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
// src/sdk/internal/hooks/use-grist-core.ts
|
|
1680
|
-
function computeAvailabilityFromSnapshot(snapshot) {
|
|
1681
|
-
const { phase } = snapshot.lifecycle;
|
|
1682
|
-
if (phase === "idle" || phase === "detecting") return null;
|
|
1683
|
-
if (phase === "terminated") {
|
|
1684
|
-
const reason = snapshot.lifecycle.reason.kind;
|
|
1685
|
-
if (reason === "not_embedded" || reason === "script_timeout") return false;
|
|
1686
|
-
return true;
|
|
1687
|
-
}
|
|
1688
|
-
return true;
|
|
1689
|
-
}
|
|
1690
|
-
function useGristCore(options) {
|
|
1691
|
-
const manager = useGristManager(options);
|
|
1692
|
-
return useGristCoreFromManager(manager);
|
|
1693
|
-
}
|
|
1694
|
-
function useGristCoreFromManager(manager) {
|
|
1695
|
-
const snapshot = (0, import_react2.useSyncExternalStore)(
|
|
1696
|
-
manager.subscribe,
|
|
1697
|
-
manager.getSnapshot,
|
|
1698
|
-
initialGristSnapshot
|
|
1699
|
-
);
|
|
1700
|
-
const status = deriveStatus(snapshot);
|
|
1701
|
-
const error = deriveErrorMessage(snapshot);
|
|
1702
|
-
const docApi = snapshot.lifecycle.phase === "online" && typeof grist !== "undefined" ? grist.docApi ?? null : null;
|
|
1703
|
-
const isAvailable = computeAvailabilityFromSnapshot(snapshot);
|
|
1704
|
-
const reload = (0, import_react2.useCallback)(async () => {
|
|
1705
|
-
manager.reload();
|
|
1706
|
-
}, [manager]);
|
|
1707
|
-
const capabilities = (0, import_react2.useMemo)(() => deriveCapabilities(snapshot), [snapshot]);
|
|
1708
|
-
return (0, import_react2.useMemo)(
|
|
1709
|
-
() => ({
|
|
1710
|
-
status,
|
|
1711
|
-
isAvailable,
|
|
1712
|
-
isReady: status === "ready",
|
|
1713
|
-
error,
|
|
1714
|
-
docApi,
|
|
1715
|
-
capabilities,
|
|
1716
|
-
reload
|
|
1717
|
-
}),
|
|
1718
|
-
[status, isAvailable, error, docApi, capabilities, reload]
|
|
1719
|
-
);
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
// src/sdk/low-level-hooks/use-grist-availability.ts
|
|
1723
|
-
function useGristAvailability(options) {
|
|
1724
|
-
const { isAvailable } = useGristCore(options);
|
|
1725
|
-
return isAvailable;
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
// src/sdk/low-level-hooks/use-grist-ready.ts
|
|
1729
|
-
function useGristReady(options) {
|
|
1730
|
-
const { isAvailable, docApi, error, reload } = useGristCore(options);
|
|
1731
|
-
return { isAvailable, docApi, error, reload };
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
// src/sdk/low-level-hooks/use-grist-handshake.ts
|
|
1735
|
-
var import_react3 = require("react");
|
|
1736
|
-
function useGristHandshake(options = {}) {
|
|
1737
|
-
const optionsRef = (0, import_react3.useRef)(options);
|
|
1738
|
-
optionsRef.current = options;
|
|
1739
|
-
const managerRef = (0, import_react3.useRef)(null);
|
|
1740
|
-
if (managerRef.current === null) {
|
|
1741
|
-
managerRef.current = createGristHandshakeManager({
|
|
1742
|
-
...options,
|
|
1743
|
-
negotiate: {
|
|
1744
|
-
readyImpl: (payload) => ensureGristReady(payload),
|
|
1745
|
-
...options.negotiate
|
|
1746
|
-
},
|
|
1747
|
-
onBeforeReload: () => {
|
|
1748
|
-
resetGristReadySingleton();
|
|
1749
|
-
options.onBeforeReload?.();
|
|
1750
|
-
}
|
|
1751
|
-
});
|
|
1752
|
-
}
|
|
1753
|
-
const manager = managerRef.current;
|
|
1754
|
-
(0, import_react3.useEffect)(() => {
|
|
1755
|
-
manager.start();
|
|
1756
|
-
return () => {
|
|
1757
|
-
manager.stop();
|
|
1758
|
-
};
|
|
1759
|
-
}, [manager]);
|
|
1760
|
-
const snapshot = (0, import_react3.useSyncExternalStore)(
|
|
1761
|
-
manager.subscribe,
|
|
1762
|
-
manager.getSnapshot,
|
|
1763
|
-
initialGristSnapshot
|
|
1764
|
-
);
|
|
1765
|
-
const projected = (0, import_react3.useMemo)(
|
|
1766
|
-
() => ({
|
|
1767
|
-
status: deriveStatus(snapshot),
|
|
1768
|
-
error: deriveErrorMessage(snapshot),
|
|
1769
|
-
capabilities: deriveCapabilities(snapshot)
|
|
1770
|
-
}),
|
|
1771
|
-
[snapshot]
|
|
1772
|
-
);
|
|
1773
|
-
return (0, import_react3.useMemo)(
|
|
1774
|
-
() => ({
|
|
1775
|
-
snapshot,
|
|
1776
|
-
status: projected.status,
|
|
1777
|
-
error: projected.error,
|
|
1778
|
-
capabilities: projected.capabilities,
|
|
1779
|
-
reload: manager.reload,
|
|
1780
|
-
restart: manager.restart
|
|
1781
|
-
}),
|
|
1782
|
-
[snapshot, projected, manager]
|
|
1783
|
-
);
|
|
1784
|
-
}
|
|
1785
|
-
function useGristCapabilities(options = {}) {
|
|
1786
|
-
return useGristHandshake(options).capabilities;
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
// src/sdk/low-level-hooks/use-ambient-grist-handshake.ts
|
|
1790
|
-
var import_react4 = require("react");
|
|
1791
|
-
var noopSubscribe = () => () => {
|
|
1792
|
-
};
|
|
1793
|
-
var getInitialSnapshot = () => initialGristSnapshot();
|
|
1794
|
-
function useAmbientGristHandshake() {
|
|
1795
|
-
const manager = useGristManagerOptional();
|
|
1796
|
-
const snapshot = (0, import_react4.useSyncExternalStore)(
|
|
1797
|
-
manager?.subscribe ?? noopSubscribe,
|
|
1798
|
-
manager?.getSnapshot ?? getInitialSnapshot,
|
|
1799
|
-
getInitialSnapshot
|
|
1800
|
-
);
|
|
1801
|
-
return (0, import_react4.useMemo)(() => {
|
|
1802
|
-
if (!manager) return null;
|
|
1803
|
-
return {
|
|
1804
|
-
snapshot,
|
|
1805
|
-
status: deriveStatus(snapshot),
|
|
1806
|
-
error: deriveErrorMessage(snapshot),
|
|
1807
|
-
capabilities: deriveCapabilities(snapshot),
|
|
1808
|
-
reload: manager.reload,
|
|
1809
|
-
restart: manager.restart
|
|
1810
|
-
};
|
|
1811
|
-
}, [manager, snapshot]);
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
// src/sdk/components/grist-handshake-provider.tsx
|
|
1815
|
-
var import_react5 = require("react");
|
|
1816
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1817
|
-
var GristHandshakeReactContext = (0, import_react5.createContext)(null);
|
|
1818
|
-
function GristHandshakeProvider({ options, children }) {
|
|
1819
|
-
const managerRef = (0, import_react5.useRef)(null);
|
|
1820
|
-
if (managerRef.current === null) {
|
|
1821
|
-
managerRef.current = createGristHandshakeManager({
|
|
1822
|
-
...options,
|
|
1823
|
-
negotiate: {
|
|
1824
|
-
readyImpl: (payload) => ensureGristReady(payload),
|
|
1825
|
-
...options?.negotiate
|
|
1826
|
-
},
|
|
1827
|
-
onBeforeReload: () => {
|
|
1828
|
-
resetGristReadySingleton();
|
|
1829
|
-
options?.onBeforeReload?.();
|
|
1830
|
-
}
|
|
1831
|
-
});
|
|
1832
|
-
}
|
|
1833
|
-
const manager = managerRef.current;
|
|
1834
|
-
(0, import_react5.useEffect)(() => {
|
|
1835
|
-
manager.start();
|
|
1836
|
-
return () => {
|
|
1837
|
-
manager.stop();
|
|
1838
|
-
};
|
|
1839
|
-
}, [manager]);
|
|
1840
|
-
const snapshot = (0, import_react5.useSyncExternalStore)(
|
|
1841
|
-
manager.subscribe,
|
|
1842
|
-
manager.getSnapshot,
|
|
1843
|
-
initialGristSnapshot
|
|
1844
|
-
);
|
|
1845
|
-
const value = (0, import_react5.useMemo)(
|
|
1846
|
-
() => ({
|
|
1847
|
-
snapshot,
|
|
1848
|
-
status: deriveStatus(snapshot),
|
|
1849
|
-
error: deriveErrorMessage(snapshot),
|
|
1850
|
-
capabilities: deriveCapabilities(snapshot),
|
|
1851
|
-
reload: manager.reload,
|
|
1852
|
-
restart: manager.restart,
|
|
1853
|
-
recordRpcSuccess: manager.recordRpcSuccess,
|
|
1854
|
-
recordRpcFailure: manager.recordRpcFailure
|
|
1855
|
-
}),
|
|
1856
|
-
[snapshot, manager]
|
|
1857
|
-
);
|
|
1858
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GristHandshakeReactContext.Provider, { value, children });
|
|
1859
|
-
}
|
|
1860
|
-
function useGristHandshakeContext() {
|
|
1861
|
-
const ctx = (0, import_react5.useContext)(GristHandshakeReactContext);
|
|
1862
|
-
if (!ctx) {
|
|
1863
|
-
throw new Error(
|
|
1864
|
-
"useGristHandshakeContext() must be used inside <GristHandshakeProvider>"
|
|
1865
|
-
);
|
|
1866
|
-
}
|
|
1867
|
-
return ctx;
|
|
1868
|
-
}
|
|
1869
|
-
function useGristHandshakeContextOptional() {
|
|
1870
|
-
return (0, import_react5.useContext)(GristHandshakeReactContext);
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
|
-
// src/sdk/low-level-hooks/use-grist-record.ts
|
|
1874
|
-
var import_react6 = require("react");
|
|
1875
|
-
function useGristRecord(cb, options) {
|
|
1876
|
-
const cbRef = (0, import_react6.useRef)(cb);
|
|
1877
|
-
(0, import_react6.useEffect)(() => {
|
|
1878
|
-
cbRef.current = cb;
|
|
1879
|
-
}, [cb]);
|
|
1880
|
-
const [mappings, setMappings] = (0, import_react6.useState)(null);
|
|
1881
|
-
const { docApi, error: readyError } = useGristReady({
|
|
1882
|
-
requiredAccess: options?.requiredAccess ?? "read table",
|
|
1883
|
-
columns: options?.columns,
|
|
1884
|
-
allowSelectBy: options?.allowSelectBy,
|
|
1885
|
-
hasCustomOptions: options?.hasCustomOptions,
|
|
1886
|
-
onEditOptions: options?.onEditOptions
|
|
1887
|
-
});
|
|
1888
|
-
(0, import_react6.useEffect)(() => {
|
|
1889
|
-
if (!docApi) return;
|
|
1890
|
-
return recordEvent.subscribe(({ record, mappings: nextMappings }) => {
|
|
1891
|
-
setMappings(nextMappings);
|
|
1892
|
-
cbRef.current(record, nextMappings);
|
|
1893
|
-
});
|
|
1894
|
-
}, [docApi]);
|
|
1895
|
-
return {
|
|
1896
|
-
loading: docApi === null && !readyError,
|
|
1897
|
-
error: readyError,
|
|
1898
|
-
mappings
|
|
1899
|
-
};
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
// src/sdk/low-level-hooks/use-grist-records.ts
|
|
1903
|
-
var import_react7 = require("react");
|
|
1904
|
-
function useGristRecords(cb, options) {
|
|
1905
|
-
const cbRef = (0, import_react7.useRef)(cb);
|
|
1906
|
-
(0, import_react7.useEffect)(() => {
|
|
1907
|
-
cbRef.current = cb;
|
|
1908
|
-
}, [cb]);
|
|
1909
|
-
const [mappings, setMappings] = (0, import_react7.useState)(null);
|
|
1910
|
-
const { docApi, error: readyError } = useGristReady({
|
|
1911
|
-
requiredAccess: options?.requiredAccess ?? "read table",
|
|
1912
|
-
columns: options?.columns,
|
|
1913
|
-
allowSelectBy: options?.allowSelectBy,
|
|
1914
|
-
hasCustomOptions: options?.hasCustomOptions,
|
|
1915
|
-
onEditOptions: options?.onEditOptions
|
|
1916
|
-
});
|
|
1917
|
-
(0, import_react7.useEffect)(() => {
|
|
1918
|
-
if (!docApi) return;
|
|
1919
|
-
return recordsEvent.subscribe(({ records, mappings: nextMappings }) => {
|
|
1920
|
-
setMappings(nextMappings);
|
|
1921
|
-
cbRef.current(records, nextMappings);
|
|
1922
|
-
});
|
|
1923
|
-
}, [docApi]);
|
|
1924
|
-
return {
|
|
1925
|
-
loading: docApi === null && !readyError,
|
|
1926
|
-
error: readyError,
|
|
1927
|
-
mappings
|
|
1928
|
-
};
|
|
1929
|
-
}
|
|
1930
|
-
|
|
1931
|
-
// src/sdk/low-level-hooks/use-grist-new-record.ts
|
|
1932
|
-
var import_react8 = require("react");
|
|
1933
|
-
function useGristNewRecord(options) {
|
|
1934
|
-
const { docApi, error: readyError } = useGristReady({
|
|
1935
|
-
requiredAccess: options?.requiredAccess ?? "read table",
|
|
1936
|
-
columns: options?.columns,
|
|
1937
|
-
allowSelectBy: options?.allowSelectBy,
|
|
1938
|
-
hasCustomOptions: options?.hasCustomOptions,
|
|
1939
|
-
onEditOptions: options?.onEditOptions
|
|
1940
|
-
});
|
|
1941
|
-
const [isNewRecord, setIsNewRecord] = (0, import_react8.useState)(false);
|
|
1942
|
-
const [mappings, setMappings] = (0, import_react8.useState)(null);
|
|
1943
|
-
const subscribedRef = (0, import_react8.useRef)(false);
|
|
1944
|
-
(0, import_react8.useEffect)(() => {
|
|
1945
|
-
if (!docApi || subscribedRef.current) return;
|
|
1946
|
-
if (typeof grist.onNewRecord !== "function") return;
|
|
1947
|
-
subscribedRef.current = true;
|
|
1948
|
-
return newRecordEvent.subscribe(({ mappings: nextMappings }) => {
|
|
1949
|
-
setIsNewRecord(true);
|
|
1950
|
-
setMappings(nextMappings);
|
|
1951
|
-
});
|
|
1952
|
-
}, [docApi]);
|
|
1953
|
-
return {
|
|
1954
|
-
isNewRecord,
|
|
1955
|
-
mappings,
|
|
1956
|
-
loading: docApi === null && !readyError,
|
|
1957
|
-
error: readyError
|
|
1958
|
-
};
|
|
1959
|
-
}
|
|
1960
|
-
|
|
1961
|
-
// src/sdk/mid-level-hooks/use-grist-actions.ts
|
|
1962
|
-
var import_react10 = require("react");
|
|
1963
|
-
|
|
1964
|
-
// src/sdk/helpers/grist-user-actions.ts
|
|
1965
|
-
function gristUpdateRecordAction(tableId, rowId, patch) {
|
|
1966
|
-
return ["UpdateRecord", tableId, rowId, patch];
|
|
1967
|
-
}
|
|
1968
|
-
function gristAddRecordAction(tableId, values) {
|
|
1969
|
-
return ["AddRecord", tableId, null, values];
|
|
1970
|
-
}
|
|
1971
|
-
function gristRemoveRecordAction(tableId, rowId) {
|
|
1972
|
-
return ["RemoveRecord", tableId, rowId];
|
|
1973
|
-
}
|
|
1974
|
-
function gristBulkUpdateRecordAction(tableId, rowIds, columnarPatch) {
|
|
1975
|
-
return ["BulkUpdateRecord", tableId, rowIds, columnarPatch];
|
|
1976
|
-
}
|
|
1977
|
-
function gristBulkAddRecordAction(tableId, values, rowIds) {
|
|
1978
|
-
const length = inferBulkLength(values);
|
|
1979
|
-
const ids = rowIds ?? new Array(length).fill(null);
|
|
1980
|
-
return ["BulkAddRecord", tableId, ids, values];
|
|
1981
|
-
}
|
|
1982
|
-
function gristBulkRemoveRecordAction(tableId, rowIds) {
|
|
1983
|
-
return ["BulkRemoveRecord", tableId, rowIds];
|
|
1984
|
-
}
|
|
1985
|
-
function inferBulkLength(values) {
|
|
1986
|
-
for (const arr of Object.values(values)) if (Array.isArray(arr)) return arr.length;
|
|
1987
|
-
return 0;
|
|
1988
|
-
}
|
|
1989
|
-
|
|
1990
|
-
// src/sdk/internal/hooks/use-rpc-heartbeat-coalesce.ts
|
|
1991
|
-
var import_react9 = require("react");
|
|
1992
|
-
function useRpcHeartbeatCoalesce() {
|
|
1993
|
-
const manager = useGristManagerOptional();
|
|
1994
|
-
return (0, import_react9.useCallback)(
|
|
1995
|
-
async (fn) => {
|
|
1996
|
-
const result = await fn();
|
|
1997
|
-
manager?.recordRpcSuccess();
|
|
1998
|
-
return result;
|
|
1999
|
-
},
|
|
2000
|
-
[manager]
|
|
2001
|
-
);
|
|
2002
|
-
}
|
|
2003
|
-
|
|
2004
|
-
// src/sdk/mid-level-hooks/use-grist-actions.ts
|
|
2005
|
-
function useGristActions(options) {
|
|
2006
|
-
const requiredAccess = options?.requiredAccess ?? "full";
|
|
2007
|
-
const { docApi, error: readyError } = useGristReady({
|
|
2008
|
-
requiredAccess,
|
|
2009
|
-
columns: options?.columns,
|
|
2010
|
-
allowSelectBy: options?.allowSelectBy,
|
|
2011
|
-
hasCustomOptions: options?.hasCustomOptions,
|
|
2012
|
-
onEditOptions: options?.onEditOptions
|
|
2013
|
-
});
|
|
2014
|
-
const [status, setStatus] = (0, import_react10.useState)("idle");
|
|
2015
|
-
const [actionError, setActionError] = (0, import_react10.useState)(null);
|
|
2016
|
-
const coalesceRpc = useRpcHeartbeatCoalesce();
|
|
2017
|
-
const apply = (0, import_react10.useCallback)(
|
|
2018
|
-
async (actions, applyOptions) => {
|
|
2019
|
-
if (!docApi) throw new Error("Grist docApi is not ready");
|
|
2020
|
-
setStatus("running");
|
|
2021
|
-
setActionError(null);
|
|
2022
|
-
try {
|
|
2023
|
-
const out = await coalesceRpc(() => docApi.applyUserActions(actions, applyOptions));
|
|
2024
|
-
setStatus("idle");
|
|
2025
|
-
return out;
|
|
2026
|
-
} catch (err) {
|
|
2027
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2028
|
-
setActionError(message);
|
|
2029
|
-
setStatus("error");
|
|
2030
|
-
throw err;
|
|
2031
|
-
}
|
|
2032
|
-
},
|
|
2033
|
-
[docApi, coalesceRpc]
|
|
2034
|
-
);
|
|
2035
|
-
const updateRecord = (0, import_react10.useCallback)(
|
|
2036
|
-
(tableId, recordId, patch, applyOptions) => apply([gristUpdateRecordAction(tableId, recordId, patch)], applyOptions),
|
|
2037
|
-
[apply]
|
|
2038
|
-
);
|
|
2039
|
-
const addRecord = (0, import_react10.useCallback)(
|
|
2040
|
-
(tableId, values, applyOptions) => apply([gristAddRecordAction(tableId, values)], applyOptions),
|
|
2041
|
-
[apply]
|
|
2042
|
-
);
|
|
2043
|
-
const removeRecord = (0, import_react10.useCallback)(
|
|
2044
|
-
(tableId, recordId, applyOptions) => apply([gristRemoveRecordAction(tableId, recordId)], applyOptions),
|
|
2045
|
-
[apply]
|
|
2046
|
-
);
|
|
2047
|
-
const bulkAddRecord = (0, import_react10.useCallback)(
|
|
2048
|
-
(tableId, values, applyOptions) => apply([gristBulkAddRecordAction(tableId, values)], applyOptions),
|
|
2049
|
-
[apply]
|
|
2050
|
-
);
|
|
2051
|
-
const bulkUpdateRecord = (0, import_react10.useCallback)(
|
|
2052
|
-
(tableId, rowIds, columnarPatch, applyOptions) => apply([gristBulkUpdateRecordAction(tableId, rowIds, columnarPatch)], applyOptions),
|
|
2053
|
-
[apply]
|
|
2054
|
-
);
|
|
2055
|
-
const bulkRemoveRecord = (0, import_react10.useCallback)(
|
|
2056
|
-
(tableId, rowIds, applyOptions) => apply([gristBulkRemoveRecordAction(tableId, rowIds)], applyOptions),
|
|
2057
|
-
[apply]
|
|
2058
|
-
);
|
|
2059
|
-
return {
|
|
2060
|
-
status: readyError ? "error" : status,
|
|
2061
|
-
error: readyError ?? actionError,
|
|
2062
|
-
apply,
|
|
2063
|
-
updateRecord,
|
|
2064
|
-
addRecord,
|
|
2065
|
-
removeRecord,
|
|
2066
|
-
bulkAddRecord,
|
|
2067
|
-
bulkUpdateRecord,
|
|
2068
|
-
bulkRemoveRecord
|
|
2069
|
-
};
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
// src/sdk/mid-level-hooks/use-grist-mapped-record.ts
|
|
2073
|
-
var import_react11 = require("react");
|
|
2074
|
-
function manualMapBack(patch, mappings) {
|
|
2075
|
-
if (!mappings) return { ...patch };
|
|
2076
|
-
const out = {};
|
|
2077
|
-
for (const [name, value] of Object.entries(patch)) {
|
|
2078
|
-
const target = mappings[name];
|
|
2079
|
-
if (Array.isArray(target)) {
|
|
2080
|
-
if (typeof console !== "undefined") {
|
|
2081
|
-
console.warn(`[lib/grist] mapBack: cannot reverse-map allowMultiple field "${name}" without grist.mapColumnNamesBack`);
|
|
2082
|
-
}
|
|
2083
|
-
continue;
|
|
2084
|
-
}
|
|
2085
|
-
out[target ?? name] = value;
|
|
2086
|
-
}
|
|
2087
|
-
return out;
|
|
2088
|
-
}
|
|
2089
|
-
function useGristMappedRecord(options) {
|
|
2090
|
-
const requiredAccess = options?.requiredAccess ?? "read table";
|
|
2091
|
-
const { docApi, error: readyError } = useGristReady({
|
|
2092
|
-
requiredAccess,
|
|
2093
|
-
columns: options?.columns,
|
|
2094
|
-
allowSelectBy: options?.allowSelectBy,
|
|
2095
|
-
hasCustomOptions: options?.hasCustomOptions,
|
|
2096
|
-
onEditOptions: options?.onEditOptions
|
|
2097
|
-
});
|
|
2098
|
-
const [rawRecord, setRawRecord] = (0, import_react11.useState)(null);
|
|
2099
|
-
const [mappedRecord, setMappedRecord] = (0, import_react11.useState)(null);
|
|
2100
|
-
const [mappings, setMappings] = (0, import_react11.useState)(null);
|
|
2101
|
-
(0, import_react11.useEffect)(() => {
|
|
2102
|
-
if (!docApi) return;
|
|
2103
|
-
return recordEvent.subscribe(({ record, mappings: nextMappings }) => {
|
|
2104
|
-
setRawRecord(record);
|
|
2105
|
-
setMappings(nextMappings);
|
|
2106
|
-
if (!record) {
|
|
2107
|
-
setMappedRecord(null);
|
|
2108
|
-
return;
|
|
2109
|
-
}
|
|
2110
|
-
if (typeof grist.mapColumnNames === "function") {
|
|
2111
|
-
const mapped = grist.mapColumnNames(record);
|
|
2112
|
-
setMappedRecord(
|
|
2113
|
-
mapped !== null && mapped !== void 0 ? mapped : null
|
|
2114
|
-
);
|
|
2115
|
-
return;
|
|
2116
|
-
}
|
|
2117
|
-
setMappedRecord(record);
|
|
2118
|
-
});
|
|
2119
|
-
}, [docApi]);
|
|
2120
|
-
const mapBack = (0, import_react11.useCallback)(
|
|
2121
|
-
(patch) => {
|
|
2122
|
-
if (typeof grist.mapColumnNamesBack === "function") {
|
|
2123
|
-
return grist.mapColumnNamesBack(patch);
|
|
2124
|
-
}
|
|
2125
|
-
return manualMapBack(patch, mappings);
|
|
2126
|
-
},
|
|
2127
|
-
[mappings]
|
|
2128
|
-
);
|
|
2129
|
-
const resolveColumnId = (0, import_react11.useCallback)(
|
|
2130
|
-
(logicalName) => {
|
|
2131
|
-
const value = mappings?.[logicalName];
|
|
2132
|
-
if (Array.isArray(value)) return void 0;
|
|
2133
|
-
return value ?? void 0;
|
|
2134
|
-
},
|
|
2135
|
-
[mappings]
|
|
2136
|
-
);
|
|
2137
|
-
return {
|
|
2138
|
-
rawRecord,
|
|
2139
|
-
mappedRecord,
|
|
2140
|
-
mappings,
|
|
2141
|
-
loading: docApi === null && !readyError,
|
|
2142
|
-
error: readyError,
|
|
2143
|
-
mapBack,
|
|
2144
|
-
resolveColumnId
|
|
2145
|
-
};
|
|
2146
|
-
}
|
|
2147
|
-
|
|
2148
|
-
// src/sdk/mid-level-hooks/use-grist-table-ops.ts
|
|
2149
|
-
var import_react12 = require("react");
|
|
2150
|
-
function resolveOps(tableId) {
|
|
2151
|
-
if (tableId === "selected") {
|
|
2152
|
-
if (!grist.selectedTable) throw new Error("grist.selectedTable is not available");
|
|
2153
|
-
return grist.selectedTable;
|
|
2154
|
-
}
|
|
2155
|
-
if (typeof grist.getTable !== "function") {
|
|
2156
|
-
throw new Error("grist.getTable is not available in this Grist version");
|
|
2157
|
-
}
|
|
2158
|
-
return grist.getTable(tableId);
|
|
2159
|
-
}
|
|
2160
|
-
function useGristTableOps(options) {
|
|
2161
|
-
const requiredAccess = options?.requiredAccess ?? "full";
|
|
2162
|
-
const tableIdOption = options?.tableId ?? "selected";
|
|
2163
|
-
const { docApi, error: readyError } = useGristReady({
|
|
2164
|
-
requiredAccess,
|
|
2165
|
-
columns: options?.columns,
|
|
2166
|
-
allowSelectBy: options?.allowSelectBy,
|
|
2167
|
-
hasCustomOptions: options?.hasCustomOptions,
|
|
2168
|
-
onEditOptions: options?.onEditOptions
|
|
2169
|
-
});
|
|
2170
|
-
const [status, setStatus] = (0, import_react12.useState)("idle");
|
|
2171
|
-
const [actionError, setActionError] = (0, import_react12.useState)(null);
|
|
2172
|
-
const coalesceRpc = useRpcHeartbeatCoalesce();
|
|
2173
|
-
let ops = null;
|
|
2174
|
-
let resolveError = null;
|
|
2175
|
-
if (docApi) {
|
|
2176
|
-
try {
|
|
2177
|
-
ops = resolveOps(tableIdOption);
|
|
2178
|
-
} catch (err) {
|
|
2179
|
-
resolveError = err instanceof Error ? err.message : String(err);
|
|
2180
|
-
}
|
|
2181
|
-
}
|
|
2182
|
-
const wrap = (0, import_react12.useCallback)(
|
|
2183
|
-
async (run) => {
|
|
2184
|
-
if (!ops) throw new Error("Grist table operations are not ready");
|
|
2185
|
-
setStatus("running");
|
|
2186
|
-
setActionError(null);
|
|
2187
|
-
try {
|
|
2188
|
-
const out = await coalesceRpc(() => run(ops));
|
|
2189
|
-
setStatus("idle");
|
|
2190
|
-
return out;
|
|
2191
|
-
} catch (err) {
|
|
2192
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2193
|
-
setActionError(message);
|
|
2194
|
-
setStatus("error");
|
|
2195
|
-
throw err;
|
|
2196
|
-
}
|
|
2197
|
-
},
|
|
2198
|
-
[ops, coalesceRpc]
|
|
2199
|
-
);
|
|
2200
|
-
const getTableId = (0, import_react12.useCallback)(async () => {
|
|
2201
|
-
if (!ops) throw new Error("Grist table operations are not ready");
|
|
2202
|
-
return ops.getTableId();
|
|
2203
|
-
}, [ops]);
|
|
2204
|
-
const create = (0, import_react12.useCallback)(
|
|
2205
|
-
(records, opOptions) => wrap((o) => o.create(records, opOptions)),
|
|
2206
|
-
[wrap]
|
|
2207
|
-
);
|
|
2208
|
-
const update = (0, import_react12.useCallback)(
|
|
2209
|
-
(records, opOptions) => wrap((o) => o.update(records, opOptions)),
|
|
2210
|
-
[wrap]
|
|
2211
|
-
);
|
|
2212
|
-
const upsert = (0, import_react12.useCallback)(
|
|
2213
|
-
(records, opOptions) => wrap((o) => o.upsert(records, opOptions)),
|
|
2214
|
-
[wrap]
|
|
2215
|
-
);
|
|
2216
|
-
const destroy = (0, import_react12.useCallback)(
|
|
2217
|
-
(recordIds) => wrap((o) => o.destroy(recordIds)),
|
|
2218
|
-
[wrap]
|
|
2219
|
-
);
|
|
2220
|
-
return {
|
|
2221
|
-
status: readyError || resolveError ? "error" : status,
|
|
2222
|
-
error: readyError ?? resolveError ?? actionError,
|
|
2223
|
-
getTableId,
|
|
2224
|
-
create,
|
|
2225
|
-
update,
|
|
2226
|
-
upsert,
|
|
2227
|
-
destroy
|
|
2228
|
-
};
|
|
2229
|
-
}
|
|
2230
|
-
|
|
2231
|
-
// src/sdk/mid-level-hooks/use-grist-rows-from-table.ts
|
|
2232
|
-
var import_react14 = require("react");
|
|
2233
|
-
|
|
2234
|
-
// src/sdk/helpers/grist-table-data.ts
|
|
2235
|
-
function tableDataToRows(data) {
|
|
2236
|
-
const idCol = data.id;
|
|
2237
|
-
if (!Array.isArray(idCol) || idCol.length === 0) return [];
|
|
2238
|
-
const keys = Object.keys(data);
|
|
2239
|
-
const n = idCol.length;
|
|
2240
|
-
const rows = [];
|
|
2241
|
-
for (let i = 0; i < n; i++) {
|
|
2242
|
-
const row = {};
|
|
2243
|
-
for (const k of keys) {
|
|
2244
|
-
const col = data[k];
|
|
2245
|
-
row[k] = Array.isArray(col) ? col[i] : void 0;
|
|
2246
|
-
}
|
|
2247
|
-
rows.push(row);
|
|
2248
|
-
}
|
|
2249
|
-
return rows;
|
|
2250
|
-
}
|
|
2251
|
-
|
|
2252
|
-
// src/sdk/internal/contexts.tsx
|
|
2253
|
-
var import_react13 = require("react");
|
|
2254
|
-
var GristCoreContext = (0, import_react13.createContext)(null);
|
|
2255
|
-
var GristSelectionContext = (0, import_react13.createContext)(null);
|
|
2256
|
-
var GristCurrentTableContext = (0, import_react13.createContext)(null);
|
|
2257
|
-
var GristWritesContext = (0, import_react13.createContext)(null);
|
|
2258
|
-
var GristReadsContext = (0, import_react13.createContext)(null);
|
|
2259
|
-
var GristWidgetOptionsApiContext = (0, import_react13.createContext)(null);
|
|
2260
|
-
var GristLinkingContext = (0, import_react13.createContext)(null);
|
|
2261
|
-
var GristAttachmentsRestContext = (0, import_react13.createContext)(null);
|
|
2262
|
-
var GristThemeContext = (0, import_react13.createContext)(null);
|
|
2263
|
-
var GristSectionApiContext = (0, import_react13.createContext)(null);
|
|
2264
|
-
function useRequiredContext(ctx, name) {
|
|
2265
|
-
const value = (0, import_react13.useContext)(ctx);
|
|
2266
|
-
if (!value) {
|
|
2267
|
-
throw new Error(`${name} must be used inside <GristWidgetProvider>`);
|
|
2268
|
-
}
|
|
2269
|
-
return value;
|
|
2270
|
-
}
|
|
2271
|
-
function useGristSelectionContext() {
|
|
2272
|
-
return useRequiredContext(GristSelectionContext, "useGristSelectionContext()");
|
|
2273
|
-
}
|
|
2274
|
-
function useGristWidgetOptionsApiContext() {
|
|
2275
|
-
return useRequiredContext(GristWidgetOptionsApiContext, "useGristWidgetOptionsApiContext()");
|
|
2276
|
-
}
|
|
2277
|
-
|
|
2278
|
-
// src/sdk/mid-level-hooks/use-grist-rows-from-table.ts
|
|
2279
|
-
function useGristRowsFromTable(options) {
|
|
2280
|
-
const requiredAccess = options.requiredAccess ?? "read table";
|
|
2281
|
-
const { docApi, error: readyError } = useGristReady({
|
|
2282
|
-
requiredAccess,
|
|
2283
|
-
columns: options.columns,
|
|
2284
|
-
allowSelectBy: options.allowSelectBy,
|
|
2285
|
-
hasCustomOptions: options.hasCustomOptions,
|
|
2286
|
-
onEditOptions: options.onEditOptions
|
|
2287
|
-
});
|
|
2288
|
-
const providerFetchTable = (0, import_react14.useContext)(GristReadsContext)?.fetchTable;
|
|
2289
|
-
const [rows, setRows] = (0, import_react14.useState)(null);
|
|
2290
|
-
const [loading, setLoading] = (0, import_react14.useState)(false);
|
|
2291
|
-
const [fetchError, setFetchError] = (0, import_react14.useState)(null);
|
|
2292
|
-
const transformRef = (0, import_react14.useRef)(options.transform);
|
|
2293
|
-
transformRef.current = options.transform;
|
|
2294
|
-
const coalesceRpc = useRpcHeartbeatCoalesce();
|
|
2295
|
-
const refresh = (0, import_react14.useCallback)(async () => {
|
|
2296
|
-
if (!docApi) return;
|
|
2297
|
-
setLoading(true);
|
|
2298
|
-
setFetchError(null);
|
|
2299
|
-
try {
|
|
2300
|
-
const data = providerFetchTable ? await providerFetchTable(options.tableId) : await coalesceRpc(
|
|
2301
|
-
() => docApi.fetchTable(options.tableId)
|
|
2302
|
-
);
|
|
2303
|
-
const raw = tableDataToRows(data);
|
|
2304
|
-
const t = transformRef.current;
|
|
2305
|
-
setRows(t ? raw.map((r) => t(r)) : raw);
|
|
2306
|
-
} catch (err) {
|
|
2307
|
-
setFetchError(err instanceof Error ? err.message : String(err));
|
|
2308
|
-
setRows(null);
|
|
2309
|
-
} finally {
|
|
2310
|
-
setLoading(false);
|
|
2311
|
-
}
|
|
2312
|
-
}, [docApi, providerFetchTable, options.tableId, coalesceRpc]);
|
|
2313
|
-
(0, import_react14.useEffect)(() => {
|
|
2314
|
-
if (docApi) void refresh();
|
|
2315
|
-
}, [docApi, refresh]);
|
|
2316
|
-
(0, import_react14.useEffect)(() => {
|
|
2317
|
-
if (!docApi || !options.pollIntervalMs || options.pollIntervalMs <= 0) return;
|
|
2318
|
-
const id = window.setInterval(() => void refresh(), options.pollIntervalMs);
|
|
2319
|
-
return () => window.clearInterval(id);
|
|
2320
|
-
}, [docApi, options.pollIntervalMs, refresh]);
|
|
2321
|
-
return {
|
|
2322
|
-
rows,
|
|
2323
|
-
loading,
|
|
2324
|
-
error: readyError ?? fetchError,
|
|
2325
|
-
refresh
|
|
2326
|
-
};
|
|
2327
|
-
}
|
|
2328
|
-
|
|
2329
|
-
// src/sdk/mid-level-hooks/use-grist-access-token.ts
|
|
2330
|
-
var import_react15 = require("react");
|
|
2331
|
-
|
|
2332
|
-
// src/sdk/helpers/grist-fetch-with-auth.ts
|
|
2333
|
-
var TOKEN_TTL_MS = 45e3;
|
|
2334
|
-
var cache = /* @__PURE__ */ new Map();
|
|
2335
|
-
var inFlight2 = /* @__PURE__ */ new Map();
|
|
2336
|
-
function cacheKey(readOnly) {
|
|
2337
|
-
return readOnly ? "ro" : "rw";
|
|
2338
|
-
}
|
|
2339
|
-
async function getGristAccessToken(docApi, options) {
|
|
2340
|
-
if (!docApi.getAccessToken) {
|
|
2341
|
-
throw new Error("getAccessToken is not available on docApi (need higher access level)");
|
|
2342
|
-
}
|
|
2343
|
-
const readOnly = options?.readOnly ?? false;
|
|
2344
|
-
const key = cacheKey(readOnly);
|
|
2345
|
-
const now = Date.now();
|
|
2346
|
-
const hit = cache.get(key);
|
|
2347
|
-
if (hit && now - hit.fetchedAt < TOKEN_TTL_MS) {
|
|
2348
|
-
return hit.token;
|
|
2349
|
-
}
|
|
2350
|
-
const pending = inFlight2.get(key);
|
|
2351
|
-
if (pending) return pending;
|
|
2352
|
-
const next = docApi.getAccessToken({ readOnly }).then((token) => {
|
|
2353
|
-
cache.set(key, { token, fetchedAt: Date.now() });
|
|
2354
|
-
inFlight2.delete(key);
|
|
2355
|
-
return token;
|
|
2356
|
-
});
|
|
2357
|
-
inFlight2.set(key, next);
|
|
2358
|
-
return next;
|
|
2359
|
-
}
|
|
2360
|
-
function clearGristAccessTokenCache() {
|
|
2361
|
-
cache.clear();
|
|
2362
|
-
inFlight2.clear();
|
|
2363
|
-
}
|
|
2364
|
-
async function fetchWithGristAuth(docApi, path, init) {
|
|
2365
|
-
const readOnly = init?.readOnly ?? false;
|
|
2366
|
-
let token;
|
|
2367
|
-
try {
|
|
2368
|
-
token = await getGristAccessToken(docApi, { readOnly });
|
|
2369
|
-
} catch (err) {
|
|
2370
|
-
clearGristAccessTokenCache();
|
|
2371
|
-
throw err;
|
|
2372
|
-
}
|
|
2373
|
-
const isAbsolute = /^https?:\/\//i.test(path);
|
|
2374
|
-
const cleanPath = isAbsolute ? path : path.startsWith("/") ? path : `/${path}`;
|
|
2375
|
-
const url = isAbsolute ? new URL(path) : new URL(`${token.baseUrl.replace(/\/$/, "")}${cleanPath}`);
|
|
2376
|
-
if (!url.searchParams.has("auth")) url.searchParams.set("auth", token.token);
|
|
2377
|
-
const headers = new Headers(init?.headers ?? {});
|
|
2378
|
-
headers.delete("Authorization");
|
|
2379
|
-
const response = await fetch(url.toString(), { ...init, headers });
|
|
2380
|
-
if (response.status === 401) {
|
|
2381
|
-
clearGristAccessTokenCache();
|
|
2382
|
-
const retryToken = await getGristAccessToken(docApi, { readOnly });
|
|
2383
|
-
url.searchParams.set("auth", retryToken.token);
|
|
2384
|
-
return fetch(url.toString(), { ...init, headers });
|
|
2385
|
-
}
|
|
2386
|
-
return response;
|
|
2387
|
-
}
|
|
2388
|
-
|
|
2389
|
-
// src/sdk/mid-level-hooks/use-grist-access-token.ts
|
|
2390
|
-
function useGristAccessToken(options) {
|
|
2391
|
-
const requiredAccess = options?.requiredAccess ?? "full";
|
|
2392
|
-
const readOnlyDefault = options?.readOnly ?? false;
|
|
2393
|
-
const prefetch = options?.prefetch ?? false;
|
|
2394
|
-
const { docApi, error: readyError } = useGristReady({
|
|
2395
|
-
requiredAccess,
|
|
2396
|
-
columns: options?.columns,
|
|
2397
|
-
allowSelectBy: options?.allowSelectBy,
|
|
2398
|
-
hasCustomOptions: options?.hasCustomOptions,
|
|
2399
|
-
onEditOptions: options?.onEditOptions
|
|
2400
|
-
});
|
|
2401
|
-
const [token, setToken] = (0, import_react15.useState)(null);
|
|
2402
|
-
const [loading, setLoading] = (0, import_react15.useState)(false);
|
|
2403
|
-
const [error, setError] = (0, import_react15.useState)(null);
|
|
2404
|
-
const refresh = (0, import_react15.useCallback)(
|
|
2405
|
-
async (overrides) => {
|
|
2406
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
2407
|
-
setLoading(true);
|
|
2408
|
-
setError(null);
|
|
2409
|
-
try {
|
|
2410
|
-
const next = await getGristAccessToken(docApi, { readOnly: overrides?.readOnly ?? readOnlyDefault });
|
|
2411
|
-
setToken(next);
|
|
2412
|
-
return next;
|
|
2413
|
-
} catch (err) {
|
|
2414
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2415
|
-
setError(message);
|
|
2416
|
-
throw err;
|
|
2417
|
-
} finally {
|
|
2418
|
-
setLoading(false);
|
|
2419
|
-
}
|
|
2420
|
-
},
|
|
2421
|
-
[docApi, readOnlyDefault]
|
|
2422
|
-
);
|
|
2423
|
-
const fetchFn = (0, import_react15.useCallback)(
|
|
2424
|
-
(path, init) => {
|
|
2425
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
2426
|
-
return fetchWithGristAuth(docApi, path, { readOnly: readOnlyDefault, ...init });
|
|
2427
|
-
},
|
|
2428
|
-
[docApi, readOnlyDefault]
|
|
2429
|
-
);
|
|
2430
|
-
(0, import_react15.useEffect)(() => {
|
|
2431
|
-
if (docApi && prefetch) void refresh();
|
|
2432
|
-
}, [docApi, prefetch, refresh]);
|
|
2433
|
-
return {
|
|
2434
|
-
token,
|
|
2435
|
-
loading,
|
|
2436
|
-
error: readyError ?? error,
|
|
2437
|
-
refresh,
|
|
2438
|
-
fetch: fetchFn
|
|
2439
|
-
};
|
|
2440
|
-
}
|
|
2441
|
-
|
|
2442
|
-
// src/sdk/mid-level-hooks/use-grist-section.ts
|
|
2443
|
-
var import_react16 = require("react");
|
|
2444
|
-
function useGristSection(options) {
|
|
2445
|
-
const { docApi, error: readyError } = useGristReady({
|
|
2446
|
-
requiredAccess: options?.requiredAccess ?? "read table",
|
|
2447
|
-
columns: options?.columns,
|
|
2448
|
-
allowSelectBy: options?.allowSelectBy,
|
|
2449
|
-
hasCustomOptions: options?.hasCustomOptions,
|
|
2450
|
-
onEditOptions: options?.onEditOptions
|
|
2451
|
-
});
|
|
2452
|
-
const [mappings, setMappings] = (0, import_react16.useState)(null);
|
|
2453
|
-
const [loading, setLoading] = (0, import_react16.useState)(false);
|
|
2454
|
-
const [error, setError] = (0, import_react16.useState)(null);
|
|
2455
|
-
const refresh = (0, import_react16.useCallback)(async () => {
|
|
2456
|
-
if (!docApi) return;
|
|
2457
|
-
if (!grist.sectionApi?.mappings) {
|
|
2458
|
-
setError("grist.sectionApi.mappings is not available in this Grist version");
|
|
2459
|
-
return;
|
|
2460
|
-
}
|
|
2461
|
-
setLoading(true);
|
|
2462
|
-
setError(null);
|
|
2463
|
-
try {
|
|
2464
|
-
const next = await grist.sectionApi.mappings();
|
|
2465
|
-
setMappings(next);
|
|
2466
|
-
} catch (err) {
|
|
2467
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
2468
|
-
} finally {
|
|
2469
|
-
setLoading(false);
|
|
2470
|
-
}
|
|
2471
|
-
}, [docApi]);
|
|
2472
|
-
const configure = (0, import_react16.useCallback)(async (next) => {
|
|
2473
|
-
if (!grist.sectionApi?.configure) {
|
|
2474
|
-
throw new Error("grist.sectionApi.configure is not available in this Grist version");
|
|
2475
|
-
}
|
|
2476
|
-
await grist.sectionApi.configure(next);
|
|
2477
|
-
}, []);
|
|
2478
|
-
(0, import_react16.useEffect)(() => {
|
|
2479
|
-
if (docApi) void refresh();
|
|
2480
|
-
}, [docApi, refresh]);
|
|
2481
|
-
return {
|
|
2482
|
-
mappings,
|
|
2483
|
-
loading,
|
|
2484
|
-
error: readyError ?? error,
|
|
2485
|
-
refresh,
|
|
2486
|
-
configure
|
|
2487
|
-
};
|
|
2488
|
-
}
|
|
2489
|
-
|
|
2490
|
-
// src/sdk/mid-level-hooks/use-grist-widget-options.ts
|
|
2491
|
-
var import_react17 = require("react");
|
|
2492
|
-
function mergeWithDefaults(defaults, stored, namespace) {
|
|
2493
|
-
if (stored === null || stored === void 0) return { ...defaults };
|
|
2494
|
-
const source = namespace ? stored[namespace] : stored;
|
|
2495
|
-
if (!source || typeof source !== "object") return { ...defaults };
|
|
2496
|
-
return { ...defaults, ...source };
|
|
2497
|
-
}
|
|
2498
|
-
function useGristWidgetOptions(config) {
|
|
2499
|
-
const requiredAccess = config.requiredAccess ?? "read table";
|
|
2500
|
-
const debounceMs = config.debounceMs ?? 250;
|
|
2501
|
-
const hasCustomOptions = config.hasCustomOptions ?? true;
|
|
2502
|
-
const { docApi, error: readyError } = useGristReady({
|
|
2503
|
-
requiredAccess,
|
|
2504
|
-
columns: config.columns,
|
|
2505
|
-
allowSelectBy: config.allowSelectBy,
|
|
2506
|
-
hasCustomOptions,
|
|
2507
|
-
onEditOptions: config.onEditOptions
|
|
2508
|
-
});
|
|
2509
|
-
const defaultsRef = (0, import_react17.useRef)(config.defaults);
|
|
2510
|
-
(0, import_react17.useEffect)(() => {
|
|
2511
|
-
defaultsRef.current = config.defaults;
|
|
2512
|
-
}, [config.defaults]);
|
|
2513
|
-
const [options, setOptionsState] = (0, import_react17.useState)(() => ({ ...config.defaults }));
|
|
2514
|
-
const [loading, setLoading] = (0, import_react17.useState)(true);
|
|
2515
|
-
const [error, setError] = (0, import_react17.useState)(null);
|
|
2516
|
-
const lastWrittenRef = (0, import_react17.useRef)(null);
|
|
2517
|
-
const debounceRef = (0, import_react17.useRef)(null);
|
|
2518
|
-
(0, import_react17.useEffect)(() => {
|
|
2519
|
-
if (!docApi) return;
|
|
2520
|
-
return optionsEvent.subscribe(({ options: stored }) => {
|
|
2521
|
-
const merged = mergeWithDefaults(defaultsRef.current, stored, config.namespace);
|
|
2522
|
-
lastWrittenRef.current = merged;
|
|
2523
|
-
setOptionsState(merged);
|
|
2524
|
-
setLoading(false);
|
|
2525
|
-
});
|
|
2526
|
-
}, [docApi, config.namespace]);
|
|
2527
|
-
const flushWrite = (0, import_react17.useCallback)(
|
|
2528
|
-
async (value) => {
|
|
2529
|
-
try {
|
|
2530
|
-
if (config.namespace) {
|
|
2531
|
-
await grist.setOption(config.namespace, value);
|
|
2532
|
-
} else {
|
|
2533
|
-
await grist.setOptions({ ...value });
|
|
2534
|
-
}
|
|
2535
|
-
lastWrittenRef.current = value;
|
|
2536
|
-
} catch (err) {
|
|
2537
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
2538
|
-
}
|
|
2539
|
-
},
|
|
2540
|
-
[config.namespace]
|
|
2541
|
-
);
|
|
2542
|
-
const scheduleWrite = (0, import_react17.useCallback)(
|
|
2543
|
-
(value) => {
|
|
2544
|
-
if (debounceRef.current) window.clearTimeout(debounceRef.current);
|
|
2545
|
-
debounceRef.current = window.setTimeout(() => {
|
|
2546
|
-
debounceRef.current = null;
|
|
2547
|
-
void flushWrite(value);
|
|
2548
|
-
}, debounceMs);
|
|
2549
|
-
},
|
|
2550
|
-
[debounceMs, flushWrite]
|
|
2551
|
-
);
|
|
2552
|
-
(0, import_react17.useEffect)(() => {
|
|
2553
|
-
return () => {
|
|
2554
|
-
if (debounceRef.current) window.clearTimeout(debounceRef.current);
|
|
2555
|
-
};
|
|
2556
|
-
}, []);
|
|
2557
|
-
const setOptionsApi = (0, import_react17.useCallback)(
|
|
2558
|
-
(next) => {
|
|
2559
|
-
setOptionsState(next);
|
|
2560
|
-
scheduleWrite(next);
|
|
2561
|
-
},
|
|
2562
|
-
[scheduleWrite]
|
|
2563
|
-
);
|
|
2564
|
-
const patchOptionsApi = (0, import_react17.useCallback)(
|
|
2565
|
-
(patch) => {
|
|
2566
|
-
setOptionsState((prev) => {
|
|
2567
|
-
const next = { ...prev, ...patch };
|
|
2568
|
-
scheduleWrite(next);
|
|
2569
|
-
return next;
|
|
2570
|
-
});
|
|
2571
|
-
},
|
|
2572
|
-
[scheduleWrite]
|
|
2573
|
-
);
|
|
2574
|
-
const reset = (0, import_react17.useCallback)(async () => {
|
|
2575
|
-
if (debounceRef.current) {
|
|
2576
|
-
window.clearTimeout(debounceRef.current);
|
|
2577
|
-
debounceRef.current = null;
|
|
2578
|
-
}
|
|
2579
|
-
const next = { ...defaultsRef.current };
|
|
2580
|
-
setOptionsState(next);
|
|
2581
|
-
await flushWrite(next);
|
|
2582
|
-
}, [flushWrite]);
|
|
2583
|
-
return (0, import_react17.useMemo)(
|
|
2584
|
-
() => ({
|
|
2585
|
-
options,
|
|
2586
|
-
loading,
|
|
2587
|
-
error: readyError ?? error,
|
|
2588
|
-
setOptions: setOptionsApi,
|
|
2589
|
-
patchOptions: patchOptionsApi,
|
|
2590
|
-
reset
|
|
2591
|
-
}),
|
|
2592
|
-
[options, loading, readyError, error, setOptionsApi, patchOptionsApi, reset]
|
|
2593
|
-
);
|
|
2594
|
-
}
|
|
2595
|
-
|
|
2596
|
-
// src/sdk/mid-level-hooks/use-grist-widget-options-from-context.ts
|
|
2597
|
-
var import_react18 = require("react");
|
|
2598
|
-
function mergeWithDefaults2(defaults, stored, namespace) {
|
|
2599
|
-
if (stored === null || stored === void 0) return { ...defaults };
|
|
2600
|
-
const source = namespace ? stored[namespace] : stored;
|
|
2601
|
-
if (!source || typeof source !== "object") return { ...defaults };
|
|
2602
|
-
return { ...defaults, ...source };
|
|
2603
|
-
}
|
|
2604
|
-
function useGristWidgetOptionsFromContext(config) {
|
|
2605
|
-
const debounceMs = config.debounceMs ?? 250;
|
|
2606
|
-
const selection = useGristSelectionContext();
|
|
2607
|
-
const optionsApi = useGristWidgetOptionsApiContext();
|
|
2608
|
-
const defaultsRef = (0, import_react18.useRef)(config.defaults);
|
|
2609
|
-
(0, import_react18.useEffect)(() => {
|
|
2610
|
-
defaultsRef.current = config.defaults;
|
|
2611
|
-
}, [config.defaults]);
|
|
2612
|
-
const rawOptions = selection.widgetOptions;
|
|
2613
|
-
const hasReceived = rawOptions !== null;
|
|
2614
|
-
const merged = (0, import_react18.useMemo)(
|
|
2615
|
-
() => mergeWithDefaults2(defaultsRef.current, rawOptions, config.namespace),
|
|
2616
|
-
[rawOptions, config.namespace]
|
|
2617
|
-
);
|
|
2618
|
-
const [localOverride, setLocalOverride] = (0, import_react18.useState)(null);
|
|
2619
|
-
const options = localOverride ?? merged;
|
|
2620
|
-
(0, import_react18.useEffect)(() => {
|
|
2621
|
-
setLocalOverride(null);
|
|
2622
|
-
}, [rawOptions]);
|
|
2623
|
-
const debounceRef = (0, import_react18.useRef)(null);
|
|
2624
|
-
const flushWrite = (0, import_react18.useCallback)(
|
|
2625
|
-
async (value) => {
|
|
2626
|
-
if (config.namespace) {
|
|
2627
|
-
await optionsApi.setWidgetOption(config.namespace, value);
|
|
2628
|
-
} else {
|
|
2629
|
-
await optionsApi.setWidgetOptions({ ...value });
|
|
2630
|
-
}
|
|
2631
|
-
},
|
|
2632
|
-
[config.namespace, optionsApi]
|
|
2633
|
-
);
|
|
2634
|
-
const scheduleWrite = (0, import_react18.useCallback)(
|
|
2635
|
-
(value) => {
|
|
2636
|
-
if (debounceRef.current) window.clearTimeout(debounceRef.current);
|
|
2637
|
-
debounceRef.current = window.setTimeout(() => {
|
|
2638
|
-
debounceRef.current = null;
|
|
2639
|
-
void flushWrite(value);
|
|
2640
|
-
}, debounceMs);
|
|
2641
|
-
},
|
|
2642
|
-
[debounceMs, flushWrite]
|
|
2643
|
-
);
|
|
2644
|
-
(0, import_react18.useEffect)(() => {
|
|
2645
|
-
return () => {
|
|
2646
|
-
if (debounceRef.current) window.clearTimeout(debounceRef.current);
|
|
2647
|
-
};
|
|
2648
|
-
}, []);
|
|
2649
|
-
const setOptions = (0, import_react18.useCallback)(
|
|
2650
|
-
(next) => {
|
|
2651
|
-
setLocalOverride(next);
|
|
2652
|
-
scheduleWrite(next);
|
|
2653
|
-
},
|
|
2654
|
-
[scheduleWrite]
|
|
2655
|
-
);
|
|
2656
|
-
const patchOptions = (0, import_react18.useCallback)(
|
|
2657
|
-
(patch) => {
|
|
2658
|
-
setLocalOverride((prev) => {
|
|
2659
|
-
const base = prev ?? merged;
|
|
2660
|
-
const next = { ...base, ...patch };
|
|
2661
|
-
scheduleWrite(next);
|
|
2662
|
-
return next;
|
|
2663
|
-
});
|
|
2664
|
-
},
|
|
2665
|
-
[merged, scheduleWrite]
|
|
2666
|
-
);
|
|
2667
|
-
const reset = (0, import_react18.useCallback)(async () => {
|
|
2668
|
-
if (debounceRef.current) {
|
|
2669
|
-
window.clearTimeout(debounceRef.current);
|
|
2670
|
-
debounceRef.current = null;
|
|
2671
|
-
}
|
|
2672
|
-
const next = { ...defaultsRef.current };
|
|
2673
|
-
setLocalOverride(null);
|
|
2674
|
-
await flushWrite(next);
|
|
2675
|
-
}, [flushWrite]);
|
|
2676
|
-
return (0, import_react18.useMemo)(
|
|
2677
|
-
() => ({
|
|
2678
|
-
options,
|
|
2679
|
-
loading: !hasReceived,
|
|
2680
|
-
setOptions,
|
|
2681
|
-
patchOptions,
|
|
2682
|
-
reset
|
|
2683
|
-
}),
|
|
2684
|
-
[options, hasReceived, setOptions, patchOptions, reset]
|
|
2685
|
-
);
|
|
2686
|
-
}
|
|
2687
|
-
|
|
2688
|
-
// src/sdk/use-grist-table.ts
|
|
2689
|
-
var import_react19 = require("react");
|
|
2690
|
-
function isCurrentMode(option) {
|
|
2691
|
-
return option === void 0 || option === "current";
|
|
2692
|
-
}
|
|
2693
|
-
function useGristTable(options) {
|
|
2694
|
-
const requiredAccess = options?.requiredAccess ?? "read table";
|
|
2695
|
-
const tableIdOption = options?.tableId ?? "current";
|
|
2696
|
-
const { docApi, error: readyError } = useGristReady({
|
|
2697
|
-
requiredAccess,
|
|
2698
|
-
columns: options?.columns,
|
|
2699
|
-
allowSelectBy: options?.allowSelectBy,
|
|
2700
|
-
hasCustomOptions: options?.hasCustomOptions,
|
|
2701
|
-
onEditOptions: options?.onEditOptions
|
|
2702
|
-
});
|
|
2703
|
-
const [tableId, setTableId] = (0, import_react19.useState)(null);
|
|
2704
|
-
const [loading, setLoading] = (0, import_react19.useState)(false);
|
|
2705
|
-
const [error, setError] = (0, import_react19.useState)(null);
|
|
2706
|
-
const fetchTable = (0, import_react19.useCallback)(
|
|
2707
|
-
async (targetTableId) => {
|
|
2708
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
2709
|
-
return docApi.fetchTable(targetTableId);
|
|
2710
|
-
},
|
|
2711
|
-
[docApi]
|
|
2712
|
-
);
|
|
2713
|
-
const listTables = (0, import_react19.useCallback)(async () => {
|
|
2714
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
2715
|
-
const out = await Promise.resolve(docApi.listTables());
|
|
2716
|
-
return [...out];
|
|
2717
|
-
}, [docApi]);
|
|
2718
|
-
const getDocName = (0, import_react19.useCallback)(async () => {
|
|
2719
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
2720
|
-
return String(await Promise.resolve(docApi.getDocName()));
|
|
2721
|
-
}, [docApi]);
|
|
2722
|
-
const fetchSelectedTable = (0, import_react19.useCallback)(
|
|
2723
|
-
async (fetchOptions) => {
|
|
2724
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
2725
|
-
const impl = docApi.fetchSelectedTable ?? grist.fetchSelectedTable;
|
|
2726
|
-
if (!impl) throw new Error("fetchSelectedTable is not available in this Grist version");
|
|
2727
|
-
const merged = { keepEncoded: false, format: "rows", ...fetchOptions };
|
|
2728
|
-
return await impl(merged);
|
|
2729
|
-
},
|
|
2730
|
-
[docApi]
|
|
2731
|
-
);
|
|
2732
|
-
const fetchSelectedRecord = (0, import_react19.useCallback)(
|
|
2733
|
-
async (rowId, fetchOptions) => {
|
|
2734
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
2735
|
-
const impl = docApi.fetchSelectedRecord ?? grist.fetchSelectedRecord;
|
|
2736
|
-
if (!impl) throw new Error("fetchSelectedRecord is not available in this Grist version");
|
|
2737
|
-
const merged = { keepEncoded: false, format: "rows", ...fetchOptions };
|
|
2738
|
-
return await impl(rowId, merged);
|
|
2739
|
-
},
|
|
2740
|
-
[docApi]
|
|
2741
|
-
);
|
|
2742
|
-
const refresh = (0, import_react19.useCallback)(async () => {
|
|
2743
|
-
if (!docApi) return;
|
|
2744
|
-
if (!isCurrentMode(tableIdOption)) {
|
|
2745
|
-
setTableId(tableIdOption);
|
|
2746
|
-
setError(null);
|
|
2747
|
-
return;
|
|
2748
|
-
}
|
|
2749
|
-
setLoading(true);
|
|
2750
|
-
setError(null);
|
|
2751
|
-
try {
|
|
2752
|
-
const next = await grist.selectedTable?.getTableId();
|
|
2753
|
-
setTableId(next ?? null);
|
|
2754
|
-
} catch (err) {
|
|
2755
|
-
setTableId(null);
|
|
2756
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
2757
|
-
} finally {
|
|
2758
|
-
setLoading(false);
|
|
2759
|
-
}
|
|
2760
|
-
}, [docApi, tableIdOption]);
|
|
2761
|
-
(0, import_react19.useEffect)(() => {
|
|
2762
|
-
if (docApi) void refresh();
|
|
2763
|
-
}, [docApi, refresh]);
|
|
2764
|
-
return {
|
|
2765
|
-
tableId,
|
|
2766
|
-
loading,
|
|
2767
|
-
error: readyError ?? error,
|
|
2768
|
-
refresh,
|
|
2769
|
-
fetchTable,
|
|
2770
|
-
listTables,
|
|
2771
|
-
getDocName,
|
|
2772
|
-
fetchSelectedTable,
|
|
2773
|
-
fetchSelectedRecord
|
|
2774
|
-
};
|
|
2775
|
-
}
|
|
2776
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
2777
|
-
0 && (module.exports = {
|
|
2778
|
-
GristHandshakeProvider,
|
|
2779
|
-
useAmbientGristHandshake,
|
|
2780
|
-
useGristAccessToken,
|
|
2781
|
-
useGristActions,
|
|
2782
|
-
useGristAvailability,
|
|
2783
|
-
useGristCapabilities,
|
|
2784
|
-
useGristHandshake,
|
|
2785
|
-
useGristHandshakeContext,
|
|
2786
|
-
useGristHandshakeContextOptional,
|
|
2787
|
-
useGristMappedRecord,
|
|
2788
|
-
useGristNewRecord,
|
|
2789
|
-
useGristReady,
|
|
2790
|
-
useGristRecord,
|
|
2791
|
-
useGristRecords,
|
|
2792
|
-
useGristRowsFromTable,
|
|
2793
|
-
useGristSection,
|
|
2794
|
-
useGristTable,
|
|
2795
|
-
useGristTableOps,
|
|
2796
|
-
useGristWidgetOptions,
|
|
2797
|
-
useGristWidgetOptionsFromContext
|
|
2798
|
-
});
|
|
2799
|
-
//# sourceMappingURL=advanced.cjs.map
|