grist-widget-sdk 0.3.27 → 0.5.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 +84 -0
- package/README.md +79 -208
- package/dist/app.cjs +1336 -4415
- package/dist/app.cjs.map +1 -1
- package/dist/app.d.cts +6 -103
- package/dist/app.d.ts +6 -103
- package/dist/app.js +5 -7
- package/dist/chunk-4ZGVKGDF.js +204 -0
- package/dist/chunk-4ZGVKGDF.js.map +1 -0
- package/dist/{chunk-6Y2IONGJ.js → chunk-LSALXIGM.js} +191 -20
- package/dist/chunk-LSALXIGM.js.map +1 -0
- package/dist/chunk-MAPAWVQC.js +1898 -0
- package/dist/chunk-MAPAWVQC.js.map +1 -0
- package/dist/{chunk-YQNMIL33.js → chunk-OXA2VITC.js} +532 -181
- package/dist/chunk-OXA2VITC.js.map +1 -0
- package/dist/define-widget-B1FIvI-h.d.cts +433 -0
- package/dist/define-widget-B9gEtJP0.d.ts +433 -0
- package/dist/emulator/testing.cjs +3341 -6512
- package/dist/emulator/testing.cjs.map +1 -1
- package/dist/emulator/testing.d.cts +29 -14
- package/dist/emulator/testing.d.ts +29 -14
- package/dist/emulator/testing.js +78 -72
- package/dist/emulator/testing.js.map +1 -1
- package/dist/emulator.cjs +63 -249
- package/dist/emulator.cjs.map +1 -1
- package/dist/emulator.d.cts +6 -8
- package/dist/emulator.d.ts +6 -8
- package/dist/emulator.js +5 -11
- package/dist/index.cjs +1442 -4862
- 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 +12 -158
- package/dist/mount-grist-widget-CkB-3iG_.d.ts +137 -0
- package/dist/mount-grist-widget-Dtelqao_.d.cts +137 -0
- 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/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
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
normalizeReplicaDocumentInput
|
|
3
|
-
} from "./chunk-GTTD5HPO.js";
|
|
4
|
-
import {
|
|
5
|
-
resolveMappings
|
|
6
|
-
} from "./chunk-PM3B4WYV.js";
|
|
7
1
|
import {
|
|
8
2
|
coerceGristDocApiRefRowId,
|
|
9
3
|
gristAddRecordAction,
|
|
@@ -12,172 +6,75 @@ import {
|
|
|
12
6
|
gristBulkUpdateRecordAction,
|
|
13
7
|
gristRemoveRecordAction,
|
|
14
8
|
gristUpdateRecordAction,
|
|
15
|
-
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
|
|
18
|
-
// src/emulator/transports/iframe.ts
|
|
19
|
-
import { MsgType, Rpc } from "grain-rpc";
|
|
9
|
+
normalizeReplicaDocumentInput
|
|
10
|
+
} from "./chunk-4ZGVKGDF.js";
|
|
20
11
|
|
|
21
|
-
// src/emulator/
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
colors: {}
|
|
28
|
-
},
|
|
29
|
-
...options?.fromReady ? { fromReady: true } : {}
|
|
30
|
-
};
|
|
12
|
+
// src/emulator/core/mappings.ts
|
|
13
|
+
function resolveMappings(state) {
|
|
14
|
+
if (state.selection.mappings !== void 0 && state.selection.mappings !== null) {
|
|
15
|
+
return state.selection.mappings;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
31
18
|
}
|
|
32
19
|
|
|
33
|
-
// src/emulator/
|
|
34
|
-
function
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}, warn: () => {
|
|
45
|
-
} }
|
|
46
|
-
});
|
|
47
|
-
rpc.queueOutgoingUntilReadyMessage();
|
|
48
|
-
const receiveFromWidget = rpc.receiveMessage.bind(rpc);
|
|
49
|
-
rpc.receiveMessage = (msg) => {
|
|
50
|
-
if (msg && typeof msg === "object" && msg.mtype === MsgType.RpcCall && msg.mdest === "grist") {
|
|
51
|
-
const copy = { ...msg };
|
|
52
|
-
delete copy.mdest;
|
|
53
|
-
receiveFromWidget(copy);
|
|
54
|
-
return;
|
|
20
|
+
// src/emulator/core/display-helpers.ts
|
|
21
|
+
function inferDisplayHelpersByRef(columns) {
|
|
22
|
+
const out = {};
|
|
23
|
+
const helperColumns = Object.entries(columns).filter(
|
|
24
|
+
([colId]) => colId.startsWith("gristHelper_")
|
|
25
|
+
);
|
|
26
|
+
const usedHelpers = /* @__PURE__ */ new Set();
|
|
27
|
+
const colRefToColId = /* @__PURE__ */ new Map();
|
|
28
|
+
for (const [colId, col] of Object.entries(columns)) {
|
|
29
|
+
if (typeof col?.colRef === "number" && Number.isFinite(col.colRef)) {
|
|
30
|
+
colRefToColId.set(col.colRef, colId);
|
|
55
31
|
}
|
|
56
|
-
receiveFromWidget(msg);
|
|
57
|
-
};
|
|
58
|
-
const onMessage = (event) => {
|
|
59
|
-
if (event.source !== iframe.contentWindow) return;
|
|
60
|
-
if (!isRpcEnvelope(event.data)) return;
|
|
61
|
-
rpc.receiveMessage(event.data);
|
|
62
|
-
};
|
|
63
|
-
window.addEventListener("message", onMessage);
|
|
64
|
-
rpc.registerImpl("GristView", api.GristView);
|
|
65
|
-
rpc.registerImpl("GristDocAPI", api.GristDocAPI);
|
|
66
|
-
rpc.registerImpl("WidgetAPI", api.WidgetAPI);
|
|
67
|
-
rpc.registerImpl("CustomSectionAPI", api.CustomSectionAPI);
|
|
68
|
-
rpc.registerImpl("_grist_api", api.GristAPI);
|
|
69
|
-
rpc.registerImpl("CommandAPI", api.CommandAPI);
|
|
70
|
-
const buildInteraction = options.buildInteraction ?? defaultInteraction(state);
|
|
71
|
-
const cursorOf = options.cursor ?? (() => state.selection.cursor);
|
|
72
|
-
const initialCursorOf = options.initialCursor ?? (() => state.selection.rowId);
|
|
73
|
-
function fireDataChange(tableId, opts) {
|
|
74
|
-
safePostMessage({
|
|
75
|
-
tableId: tableId ?? state.selection.tableId,
|
|
76
|
-
dataChange: true,
|
|
77
|
-
...opts?.mappingsChange ? { mappingsChange: true } : {}
|
|
78
|
-
});
|
|
79
32
|
}
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
tableId: state.selection.tableId,
|
|
85
|
-
rowId: id,
|
|
86
|
-
cursor: cursorOf(),
|
|
87
|
-
...opts?.mappingsChange ? { mappingsChange: true } : {}
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
function fireOptions() {
|
|
91
|
-
safePostMessage({
|
|
92
|
-
options: hasOptions(state.widgetOptions) ? { ...state.widgetOptions } : null,
|
|
93
|
-
settings: { ...buildInteraction() }
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
function fireTheme(message) {
|
|
97
|
-
safePostMessage(message);
|
|
98
|
-
}
|
|
99
|
-
function emitInitialState(opts) {
|
|
100
|
-
fireOptions();
|
|
101
|
-
if (options.initialTheme) {
|
|
102
|
-
fireTheme(buildGristThemeHostMessage(options.initialTheme, { fromReady: true }));
|
|
33
|
+
for (const [colId, col] of Object.entries(columns)) {
|
|
34
|
+
const type = col?.type;
|
|
35
|
+
if (typeof type !== "string" || !type.startsWith("Ref:") && !type.startsWith("RefList:")) {
|
|
36
|
+
continue;
|
|
103
37
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const timer = setTimeout(() => {
|
|
111
|
-
timers.delete(timer);
|
|
112
|
-
emitInitialState(opts);
|
|
113
|
-
}, 0);
|
|
114
|
-
timers.add(timer);
|
|
115
|
-
}
|
|
116
|
-
const offBus = bus.on((event) => {
|
|
117
|
-
switch (event.type) {
|
|
118
|
-
case "ready":
|
|
119
|
-
emitInitialStateWithReplay();
|
|
120
|
-
break;
|
|
121
|
-
case "data-change":
|
|
122
|
-
fireDataChange(event.tableId);
|
|
123
|
-
break;
|
|
124
|
-
case "cursor-change": {
|
|
125
|
-
const rowId = event.rowId === null ? state.selection.rowId : event.rowId;
|
|
126
|
-
if (rowId !== null) {
|
|
127
|
-
safePostMessage({
|
|
128
|
-
tableId: event.tableId,
|
|
129
|
-
rowId,
|
|
130
|
-
cursor: cursorOf()
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
fireDataChange(event.tableId);
|
|
134
|
-
break;
|
|
38
|
+
if (typeof col.displayColRef === "number" && Number.isFinite(col.displayColRef)) {
|
|
39
|
+
const helperColId = colRefToColId.get(col.displayColRef);
|
|
40
|
+
if (helperColId) {
|
|
41
|
+
out[colId] = helperColId;
|
|
42
|
+
usedHelpers.add(helperColId);
|
|
43
|
+
continue;
|
|
135
44
|
}
|
|
136
|
-
case "options-change":
|
|
137
|
-
fireOptions();
|
|
138
|
-
break;
|
|
139
|
-
case "mappings-change":
|
|
140
|
-
emitInitialStateWithReplay({ mappingsChange: true });
|
|
141
|
-
break;
|
|
142
|
-
case "theme-change":
|
|
143
|
-
fireTheme(event.message);
|
|
144
|
-
break;
|
|
145
45
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
46
|
+
const match = helperColumns.find(([, helper]) => {
|
|
47
|
+
const formula = helper?.formula;
|
|
48
|
+
return typeof formula === "string" && (formula.includes(`$${colId}.`) || formula.includes(`$${colId}`));
|
|
49
|
+
});
|
|
50
|
+
if (match) {
|
|
51
|
+
out[colId] = match[0];
|
|
52
|
+
usedHelpers.add(match[0]);
|
|
153
53
|
}
|
|
154
54
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return
|
|
178
|
-
access_level: state.configuration?.requiredAccess ?? "read table",
|
|
179
|
-
...state.widgetInteraction
|
|
180
|
-
});
|
|
55
|
+
const unresolvedRefs = Object.entries(columns).filter(([colId, col]) => {
|
|
56
|
+
const type = col?.type;
|
|
57
|
+
return !out[colId] && typeof type === "string" && (type.startsWith("Ref:") || type.startsWith("RefList:"));
|
|
58
|
+
}).sort((a, b) => (a[1]?.parentPos ?? Number.MAX_SAFE_INTEGER) - (b[1]?.parentPos ?? Number.MAX_SAFE_INTEGER));
|
|
59
|
+
const helperByPos = helperColumns.filter(([helperId]) => !usedHelpers.has(helperId)).sort((a, b) => (a[1]?.parentPos ?? Number.MAX_SAFE_INTEGER) - (b[1]?.parentPos ?? Number.MAX_SAFE_INTEGER));
|
|
60
|
+
for (const [refId, refCol] of unresolvedRefs) {
|
|
61
|
+
const refPos = refCol?.parentPos ?? Number.MAX_SAFE_INTEGER;
|
|
62
|
+
const candidate = helperByPos.find(([, helperCol]) => {
|
|
63
|
+
const helperPos = helperCol?.parentPos ?? Number.MAX_SAFE_INTEGER;
|
|
64
|
+
return helperPos > refPos;
|
|
65
|
+
});
|
|
66
|
+
if (!candidate) continue;
|
|
67
|
+
out[refId] = candidate[0];
|
|
68
|
+
usedHelpers.add(candidate[0]);
|
|
69
|
+
}
|
|
70
|
+
for (const [refId] of unresolvedRefs) {
|
|
71
|
+
if (out[refId]) continue;
|
|
72
|
+
const candidate = helperByPos.find(([helperId]) => !usedHelpers.has(helperId));
|
|
73
|
+
if (!candidate) continue;
|
|
74
|
+
out[refId] = candidate[0];
|
|
75
|
+
usedHelpers.add(candidate[0]);
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
181
78
|
}
|
|
182
79
|
|
|
183
80
|
// src/emulator/core/document.ts
|
|
@@ -750,6 +647,7 @@ function normalizeColumnarForTable(table, columns) {
|
|
|
750
647
|
}
|
|
751
648
|
function normalizeCellValueForType(value, type) {
|
|
752
649
|
if (value === null || value === void 0 || typeof type !== "string") return value;
|
|
650
|
+
if (isListColumnType(type) && Array.isArray(value) && value[0] !== "L") return ["E", "KeyError"];
|
|
753
651
|
if (type.startsWith("Ref:")) return normalizeRefValue(value);
|
|
754
652
|
if (type.startsWith("RefList:")) return normalizeRefListValue(value);
|
|
755
653
|
return value;
|
|
@@ -767,6 +665,9 @@ function extractRefRowId(value) {
|
|
|
767
665
|
if (fromTuple !== void 0 && fromTuple > 0) return Math.trunc(fromTuple);
|
|
768
666
|
return toPositiveInt(value);
|
|
769
667
|
}
|
|
668
|
+
function isListColumnType(type) {
|
|
669
|
+
return type === "ChoiceList" || type === "Attachments" || type.startsWith("RefList:");
|
|
670
|
+
}
|
|
770
671
|
function normalizeRefValue(value) {
|
|
771
672
|
return extractRefRowId(value);
|
|
772
673
|
}
|
|
@@ -798,6 +699,310 @@ function toPositiveInt(value) {
|
|
|
798
699
|
return null;
|
|
799
700
|
}
|
|
800
701
|
|
|
702
|
+
// src/emulator/validators.ts
|
|
703
|
+
import { z } from "zod";
|
|
704
|
+
var emulatorRowSchema = z.object({ id: z.number() }).catchall(z.unknown());
|
|
705
|
+
var formulaKindSchema = z.enum(["computed", "trigger", "none", "empty_slot"]);
|
|
706
|
+
var replicaColumnSchema = z.object({
|
|
707
|
+
colRef: z.number().optional(),
|
|
708
|
+
type: z.string().optional(),
|
|
709
|
+
label: z.string().optional(),
|
|
710
|
+
parentPos: z.number().optional(),
|
|
711
|
+
isFormula: z.boolean().optional(),
|
|
712
|
+
formulaKind: formulaKindSchema.optional(),
|
|
713
|
+
formula: z.string().optional(),
|
|
714
|
+
description: z.string().optional(),
|
|
715
|
+
widgetOptions: z.record(z.string(), z.unknown()).optional(),
|
|
716
|
+
displayColRef: z.number().optional(),
|
|
717
|
+
visibleColRef: z.number().optional()
|
|
718
|
+
}).strict();
|
|
719
|
+
var replicaTableSchema = z.object({
|
|
720
|
+
label: z.string().optional(),
|
|
721
|
+
columns: z.record(z.string(), replicaColumnSchema).optional(),
|
|
722
|
+
rows: z.array(emulatorRowSchema).optional(),
|
|
723
|
+
rowCount: z.number().optional(),
|
|
724
|
+
dataOmitted: z.boolean().optional(),
|
|
725
|
+
dataOmittedReason: z.string().optional(),
|
|
726
|
+
error: z.string().optional()
|
|
727
|
+
}).strict();
|
|
728
|
+
var replicaSelectionSchema = z.object({
|
|
729
|
+
tableId: z.string().optional(),
|
|
730
|
+
rowId: z.union([z.number(), z.literal("new"), z.null()]).optional(),
|
|
731
|
+
mappings: z.unknown().nullable().optional(),
|
|
732
|
+
cursor: z.unknown().optional()
|
|
733
|
+
}).strict();
|
|
734
|
+
var replicaWidgetConfigurationSchema = z.object({
|
|
735
|
+
requiredAccess: z.string().optional(),
|
|
736
|
+
columns: z.unknown().optional(),
|
|
737
|
+
allowSelectBy: z.boolean().optional(),
|
|
738
|
+
hasCustomOptions: z.boolean().optional()
|
|
739
|
+
}).strict();
|
|
740
|
+
var replicaAccessTokenSchema = z.object({
|
|
741
|
+
token: z.string(),
|
|
742
|
+
baseUrl: z.string(),
|
|
743
|
+
ttlMsecs: z.number()
|
|
744
|
+
}).strict();
|
|
745
|
+
var replicaDocumentSchema = z.object({
|
|
746
|
+
generatedAt: z.string(),
|
|
747
|
+
source: z.string().optional(),
|
|
748
|
+
docName: z.string().optional(),
|
|
749
|
+
mode: z.enum(["schema-only", "schema+samples", "schema+data"]).optional(),
|
|
750
|
+
tables: z.record(z.string(), replicaTableSchema),
|
|
751
|
+
selection: replicaSelectionSchema.optional(),
|
|
752
|
+
widgetOptions: z.record(z.string(), z.unknown()).optional(),
|
|
753
|
+
widgetInteraction: z.unknown().optional(),
|
|
754
|
+
configuration: replicaWidgetConfigurationSchema.optional(),
|
|
755
|
+
accessToken: replicaAccessTokenSchema.nullable().optional()
|
|
756
|
+
}).strict().refine((value) => Object.keys(value.tables).length > 0, {
|
|
757
|
+
message: "at least one table is required",
|
|
758
|
+
path: ["tables"]
|
|
759
|
+
});
|
|
760
|
+
function formatZodError(prefix, error) {
|
|
761
|
+
const details = error.issues.map((issue) => {
|
|
762
|
+
const where = issue.path.length > 0 ? issue.path.join(".") : "<root>";
|
|
763
|
+
return `${where}: ${issue.message}`;
|
|
764
|
+
}).join("; ");
|
|
765
|
+
return new Error(`${prefix}: ${details}`);
|
|
766
|
+
}
|
|
767
|
+
function safeParseDocument(input) {
|
|
768
|
+
return replicaDocumentSchema.safeParse(input);
|
|
769
|
+
}
|
|
770
|
+
function parseDocument(input) {
|
|
771
|
+
const result = safeParseDocument(input);
|
|
772
|
+
if (!result.success) {
|
|
773
|
+
throw formatZodError("parseDocument", result.error);
|
|
774
|
+
}
|
|
775
|
+
return result.data;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// src/emulator/api/command-api.ts
|
|
779
|
+
function createCommandApiImpl(bus) {
|
|
780
|
+
return {
|
|
781
|
+
async run(name) {
|
|
782
|
+
bus.emit({ type: "user-action", action: ["RunCommand", name] });
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// src/emulator/api/custom-section-api.ts
|
|
788
|
+
function createCustomSectionApiImpl(state, bus) {
|
|
789
|
+
return {
|
|
790
|
+
async configure(settings) {
|
|
791
|
+
const config = {
|
|
792
|
+
requiredAccess: settings.requiredAccess,
|
|
793
|
+
columns: settings.columns,
|
|
794
|
+
allowSelectBy: settings.allowSelectBy,
|
|
795
|
+
hasCustomOptions: settings.hasCustomOptions
|
|
796
|
+
};
|
|
797
|
+
state.configuration = config;
|
|
798
|
+
bus.emit({ type: "ready", configuration: config });
|
|
799
|
+
bus.emit({ type: "mappings-change", mappings: resolveMappings(state) });
|
|
800
|
+
},
|
|
801
|
+
async mappings() {
|
|
802
|
+
return resolveMappings(state);
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// src/emulator/api/grist-api.ts
|
|
808
|
+
function createGristApiImpl() {
|
|
809
|
+
return {
|
|
810
|
+
async ping() {
|
|
811
|
+
return "pong";
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// src/emulator/core/columnar.ts
|
|
817
|
+
function rowsToColumnar(table, rows, options = {}) {
|
|
818
|
+
const cols = Object.keys(table.columns);
|
|
819
|
+
const out = { id: rows.map((r) => r.id) };
|
|
820
|
+
for (const col of cols) {
|
|
821
|
+
out[col] = rows.map((r) => encodeForWire(r[col], table.columns[col]?.type, options.keepEncoded));
|
|
822
|
+
}
|
|
823
|
+
return out;
|
|
824
|
+
}
|
|
825
|
+
function rowToRecord(table, row, options = {}) {
|
|
826
|
+
const out = { id: row.id };
|
|
827
|
+
for (const col of Object.keys(table.columns)) {
|
|
828
|
+
out[col] = encodeForWire(row[col], table.columns[col]?.type, options.keepEncoded);
|
|
829
|
+
}
|
|
830
|
+
return out;
|
|
831
|
+
}
|
|
832
|
+
function encodeForWire(value, type, keepEncoded) {
|
|
833
|
+
if (value instanceof Date) {
|
|
834
|
+
const epoch = Math.floor(value.getTime() / 1e3);
|
|
835
|
+
if (keepEncoded) {
|
|
836
|
+
return type && type.startsWith("DateTime") ? ["D", epoch, "UTC"] : ["d", epoch];
|
|
837
|
+
}
|
|
838
|
+
return epoch;
|
|
839
|
+
}
|
|
840
|
+
return value;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// src/emulator/api/grist-doc-api.ts
|
|
844
|
+
function createGristDocApiImpl(state, bus) {
|
|
845
|
+
return {
|
|
846
|
+
async getDocName() {
|
|
847
|
+
return state.docName;
|
|
848
|
+
},
|
|
849
|
+
async listTables() {
|
|
850
|
+
return [...state.tables.keys()];
|
|
851
|
+
},
|
|
852
|
+
async fetchTable(tableId) {
|
|
853
|
+
if (tableId === "_grist_Tables") {
|
|
854
|
+
return synthesizeTablesMeta(state);
|
|
855
|
+
}
|
|
856
|
+
if (tableId === "_grist_Tables_column") {
|
|
857
|
+
return synthesizeTablesColumnMeta(state);
|
|
858
|
+
}
|
|
859
|
+
const table = getTable(state, tableId);
|
|
860
|
+
return rowsToColumnar(table, table.rows);
|
|
861
|
+
},
|
|
862
|
+
async applyUserActions(actions) {
|
|
863
|
+
const result = applyUserActions(state, bus, actions);
|
|
864
|
+
return { retValues: result.retValues };
|
|
865
|
+
},
|
|
866
|
+
async getAccessToken() {
|
|
867
|
+
if (!state.accessToken) {
|
|
868
|
+
const err = new Error("getAccessToken not available (no token configured on emulator)");
|
|
869
|
+
throw err;
|
|
870
|
+
}
|
|
871
|
+
return { ...state.accessToken };
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// src/emulator/api/grist-view.ts
|
|
877
|
+
function createGristViewImpl(state, bus) {
|
|
878
|
+
return {
|
|
879
|
+
async fetchSelectedTable(options) {
|
|
880
|
+
const table = getSelectedTable(state);
|
|
881
|
+
return rowsToColumnar(table, table.rows, { keepEncoded: options?.keepEncoded });
|
|
882
|
+
},
|
|
883
|
+
async fetchSelectedRecord(rowId, options) {
|
|
884
|
+
const table = getSelectedTable(state);
|
|
885
|
+
const row = findRow(table, rowId);
|
|
886
|
+
if (!row) throw new Error(`No row ${rowId} in ${table.tableId}`);
|
|
887
|
+
return rowToRecord(table, row, { keepEncoded: options?.keepEncoded });
|
|
888
|
+
},
|
|
889
|
+
async allowSelectBy() {
|
|
890
|
+
return state.configuration?.allowSelectBy ?? false;
|
|
891
|
+
},
|
|
892
|
+
async setSelectedRows(rowIds) {
|
|
893
|
+
if (!rowIds || rowIds.length === 0) {
|
|
894
|
+
state.selection.rowId = null;
|
|
895
|
+
} else {
|
|
896
|
+
state.selection.rowId = rowIds[0];
|
|
897
|
+
}
|
|
898
|
+
bus.emit({
|
|
899
|
+
type: "cursor-change",
|
|
900
|
+
tableId: state.selection.tableId,
|
|
901
|
+
rowId: state.selection.rowId
|
|
902
|
+
});
|
|
903
|
+
},
|
|
904
|
+
async setCursorPos(cursor) {
|
|
905
|
+
const tableId = state.selection.tableId;
|
|
906
|
+
if (typeof cursor.rowId === "number") state.selection.rowId = cursor.rowId;
|
|
907
|
+
state.selection.cursor = { ...state.selection.cursor, ...cursor };
|
|
908
|
+
bus.emit({
|
|
909
|
+
type: "cursor-change",
|
|
910
|
+
tableId,
|
|
911
|
+
rowId: state.selection.rowId,
|
|
912
|
+
cursor: state.selection.cursor
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// src/emulator/api/widget-api.ts
|
|
919
|
+
function createWidgetApiImpl(state, bus) {
|
|
920
|
+
function emit() {
|
|
921
|
+
bus.emit({
|
|
922
|
+
type: "options-change",
|
|
923
|
+
options: { ...state.widgetOptions },
|
|
924
|
+
interaction: { ...state.widgetInteraction }
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
return {
|
|
928
|
+
async getOption(key) {
|
|
929
|
+
return state.widgetOptions[key];
|
|
930
|
+
},
|
|
931
|
+
async setOption(key, value) {
|
|
932
|
+
state.widgetOptions[key] = value;
|
|
933
|
+
emit();
|
|
934
|
+
},
|
|
935
|
+
async getOptions() {
|
|
936
|
+
return Object.keys(state.widgetOptions).length > 0 ? { ...state.widgetOptions } : null;
|
|
937
|
+
},
|
|
938
|
+
// Replaces every option, as Grist's WidgetAPI.setOptions does.
|
|
939
|
+
async setOptions(options) {
|
|
940
|
+
state.widgetOptions = { ...options };
|
|
941
|
+
emit();
|
|
942
|
+
},
|
|
943
|
+
async clearOptions() {
|
|
944
|
+
state.widgetOptions = {};
|
|
945
|
+
emit();
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// src/emulator/api/index.ts
|
|
951
|
+
function createApiBundle(state, bus) {
|
|
952
|
+
return {
|
|
953
|
+
GristView: createGristViewImpl(state, bus),
|
|
954
|
+
GristDocAPI: createGristDocApiImpl(state, bus),
|
|
955
|
+
WidgetAPI: createWidgetApiImpl(state, bus),
|
|
956
|
+
CustomSectionAPI: createCustomSectionApiImpl(state, bus),
|
|
957
|
+
GristAPI: createGristApiImpl(),
|
|
958
|
+
CommandAPI: createCommandApiImpl(bus)
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// src/emulator/core/event-bus.ts
|
|
963
|
+
function createEventBus(options = {}) {
|
|
964
|
+
const limit = options.historyLimit ?? 200;
|
|
965
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
966
|
+
const history = [];
|
|
967
|
+
return {
|
|
968
|
+
emit(event) {
|
|
969
|
+
history.push(event);
|
|
970
|
+
if (history.length > limit) history.shift();
|
|
971
|
+
for (const fn of [...listeners]) {
|
|
972
|
+
try {
|
|
973
|
+
fn(event);
|
|
974
|
+
} catch (err) {
|
|
975
|
+
console.error("[grist-emulator] event listener threw:", err);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
},
|
|
979
|
+
on(listener) {
|
|
980
|
+
listeners.add(listener);
|
|
981
|
+
return () => {
|
|
982
|
+
listeners.delete(listener);
|
|
983
|
+
};
|
|
984
|
+
},
|
|
985
|
+
history() {
|
|
986
|
+
return history;
|
|
987
|
+
},
|
|
988
|
+
clear() {
|
|
989
|
+
history.length = 0;
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// src/emulator/theme-message.ts
|
|
995
|
+
function buildGristThemeHostMessage(appearance, options) {
|
|
996
|
+
return {
|
|
997
|
+
theme: {
|
|
998
|
+
appearance,
|
|
999
|
+
name: appearance,
|
|
1000
|
+
colors: {}
|
|
1001
|
+
},
|
|
1002
|
+
...options?.fromReady ? { fromReady: true } : {}
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
|
|
801
1006
|
// src/emulator/transports/inline.ts
|
|
802
1007
|
function createInlineTransport(options) {
|
|
803
1008
|
const { state, bus, api } = options;
|
|
@@ -827,14 +1032,6 @@ function createInlineTransport(options) {
|
|
|
827
1032
|
logErr(err);
|
|
828
1033
|
}
|
|
829
1034
|
}
|
|
830
|
-
lastRecord = { record: null, mappings };
|
|
831
|
-
for (const [cb] of recordCbs) {
|
|
832
|
-
try {
|
|
833
|
-
cb(null, mappings);
|
|
834
|
-
} catch (err) {
|
|
835
|
-
logErr(err);
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
1035
|
return;
|
|
839
1036
|
}
|
|
840
1037
|
if (rowId === null) {
|
|
@@ -1172,18 +1369,172 @@ function installFakeIframeContext(target) {
|
|
|
1172
1369
|
}
|
|
1173
1370
|
}
|
|
1174
1371
|
|
|
1372
|
+
// src/emulator/create-grist-emulator.ts
|
|
1373
|
+
function createGristEmulator(options) {
|
|
1374
|
+
const validatedDocument = parseDocument(options.document);
|
|
1375
|
+
const state = createDocument({
|
|
1376
|
+
...validatedDocument,
|
|
1377
|
+
widgetOptions: options.widgetOptions ?? validatedDocument.widgetOptions,
|
|
1378
|
+
// No access_level by default: the emulator then grants the access the
|
|
1379
|
+
// widget asks for in grist.ready(), as a user accepting it in Grist would.
|
|
1380
|
+
widgetInteraction: options.widgetInteraction ?? validatedDocument.widgetInteraction ?? {}
|
|
1381
|
+
});
|
|
1382
|
+
const bus = createEventBus();
|
|
1383
|
+
const api = createApiBundle(state, bus);
|
|
1384
|
+
const inline = createInlineTransport({
|
|
1385
|
+
state,
|
|
1386
|
+
bus,
|
|
1387
|
+
api,
|
|
1388
|
+
initialTheme: options.initialTheme,
|
|
1389
|
+
target: options.transport?.target
|
|
1390
|
+
});
|
|
1391
|
+
const transport = inline;
|
|
1392
|
+
const grist = inline.grist;
|
|
1393
|
+
function apply(actions) {
|
|
1394
|
+
return applyUserActions(state, bus, actions);
|
|
1395
|
+
}
|
|
1396
|
+
return {
|
|
1397
|
+
state,
|
|
1398
|
+
bus,
|
|
1399
|
+
api,
|
|
1400
|
+
transport,
|
|
1401
|
+
grist,
|
|
1402
|
+
apply,
|
|
1403
|
+
mutate: {
|
|
1404
|
+
addRow: (tableId, fields) => apply([["AddRecord", tableId, null, fields]]).retValues[0],
|
|
1405
|
+
updateRow: (tableId, rowId, fields) => {
|
|
1406
|
+
apply([["UpdateRecord", tableId, rowId, fields]]);
|
|
1407
|
+
},
|
|
1408
|
+
removeRow: (tableId, rowId) => {
|
|
1409
|
+
apply([["RemoveRecord", tableId, rowId]]);
|
|
1410
|
+
}
|
|
1411
|
+
},
|
|
1412
|
+
selectRow(rowId) {
|
|
1413
|
+
state.selection.rowId = rowId;
|
|
1414
|
+
bus.emit({ type: "cursor-change", tableId: state.selection.tableId, rowId });
|
|
1415
|
+
},
|
|
1416
|
+
selectTable(tableId) {
|
|
1417
|
+
if (!state.tables.has(tableId)) throw new Error(`Unknown table: ${tableId}`);
|
|
1418
|
+
state.selection.tableId = tableId;
|
|
1419
|
+
const first = state.tables.get(tableId).rows[0]?.id ?? null;
|
|
1420
|
+
state.selection.rowId = first;
|
|
1421
|
+
bus.emit({ type: "data-change", tableId });
|
|
1422
|
+
bus.emit({ type: "cursor-change", tableId, rowId: first });
|
|
1423
|
+
},
|
|
1424
|
+
setColumnMappings(mappings) {
|
|
1425
|
+
if (mappings === void 0) {
|
|
1426
|
+
delete state.selection.mappings;
|
|
1427
|
+
} else {
|
|
1428
|
+
state.selection.mappings = mappings;
|
|
1429
|
+
}
|
|
1430
|
+
bus.emit({ type: "mappings-change", mappings: resolveMappings(state) });
|
|
1431
|
+
},
|
|
1432
|
+
async setOptions(opts) {
|
|
1433
|
+
await api.WidgetAPI.setOptions(opts);
|
|
1434
|
+
},
|
|
1435
|
+
setInteraction(interaction) {
|
|
1436
|
+
state.widgetInteraction = { ...interaction };
|
|
1437
|
+
bus.emit({ type: "options-change", options: { ...state.widgetOptions }, interaction: { ...interaction } });
|
|
1438
|
+
},
|
|
1439
|
+
theme: {
|
|
1440
|
+
set(next) {
|
|
1441
|
+
bus.emit({ type: "theme-change", message: buildGristThemeHostMessage(next) });
|
|
1442
|
+
}
|
|
1443
|
+
},
|
|
1444
|
+
dispose() {
|
|
1445
|
+
transport.dispose();
|
|
1446
|
+
bus.clear();
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// src/emulator/templates.ts
|
|
1452
|
+
var PRESET_GENERATED_AT = "1970-01-01T00:00:00.000Z";
|
|
1453
|
+
function replicaBase(overrides) {
|
|
1454
|
+
return {
|
|
1455
|
+
generatedAt: PRESET_GENERATED_AT,
|
|
1456
|
+
...overrides
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
var presets = {
|
|
1460
|
+
/**
|
|
1461
|
+
* Default starter fixture for demos / sandboxes that need a neutral document
|
|
1462
|
+
* without choosing a domain-specific preset.
|
|
1463
|
+
*/
|
|
1464
|
+
defaultTemplate() {
|
|
1465
|
+
return replicaBase({
|
|
1466
|
+
docName: "Default",
|
|
1467
|
+
tables: {
|
|
1468
|
+
Table1: {
|
|
1469
|
+
label: "Table1",
|
|
1470
|
+
rows: [
|
|
1471
|
+
{ id: 1, Name: "Alice" },
|
|
1472
|
+
{ id: 2, Name: "Bob" }
|
|
1473
|
+
]
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
});
|
|
1477
|
+
},
|
|
1478
|
+
blank() {
|
|
1479
|
+
return replicaBase({
|
|
1480
|
+
docName: "Blank",
|
|
1481
|
+
tables: {
|
|
1482
|
+
Table1: { rows: [{ id: 1 }] }
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
},
|
|
1486
|
+
todoList() {
|
|
1487
|
+
return replicaBase({
|
|
1488
|
+
docName: "Todo list",
|
|
1489
|
+
mode: "schema+data",
|
|
1490
|
+
tables: {
|
|
1491
|
+
Tasks: {
|
|
1492
|
+
label: "Tasks",
|
|
1493
|
+
columns: {
|
|
1494
|
+
title: { type: "Text", label: "Title" },
|
|
1495
|
+
done: { type: "Bool", label: "Done" },
|
|
1496
|
+
priority: { type: "Choice", label: "Priority" }
|
|
1497
|
+
},
|
|
1498
|
+
rows: [
|
|
1499
|
+
{ id: 1, title: "Buy milk", done: false, priority: "high" },
|
|
1500
|
+
{ id: 2, title: "Write the SOTA emulator", done: true, priority: "high" },
|
|
1501
|
+
{ id: 3, title: "Ship it", done: false, priority: "medium" }
|
|
1502
|
+
]
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
},
|
|
1507
|
+
contacts() {
|
|
1508
|
+
return replicaBase({
|
|
1509
|
+
docName: "Contacts",
|
|
1510
|
+
mode: "schema+data",
|
|
1511
|
+
tables: {
|
|
1512
|
+
Contacts: {
|
|
1513
|
+
label: "Contacts",
|
|
1514
|
+
columns: {
|
|
1515
|
+
firstName: { type: "Text" },
|
|
1516
|
+
lastName: { type: "Text" },
|
|
1517
|
+
email: { type: "Text" },
|
|
1518
|
+
company: { type: "Text" }
|
|
1519
|
+
},
|
|
1520
|
+
rows: [
|
|
1521
|
+
{ id: 1, firstName: "Ada", lastName: "Lovelace", email: "ada@analytical.engine", company: "British Crown" },
|
|
1522
|
+
{ id: 2, firstName: "Linus", lastName: "Torvalds", email: "linus@kernel.org", company: "Linux Foundation" },
|
|
1523
|
+
{ id: 3, firstName: "Margaret", lastName: "Hamilton", email: "margaret@apollo.nasa", company: "NASA" }
|
|
1524
|
+
]
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
};
|
|
1530
|
+
|
|
1175
1531
|
export {
|
|
1532
|
+
resolveMappings,
|
|
1176
1533
|
enrichEmulatorColumnMetadata,
|
|
1177
|
-
createDocument,
|
|
1178
|
-
getTable,
|
|
1179
|
-
getSelectedTable,
|
|
1180
|
-
findRow,
|
|
1181
|
-
synthesizeTablesMeta,
|
|
1182
|
-
synthesizeTablesColumnMeta,
|
|
1183
1534
|
EmulatorActionError,
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1535
|
+
safeParseDocument,
|
|
1536
|
+
parseDocument,
|
|
1537
|
+
createGristEmulator,
|
|
1538
|
+
presets
|
|
1188
1539
|
};
|
|
1189
|
-
//# sourceMappingURL=chunk-
|
|
1540
|
+
//# sourceMappingURL=chunk-OXA2VITC.js.map
|