grist-widget-sdk 0.3.26 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -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 +9 -517
- package/dist/app.js.map +1 -1
- 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-P6VTEKWO.js +618 -0
- package/dist/chunk-P6VTEKWO.js.map +1 -0
- package/dist/emulator/testing.cjs +2084 -391
- package/dist/emulator/testing.cjs.map +1 -1
- package/dist/emulator/testing.d.cts +55 -7
- package/dist/emulator/testing.d.ts +55 -7
- package/dist/emulator/testing.js +144 -19
- 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 -161
- package/dist/{inline-C7DkvuET.d.cts → templates-CGmRIQBV.d.cts} +89 -35
- package/dist/{inline-DpcT8tV9.d.ts → 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 -875
- 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-ELGVOHFD.js +0 -32
- package/dist/chunk-ELGVOHFD.js.map +0 -1
- package/dist/chunk-EPEBKXVN.js +0 -470
- package/dist/chunk-EPEBKXVN.js.map +0 -1
- package/dist/chunk-GTTD5HPO.js +0 -36
- package/dist/chunk-GTTD5HPO.js.map +0 -1
- package/dist/chunk-JASMM2YC.js +0 -243
- package/dist/chunk-JASMM2YC.js.map +0 -1
- package/dist/chunk-N3X46JW2.js +0 -37
- package/dist/chunk-N3X46JW2.js.map +0 -1
- package/dist/chunk-PDGEZ24B.js +0 -2366
- package/dist/chunk-PDGEZ24B.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-TZB3OZA6.js +0 -1630
- package/dist/chunk-TZB3OZA6.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 -508
- 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
|
|
@@ -798,6 +695,310 @@ function toPositiveInt(value) {
|
|
|
798
695
|
return null;
|
|
799
696
|
}
|
|
800
697
|
|
|
698
|
+
// src/emulator/validators.ts
|
|
699
|
+
import { z } from "zod";
|
|
700
|
+
var emulatorRowSchema = z.object({ id: z.number() }).catchall(z.unknown());
|
|
701
|
+
var formulaKindSchema = z.enum(["computed", "trigger", "none", "empty_slot"]);
|
|
702
|
+
var replicaColumnSchema = z.object({
|
|
703
|
+
colRef: z.number().optional(),
|
|
704
|
+
type: z.string().optional(),
|
|
705
|
+
label: z.string().optional(),
|
|
706
|
+
parentPos: z.number().optional(),
|
|
707
|
+
isFormula: z.boolean().optional(),
|
|
708
|
+
formulaKind: formulaKindSchema.optional(),
|
|
709
|
+
formula: z.string().optional(),
|
|
710
|
+
description: z.string().optional(),
|
|
711
|
+
widgetOptions: z.record(z.string(), z.unknown()).optional(),
|
|
712
|
+
displayColRef: z.number().optional(),
|
|
713
|
+
visibleColRef: z.number().optional()
|
|
714
|
+
}).strict();
|
|
715
|
+
var replicaTableSchema = z.object({
|
|
716
|
+
label: z.string().optional(),
|
|
717
|
+
columns: z.record(z.string(), replicaColumnSchema).optional(),
|
|
718
|
+
rows: z.array(emulatorRowSchema).optional(),
|
|
719
|
+
rowCount: z.number().optional(),
|
|
720
|
+
dataOmitted: z.boolean().optional(),
|
|
721
|
+
dataOmittedReason: z.string().optional(),
|
|
722
|
+
error: z.string().optional()
|
|
723
|
+
}).strict();
|
|
724
|
+
var replicaSelectionSchema = z.object({
|
|
725
|
+
tableId: z.string().optional(),
|
|
726
|
+
rowId: z.union([z.number(), z.literal("new"), z.null()]).optional(),
|
|
727
|
+
mappings: z.unknown().nullable().optional(),
|
|
728
|
+
cursor: z.unknown().optional()
|
|
729
|
+
}).strict();
|
|
730
|
+
var replicaWidgetConfigurationSchema = z.object({
|
|
731
|
+
requiredAccess: z.string().optional(),
|
|
732
|
+
columns: z.unknown().optional(),
|
|
733
|
+
allowSelectBy: z.boolean().optional(),
|
|
734
|
+
hasCustomOptions: z.boolean().optional()
|
|
735
|
+
}).strict();
|
|
736
|
+
var replicaAccessTokenSchema = z.object({
|
|
737
|
+
token: z.string(),
|
|
738
|
+
baseUrl: z.string(),
|
|
739
|
+
ttlMsecs: z.number()
|
|
740
|
+
}).strict();
|
|
741
|
+
var replicaDocumentSchema = z.object({
|
|
742
|
+
generatedAt: z.string(),
|
|
743
|
+
source: z.string().optional(),
|
|
744
|
+
docName: z.string().optional(),
|
|
745
|
+
mode: z.enum(["schema-only", "schema+samples", "schema+data"]).optional(),
|
|
746
|
+
tables: z.record(z.string(), replicaTableSchema),
|
|
747
|
+
selection: replicaSelectionSchema.optional(),
|
|
748
|
+
widgetOptions: z.record(z.string(), z.unknown()).optional(),
|
|
749
|
+
widgetInteraction: z.unknown().optional(),
|
|
750
|
+
configuration: replicaWidgetConfigurationSchema.optional(),
|
|
751
|
+
accessToken: replicaAccessTokenSchema.nullable().optional()
|
|
752
|
+
}).strict().refine((value) => Object.keys(value.tables).length > 0, {
|
|
753
|
+
message: "at least one table is required",
|
|
754
|
+
path: ["tables"]
|
|
755
|
+
});
|
|
756
|
+
function formatZodError(prefix, error) {
|
|
757
|
+
const details = error.issues.map((issue) => {
|
|
758
|
+
const where = issue.path.length > 0 ? issue.path.join(".") : "<root>";
|
|
759
|
+
return `${where}: ${issue.message}`;
|
|
760
|
+
}).join("; ");
|
|
761
|
+
return new Error(`${prefix}: ${details}`);
|
|
762
|
+
}
|
|
763
|
+
function safeParseDocument(input) {
|
|
764
|
+
return replicaDocumentSchema.safeParse(input);
|
|
765
|
+
}
|
|
766
|
+
function parseDocument(input) {
|
|
767
|
+
const result = safeParseDocument(input);
|
|
768
|
+
if (!result.success) {
|
|
769
|
+
throw formatZodError("parseDocument", result.error);
|
|
770
|
+
}
|
|
771
|
+
return result.data;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// src/emulator/api/command-api.ts
|
|
775
|
+
function createCommandApiImpl(bus) {
|
|
776
|
+
return {
|
|
777
|
+
async run(name) {
|
|
778
|
+
bus.emit({ type: "user-action", action: ["RunCommand", name] });
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// src/emulator/api/custom-section-api.ts
|
|
784
|
+
function createCustomSectionApiImpl(state, bus) {
|
|
785
|
+
return {
|
|
786
|
+
async configure(settings) {
|
|
787
|
+
const config = {
|
|
788
|
+
requiredAccess: settings.requiredAccess,
|
|
789
|
+
columns: settings.columns,
|
|
790
|
+
allowSelectBy: settings.allowSelectBy,
|
|
791
|
+
hasCustomOptions: settings.hasCustomOptions
|
|
792
|
+
};
|
|
793
|
+
state.configuration = config;
|
|
794
|
+
bus.emit({ type: "ready", configuration: config });
|
|
795
|
+
bus.emit({ type: "mappings-change", mappings: resolveMappings(state) });
|
|
796
|
+
},
|
|
797
|
+
async mappings() {
|
|
798
|
+
return resolveMappings(state);
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// src/emulator/api/grist-api.ts
|
|
804
|
+
function createGristApiImpl() {
|
|
805
|
+
return {
|
|
806
|
+
async ping() {
|
|
807
|
+
return "pong";
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// src/emulator/core/columnar.ts
|
|
813
|
+
function rowsToColumnar(table, rows, options = {}) {
|
|
814
|
+
const cols = Object.keys(table.columns);
|
|
815
|
+
const out = { id: rows.map((r) => r.id) };
|
|
816
|
+
for (const col of cols) {
|
|
817
|
+
out[col] = rows.map((r) => encodeForWire(r[col], table.columns[col]?.type, options.keepEncoded));
|
|
818
|
+
}
|
|
819
|
+
return out;
|
|
820
|
+
}
|
|
821
|
+
function rowToRecord(table, row, options = {}) {
|
|
822
|
+
const out = { id: row.id };
|
|
823
|
+
for (const col of Object.keys(table.columns)) {
|
|
824
|
+
out[col] = encodeForWire(row[col], table.columns[col]?.type, options.keepEncoded);
|
|
825
|
+
}
|
|
826
|
+
return out;
|
|
827
|
+
}
|
|
828
|
+
function encodeForWire(value, type, keepEncoded) {
|
|
829
|
+
if (value instanceof Date) {
|
|
830
|
+
const epoch = Math.floor(value.getTime() / 1e3);
|
|
831
|
+
if (keepEncoded) {
|
|
832
|
+
return type && type.startsWith("DateTime") ? ["D", epoch, "UTC"] : ["d", epoch];
|
|
833
|
+
}
|
|
834
|
+
return epoch;
|
|
835
|
+
}
|
|
836
|
+
return value;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// src/emulator/api/grist-doc-api.ts
|
|
840
|
+
function createGristDocApiImpl(state, bus) {
|
|
841
|
+
return {
|
|
842
|
+
async getDocName() {
|
|
843
|
+
return state.docName;
|
|
844
|
+
},
|
|
845
|
+
async listTables() {
|
|
846
|
+
return [...state.tables.keys()];
|
|
847
|
+
},
|
|
848
|
+
async fetchTable(tableId) {
|
|
849
|
+
if (tableId === "_grist_Tables") {
|
|
850
|
+
return synthesizeTablesMeta(state);
|
|
851
|
+
}
|
|
852
|
+
if (tableId === "_grist_Tables_column") {
|
|
853
|
+
return synthesizeTablesColumnMeta(state);
|
|
854
|
+
}
|
|
855
|
+
const table = getTable(state, tableId);
|
|
856
|
+
return rowsToColumnar(table, table.rows);
|
|
857
|
+
},
|
|
858
|
+
async applyUserActions(actions) {
|
|
859
|
+
const result = applyUserActions(state, bus, actions);
|
|
860
|
+
return { retValues: result.retValues };
|
|
861
|
+
},
|
|
862
|
+
async getAccessToken() {
|
|
863
|
+
if (!state.accessToken) {
|
|
864
|
+
const err = new Error("getAccessToken not available (no token configured on emulator)");
|
|
865
|
+
throw err;
|
|
866
|
+
}
|
|
867
|
+
return { ...state.accessToken };
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// src/emulator/api/grist-view.ts
|
|
873
|
+
function createGristViewImpl(state, bus) {
|
|
874
|
+
return {
|
|
875
|
+
async fetchSelectedTable(options) {
|
|
876
|
+
const table = getSelectedTable(state);
|
|
877
|
+
return rowsToColumnar(table, table.rows, { keepEncoded: options?.keepEncoded });
|
|
878
|
+
},
|
|
879
|
+
async fetchSelectedRecord(rowId, options) {
|
|
880
|
+
const table = getSelectedTable(state);
|
|
881
|
+
const row = findRow(table, rowId);
|
|
882
|
+
if (!row) throw new Error(`No row ${rowId} in ${table.tableId}`);
|
|
883
|
+
return rowToRecord(table, row, { keepEncoded: options?.keepEncoded });
|
|
884
|
+
},
|
|
885
|
+
async allowSelectBy() {
|
|
886
|
+
return state.configuration?.allowSelectBy ?? false;
|
|
887
|
+
},
|
|
888
|
+
async setSelectedRows(rowIds) {
|
|
889
|
+
if (!rowIds || rowIds.length === 0) {
|
|
890
|
+
state.selection.rowId = null;
|
|
891
|
+
} else {
|
|
892
|
+
state.selection.rowId = rowIds[0];
|
|
893
|
+
}
|
|
894
|
+
bus.emit({
|
|
895
|
+
type: "cursor-change",
|
|
896
|
+
tableId: state.selection.tableId,
|
|
897
|
+
rowId: state.selection.rowId
|
|
898
|
+
});
|
|
899
|
+
},
|
|
900
|
+
async setCursorPos(cursor) {
|
|
901
|
+
const tableId = state.selection.tableId;
|
|
902
|
+
if (typeof cursor.rowId === "number") state.selection.rowId = cursor.rowId;
|
|
903
|
+
state.selection.cursor = { ...state.selection.cursor, ...cursor };
|
|
904
|
+
bus.emit({
|
|
905
|
+
type: "cursor-change",
|
|
906
|
+
tableId,
|
|
907
|
+
rowId: state.selection.rowId,
|
|
908
|
+
cursor: state.selection.cursor
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// src/emulator/api/widget-api.ts
|
|
915
|
+
function createWidgetApiImpl(state, bus) {
|
|
916
|
+
function emit() {
|
|
917
|
+
bus.emit({
|
|
918
|
+
type: "options-change",
|
|
919
|
+
options: { ...state.widgetOptions },
|
|
920
|
+
interaction: { ...state.widgetInteraction }
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
return {
|
|
924
|
+
async getOption(key) {
|
|
925
|
+
return state.widgetOptions[key];
|
|
926
|
+
},
|
|
927
|
+
async setOption(key, value) {
|
|
928
|
+
state.widgetOptions[key] = value;
|
|
929
|
+
emit();
|
|
930
|
+
},
|
|
931
|
+
async getOptions() {
|
|
932
|
+
return Object.keys(state.widgetOptions).length > 0 ? { ...state.widgetOptions } : null;
|
|
933
|
+
},
|
|
934
|
+
// Replaces every option, as Grist's WidgetAPI.setOptions does.
|
|
935
|
+
async setOptions(options) {
|
|
936
|
+
state.widgetOptions = { ...options };
|
|
937
|
+
emit();
|
|
938
|
+
},
|
|
939
|
+
async clearOptions() {
|
|
940
|
+
state.widgetOptions = {};
|
|
941
|
+
emit();
|
|
942
|
+
}
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// src/emulator/api/index.ts
|
|
947
|
+
function createApiBundle(state, bus) {
|
|
948
|
+
return {
|
|
949
|
+
GristView: createGristViewImpl(state, bus),
|
|
950
|
+
GristDocAPI: createGristDocApiImpl(state, bus),
|
|
951
|
+
WidgetAPI: createWidgetApiImpl(state, bus),
|
|
952
|
+
CustomSectionAPI: createCustomSectionApiImpl(state, bus),
|
|
953
|
+
GristAPI: createGristApiImpl(),
|
|
954
|
+
CommandAPI: createCommandApiImpl(bus)
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// src/emulator/core/event-bus.ts
|
|
959
|
+
function createEventBus(options = {}) {
|
|
960
|
+
const limit = options.historyLimit ?? 200;
|
|
961
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
962
|
+
const history = [];
|
|
963
|
+
return {
|
|
964
|
+
emit(event) {
|
|
965
|
+
history.push(event);
|
|
966
|
+
if (history.length > limit) history.shift();
|
|
967
|
+
for (const fn of [...listeners]) {
|
|
968
|
+
try {
|
|
969
|
+
fn(event);
|
|
970
|
+
} catch (err) {
|
|
971
|
+
console.error("[grist-emulator] event listener threw:", err);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
},
|
|
975
|
+
on(listener) {
|
|
976
|
+
listeners.add(listener);
|
|
977
|
+
return () => {
|
|
978
|
+
listeners.delete(listener);
|
|
979
|
+
};
|
|
980
|
+
},
|
|
981
|
+
history() {
|
|
982
|
+
return history;
|
|
983
|
+
},
|
|
984
|
+
clear() {
|
|
985
|
+
history.length = 0;
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// src/emulator/theme-message.ts
|
|
991
|
+
function buildGristThemeHostMessage(appearance, options) {
|
|
992
|
+
return {
|
|
993
|
+
theme: {
|
|
994
|
+
appearance,
|
|
995
|
+
name: appearance,
|
|
996
|
+
colors: {}
|
|
997
|
+
},
|
|
998
|
+
...options?.fromReady ? { fromReady: true } : {}
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
801
1002
|
// src/emulator/transports/inline.ts
|
|
802
1003
|
function createInlineTransport(options) {
|
|
803
1004
|
const { state, bus, api } = options;
|
|
@@ -827,14 +1028,6 @@ function createInlineTransport(options) {
|
|
|
827
1028
|
logErr(err);
|
|
828
1029
|
}
|
|
829
1030
|
}
|
|
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
1031
|
return;
|
|
839
1032
|
}
|
|
840
1033
|
if (rowId === null) {
|
|
@@ -1172,18 +1365,172 @@ function installFakeIframeContext(target) {
|
|
|
1172
1365
|
}
|
|
1173
1366
|
}
|
|
1174
1367
|
|
|
1368
|
+
// src/emulator/create-grist-emulator.ts
|
|
1369
|
+
function createGristEmulator(options) {
|
|
1370
|
+
const validatedDocument = parseDocument(options.document);
|
|
1371
|
+
const state = createDocument({
|
|
1372
|
+
...validatedDocument,
|
|
1373
|
+
widgetOptions: options.widgetOptions ?? validatedDocument.widgetOptions,
|
|
1374
|
+
// No access_level by default: the emulator then grants the access the
|
|
1375
|
+
// widget asks for in grist.ready(), as a user accepting it in Grist would.
|
|
1376
|
+
widgetInteraction: options.widgetInteraction ?? validatedDocument.widgetInteraction ?? {}
|
|
1377
|
+
});
|
|
1378
|
+
const bus = createEventBus();
|
|
1379
|
+
const api = createApiBundle(state, bus);
|
|
1380
|
+
const inline = createInlineTransport({
|
|
1381
|
+
state,
|
|
1382
|
+
bus,
|
|
1383
|
+
api,
|
|
1384
|
+
initialTheme: options.initialTheme,
|
|
1385
|
+
target: options.transport?.target
|
|
1386
|
+
});
|
|
1387
|
+
const transport = inline;
|
|
1388
|
+
const grist = inline.grist;
|
|
1389
|
+
function apply(actions) {
|
|
1390
|
+
return applyUserActions(state, bus, actions);
|
|
1391
|
+
}
|
|
1392
|
+
return {
|
|
1393
|
+
state,
|
|
1394
|
+
bus,
|
|
1395
|
+
api,
|
|
1396
|
+
transport,
|
|
1397
|
+
grist,
|
|
1398
|
+
apply,
|
|
1399
|
+
mutate: {
|
|
1400
|
+
addRow: (tableId, fields) => apply([["AddRecord", tableId, null, fields]]).retValues[0],
|
|
1401
|
+
updateRow: (tableId, rowId, fields) => {
|
|
1402
|
+
apply([["UpdateRecord", tableId, rowId, fields]]);
|
|
1403
|
+
},
|
|
1404
|
+
removeRow: (tableId, rowId) => {
|
|
1405
|
+
apply([["RemoveRecord", tableId, rowId]]);
|
|
1406
|
+
}
|
|
1407
|
+
},
|
|
1408
|
+
selectRow(rowId) {
|
|
1409
|
+
state.selection.rowId = rowId;
|
|
1410
|
+
bus.emit({ type: "cursor-change", tableId: state.selection.tableId, rowId });
|
|
1411
|
+
},
|
|
1412
|
+
selectTable(tableId) {
|
|
1413
|
+
if (!state.tables.has(tableId)) throw new Error(`Unknown table: ${tableId}`);
|
|
1414
|
+
state.selection.tableId = tableId;
|
|
1415
|
+
const first = state.tables.get(tableId).rows[0]?.id ?? null;
|
|
1416
|
+
state.selection.rowId = first;
|
|
1417
|
+
bus.emit({ type: "data-change", tableId });
|
|
1418
|
+
bus.emit({ type: "cursor-change", tableId, rowId: first });
|
|
1419
|
+
},
|
|
1420
|
+
setColumnMappings(mappings) {
|
|
1421
|
+
if (mappings === void 0) {
|
|
1422
|
+
delete state.selection.mappings;
|
|
1423
|
+
} else {
|
|
1424
|
+
state.selection.mappings = mappings;
|
|
1425
|
+
}
|
|
1426
|
+
bus.emit({ type: "mappings-change", mappings: resolveMappings(state) });
|
|
1427
|
+
},
|
|
1428
|
+
async setOptions(opts) {
|
|
1429
|
+
await api.WidgetAPI.setOptions(opts);
|
|
1430
|
+
},
|
|
1431
|
+
setInteraction(interaction) {
|
|
1432
|
+
state.widgetInteraction = { ...interaction };
|
|
1433
|
+
bus.emit({ type: "options-change", options: { ...state.widgetOptions }, interaction: { ...interaction } });
|
|
1434
|
+
},
|
|
1435
|
+
theme: {
|
|
1436
|
+
set(next) {
|
|
1437
|
+
bus.emit({ type: "theme-change", message: buildGristThemeHostMessage(next) });
|
|
1438
|
+
}
|
|
1439
|
+
},
|
|
1440
|
+
dispose() {
|
|
1441
|
+
transport.dispose();
|
|
1442
|
+
bus.clear();
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
// src/emulator/templates.ts
|
|
1448
|
+
var PRESET_GENERATED_AT = "1970-01-01T00:00:00.000Z";
|
|
1449
|
+
function replicaBase(overrides) {
|
|
1450
|
+
return {
|
|
1451
|
+
generatedAt: PRESET_GENERATED_AT,
|
|
1452
|
+
...overrides
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
var presets = {
|
|
1456
|
+
/**
|
|
1457
|
+
* Default starter fixture for demos / sandboxes that need a neutral document
|
|
1458
|
+
* without choosing a domain-specific preset.
|
|
1459
|
+
*/
|
|
1460
|
+
defaultTemplate() {
|
|
1461
|
+
return replicaBase({
|
|
1462
|
+
docName: "Default",
|
|
1463
|
+
tables: {
|
|
1464
|
+
Table1: {
|
|
1465
|
+
label: "Table1",
|
|
1466
|
+
rows: [
|
|
1467
|
+
{ id: 1, Name: "Alice" },
|
|
1468
|
+
{ id: 2, Name: "Bob" }
|
|
1469
|
+
]
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
});
|
|
1473
|
+
},
|
|
1474
|
+
blank() {
|
|
1475
|
+
return replicaBase({
|
|
1476
|
+
docName: "Blank",
|
|
1477
|
+
tables: {
|
|
1478
|
+
Table1: { rows: [{ id: 1 }] }
|
|
1479
|
+
}
|
|
1480
|
+
});
|
|
1481
|
+
},
|
|
1482
|
+
todoList() {
|
|
1483
|
+
return replicaBase({
|
|
1484
|
+
docName: "Todo list",
|
|
1485
|
+
mode: "schema+data",
|
|
1486
|
+
tables: {
|
|
1487
|
+
Tasks: {
|
|
1488
|
+
label: "Tasks",
|
|
1489
|
+
columns: {
|
|
1490
|
+
title: { type: "Text", label: "Title" },
|
|
1491
|
+
done: { type: "Bool", label: "Done" },
|
|
1492
|
+
priority: { type: "Choice", label: "Priority" }
|
|
1493
|
+
},
|
|
1494
|
+
rows: [
|
|
1495
|
+
{ id: 1, title: "Buy milk", done: false, priority: "high" },
|
|
1496
|
+
{ id: 2, title: "Write the SOTA emulator", done: true, priority: "high" },
|
|
1497
|
+
{ id: 3, title: "Ship it", done: false, priority: "medium" }
|
|
1498
|
+
]
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
});
|
|
1502
|
+
},
|
|
1503
|
+
contacts() {
|
|
1504
|
+
return replicaBase({
|
|
1505
|
+
docName: "Contacts",
|
|
1506
|
+
mode: "schema+data",
|
|
1507
|
+
tables: {
|
|
1508
|
+
Contacts: {
|
|
1509
|
+
label: "Contacts",
|
|
1510
|
+
columns: {
|
|
1511
|
+
firstName: { type: "Text" },
|
|
1512
|
+
lastName: { type: "Text" },
|
|
1513
|
+
email: { type: "Text" },
|
|
1514
|
+
company: { type: "Text" }
|
|
1515
|
+
},
|
|
1516
|
+
rows: [
|
|
1517
|
+
{ id: 1, firstName: "Ada", lastName: "Lovelace", email: "ada@analytical.engine", company: "British Crown" },
|
|
1518
|
+
{ id: 2, firstName: "Linus", lastName: "Torvalds", email: "linus@kernel.org", company: "Linux Foundation" },
|
|
1519
|
+
{ id: 3, firstName: "Margaret", lastName: "Hamilton", email: "margaret@apollo.nasa", company: "NASA" }
|
|
1520
|
+
]
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
};
|
|
1526
|
+
|
|
1175
1527
|
export {
|
|
1528
|
+
resolveMappings,
|
|
1176
1529
|
enrichEmulatorColumnMetadata,
|
|
1177
|
-
createDocument,
|
|
1178
|
-
getTable,
|
|
1179
|
-
getSelectedTable,
|
|
1180
|
-
findRow,
|
|
1181
|
-
synthesizeTablesMeta,
|
|
1182
|
-
synthesizeTablesColumnMeta,
|
|
1183
1530
|
EmulatorActionError,
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1531
|
+
safeParseDocument,
|
|
1532
|
+
parseDocument,
|
|
1533
|
+
createGristEmulator,
|
|
1534
|
+
presets
|
|
1188
1535
|
};
|
|
1189
|
-
//# sourceMappingURL=chunk-
|
|
1536
|
+
//# sourceMappingURL=chunk-B5ZZNLVS.js.map
|