@richardmcquiston01/dashboard-widgets-toolkit 0.10.0 → 0.11.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/dist/core.cjs +1222 -17
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +13 -179
- package/dist/core.d.ts +13 -179
- package/dist/core.js +1191 -18
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +1222 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1191 -18
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +587 -32
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +75 -5
- package/dist/react.d.ts +75 -5
- package/dist/react.js +588 -34
- package/dist/react.js.map +1 -1
- package/dist/storage-VaaysVnZ.d.cts +1333 -0
- package/dist/storage-VaaysVnZ.d.ts +1333 -0
- package/package.json +1 -1
- package/dist/scale-N69bo7wo.d.cts +0 -758
- package/dist/scale-N69bo7wo.d.ts +0 -758
package/dist/core.cjs
CHANGED
|
@@ -942,6 +942,283 @@ function enforceLocks(definitions, layout, options = {}) {
|
|
|
942
942
|
}));
|
|
943
943
|
}
|
|
944
944
|
|
|
945
|
+
// src/core/options.ts
|
|
946
|
+
var DATE_RANGE_PRESETS = [
|
|
947
|
+
"today",
|
|
948
|
+
"yesterday",
|
|
949
|
+
"last7",
|
|
950
|
+
"last30",
|
|
951
|
+
"last90",
|
|
952
|
+
"thisMonth",
|
|
953
|
+
"lastMonth",
|
|
954
|
+
"thisYear",
|
|
955
|
+
"lastYear"
|
|
956
|
+
];
|
|
957
|
+
var WIDGET_OPTION_TYPES = [
|
|
958
|
+
"choice",
|
|
959
|
+
"number",
|
|
960
|
+
"boolean",
|
|
961
|
+
"text",
|
|
962
|
+
"dateRange",
|
|
963
|
+
"color",
|
|
964
|
+
"sort",
|
|
965
|
+
"columns"
|
|
966
|
+
];
|
|
967
|
+
var MAX_WIDGET_OPTIONS = 20;
|
|
968
|
+
var MAX_TEXT_OPTION_LENGTH = 500;
|
|
969
|
+
var OPTION_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,39}$/;
|
|
970
|
+
var COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
|
|
971
|
+
var INTERVAL_PATTERN = /^(\d{4}-\d{2}-\d{2})\/(\d{4}-\d{2}-\d{2})$/;
|
|
972
|
+
var CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/;
|
|
973
|
+
function isColumnKeyFor(kind, key) {
|
|
974
|
+
if (kind === "TABLE") return /^c\d+$/.test(key);
|
|
975
|
+
if (kind === "BAR_LIST") return key === "label" || key === "value";
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
function isClientApplied(option) {
|
|
979
|
+
return (option.type === "sort" || option.type === "columns") && option.apply === "client";
|
|
980
|
+
}
|
|
981
|
+
function civilToday(context) {
|
|
982
|
+
const now = context.now ?? Date.now();
|
|
983
|
+
let timeZone = context.timeZone ?? "UTC";
|
|
984
|
+
try {
|
|
985
|
+
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
986
|
+
} catch {
|
|
987
|
+
timeZone = "UTC";
|
|
988
|
+
}
|
|
989
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
990
|
+
timeZone,
|
|
991
|
+
year: "numeric",
|
|
992
|
+
month: "2-digit",
|
|
993
|
+
day: "2-digit"
|
|
994
|
+
}).formatToParts(new Date(now));
|
|
995
|
+
const pick = (type) => Number(parts.find((part) => part.type === type)?.value ?? "0");
|
|
996
|
+
return { year: pick("year"), month: pick("month"), day: pick("day") };
|
|
997
|
+
}
|
|
998
|
+
function toIso(epochMs) {
|
|
999
|
+
return new Date(epochMs).toISOString().slice(0, 10);
|
|
1000
|
+
}
|
|
1001
|
+
var DAY_MS = 864e5;
|
|
1002
|
+
function parseIsoDay(text) {
|
|
1003
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
|
|
1004
|
+
if (match === null) return void 0;
|
|
1005
|
+
const epoch = Date.UTC(
|
|
1006
|
+
Number(match[1]),
|
|
1007
|
+
Number(match[2]) - 1,
|
|
1008
|
+
Number(match[3])
|
|
1009
|
+
);
|
|
1010
|
+
return toIso(epoch) === text ? epoch : void 0;
|
|
1011
|
+
}
|
|
1012
|
+
function isIsoInterval(value) {
|
|
1013
|
+
const match = INTERVAL_PATTERN.exec(value);
|
|
1014
|
+
if (match === null) return false;
|
|
1015
|
+
const start = parseIsoDay(match[1]);
|
|
1016
|
+
const end = parseIsoDay(match[2]);
|
|
1017
|
+
return start !== void 0 && end !== void 0 && start <= end;
|
|
1018
|
+
}
|
|
1019
|
+
function isPreset(value) {
|
|
1020
|
+
return DATE_RANGE_PRESETS.includes(value);
|
|
1021
|
+
}
|
|
1022
|
+
function resolveDateRange(value, context = {}) {
|
|
1023
|
+
if (isIsoInterval(value)) return value;
|
|
1024
|
+
if (!isPreset(value)) return void 0;
|
|
1025
|
+
const today = civilToday(context);
|
|
1026
|
+
const todayMs = Date.UTC(today.year, today.month - 1, today.day);
|
|
1027
|
+
const range = (startMs, endMs) => `${toIso(startMs)}/${toIso(endMs)}`;
|
|
1028
|
+
switch (value) {
|
|
1029
|
+
case "today":
|
|
1030
|
+
return range(todayMs, todayMs);
|
|
1031
|
+
case "yesterday":
|
|
1032
|
+
return range(todayMs - DAY_MS, todayMs - DAY_MS);
|
|
1033
|
+
case "last7":
|
|
1034
|
+
return range(todayMs - 6 * DAY_MS, todayMs);
|
|
1035
|
+
case "last30":
|
|
1036
|
+
return range(todayMs - 29 * DAY_MS, todayMs);
|
|
1037
|
+
case "last90":
|
|
1038
|
+
return range(todayMs - 89 * DAY_MS, todayMs);
|
|
1039
|
+
case "thisMonth":
|
|
1040
|
+
return range(Date.UTC(today.year, today.month - 1, 1), todayMs);
|
|
1041
|
+
case "lastMonth":
|
|
1042
|
+
return range(
|
|
1043
|
+
Date.UTC(today.year, today.month - 2, 1),
|
|
1044
|
+
Date.UTC(today.year, today.month - 1, 1) - DAY_MS
|
|
1045
|
+
);
|
|
1046
|
+
case "thisYear":
|
|
1047
|
+
return range(Date.UTC(today.year, 0, 1), todayMs);
|
|
1048
|
+
case "lastYear":
|
|
1049
|
+
return range(
|
|
1050
|
+
Date.UTC(today.year - 1, 0, 1),
|
|
1051
|
+
Date.UTC(today.year, 0, 1) - DAY_MS
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
function onStepGrid(option, value) {
|
|
1056
|
+
if (option.step === void 0) return true;
|
|
1057
|
+
const steps = (value - option.min) / option.step;
|
|
1058
|
+
return Math.abs(steps - Math.round(steps)) < 1e-9;
|
|
1059
|
+
}
|
|
1060
|
+
function parseSortValue(value) {
|
|
1061
|
+
const separator = value.lastIndexOf(":");
|
|
1062
|
+
if (separator <= 0) return void 0;
|
|
1063
|
+
const column = value.slice(0, separator);
|
|
1064
|
+
const direction = value.slice(separator + 1);
|
|
1065
|
+
if (direction !== "asc" && direction !== "desc") return void 0;
|
|
1066
|
+
return { column, direction };
|
|
1067
|
+
}
|
|
1068
|
+
function sortFromOptions(definition, values) {
|
|
1069
|
+
if (values === void 0) return void 0;
|
|
1070
|
+
const sortOption = definition.options?.find(
|
|
1071
|
+
(option) => option.type === "sort"
|
|
1072
|
+
);
|
|
1073
|
+
if (sortOption === void 0) return void 0;
|
|
1074
|
+
const chosen = values[sortOption.key];
|
|
1075
|
+
return typeof chosen === "string" ? parseSortValue(chosen) : void 0;
|
|
1076
|
+
}
|
|
1077
|
+
function coerceOptionValue(option, value, context = {}) {
|
|
1078
|
+
switch (option.type) {
|
|
1079
|
+
case "choice":
|
|
1080
|
+
return typeof value === "string" && option.choices.some((choice) => choice.value === value) ? value : void 0;
|
|
1081
|
+
case "number":
|
|
1082
|
+
return typeof value === "number" && Number.isFinite(value) && value >= option.min && value <= option.max && onStepGrid(option, value) ? value : void 0;
|
|
1083
|
+
case "boolean":
|
|
1084
|
+
return typeof value === "boolean" ? value : void 0;
|
|
1085
|
+
case "text": {
|
|
1086
|
+
if (typeof value !== "string") return void 0;
|
|
1087
|
+
const trimmed = value.trim();
|
|
1088
|
+
return trimmed.length <= option.maxLength && !CONTROL_CHARACTERS.test(trimmed) ? trimmed : void 0;
|
|
1089
|
+
}
|
|
1090
|
+
case "dateRange": {
|
|
1091
|
+
if (typeof value !== "string") return void 0;
|
|
1092
|
+
if (isPreset(value) && option.presets !== void 0 && !option.presets.some((preset) => preset.value === value)) {
|
|
1093
|
+
return void 0;
|
|
1094
|
+
}
|
|
1095
|
+
return resolveDateRange(value, context);
|
|
1096
|
+
}
|
|
1097
|
+
case "color":
|
|
1098
|
+
return typeof value === "string" && COLOR_PATTERN.test(value) ? value.toLowerCase() : void 0;
|
|
1099
|
+
case "sort": {
|
|
1100
|
+
if (typeof value !== "string") return void 0;
|
|
1101
|
+
const sort = parseSortValue(value);
|
|
1102
|
+
return sort !== void 0 && option.columns.some((column) => column.key === sort.column) ? value : void 0;
|
|
1103
|
+
}
|
|
1104
|
+
case "columns": {
|
|
1105
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
1106
|
+
const keys = value;
|
|
1107
|
+
const allowed = new Set(option.columns.map((c) => c.key));
|
|
1108
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1109
|
+
for (const key of keys) {
|
|
1110
|
+
if (typeof key !== "string" || !allowed.has(key) || seen.has(key)) {
|
|
1111
|
+
return void 0;
|
|
1112
|
+
}
|
|
1113
|
+
seen.add(key);
|
|
1114
|
+
}
|
|
1115
|
+
return [...seen];
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
function defaultOptionValue(option, context = {}) {
|
|
1120
|
+
switch (option.type) {
|
|
1121
|
+
case "dateRange":
|
|
1122
|
+
return resolveDateRange(option.default, context);
|
|
1123
|
+
case "sort":
|
|
1124
|
+
return option.default;
|
|
1125
|
+
case "columns":
|
|
1126
|
+
return [...option.default];
|
|
1127
|
+
default:
|
|
1128
|
+
return option.default;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
var NO_VALUES = Object.freeze({});
|
|
1132
|
+
function resolveOptionValues(definition, chosen, context = {}) {
|
|
1133
|
+
const options = definition.options;
|
|
1134
|
+
if (options === void 0 || options.length === 0) return NO_VALUES;
|
|
1135
|
+
const values = {};
|
|
1136
|
+
for (const option of options) {
|
|
1137
|
+
const picked = chosen?.[option.key];
|
|
1138
|
+
const value = (picked === void 0 ? void 0 : coerceOptionValue(option, picked, context)) ?? defaultOptionValue(option, context);
|
|
1139
|
+
if (value !== void 0) values[option.key] = value;
|
|
1140
|
+
}
|
|
1141
|
+
return values;
|
|
1142
|
+
}
|
|
1143
|
+
function sameValue(a, b) {
|
|
1144
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
1145
|
+
return a.length === b.length && a.every((item, i) => item === b[i]);
|
|
1146
|
+
}
|
|
1147
|
+
return a === b;
|
|
1148
|
+
}
|
|
1149
|
+
function sameOptionValues(a, b) {
|
|
1150
|
+
const keys = Object.keys({ ...a, ...b });
|
|
1151
|
+
return keys.every((key) => sameValue(a[key], b[key]));
|
|
1152
|
+
}
|
|
1153
|
+
function optionsCacheSuffix(definition, values, context = {}) {
|
|
1154
|
+
const parts = [];
|
|
1155
|
+
for (const option of definition.options ?? []) {
|
|
1156
|
+
if (isClientApplied(option)) continue;
|
|
1157
|
+
const value = values[option.key];
|
|
1158
|
+
if (sameValue(value, defaultOptionValue(option, context))) continue;
|
|
1159
|
+
parts.push(
|
|
1160
|
+
`${option.key}=${encodeURIComponent(JSON.stringify(value ?? null))}`
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
parts.sort();
|
|
1164
|
+
return parts.length === 0 ? "" : `?${parts.join("&")}`;
|
|
1165
|
+
}
|
|
1166
|
+
function tableColumnIndex(key) {
|
|
1167
|
+
const match = /^c(\d+)$/.exec(key);
|
|
1168
|
+
return match === null ? void 0 : Number(match[1]);
|
|
1169
|
+
}
|
|
1170
|
+
function sortBarItems(items, sort) {
|
|
1171
|
+
const sign = sort.direction === "desc" ? -1 : 1;
|
|
1172
|
+
return items.map((item, index) => ({ item, index })).sort((a, b) => {
|
|
1173
|
+
const order = sort.column === "value" ? a.item.value - b.item.value : a.item.label.localeCompare(b.item.label);
|
|
1174
|
+
return order * sign || a.index - b.index;
|
|
1175
|
+
}).map(({ item }) => item);
|
|
1176
|
+
}
|
|
1177
|
+
function applyClientOptions(definition, data, values) {
|
|
1178
|
+
const options = definition.options ?? [];
|
|
1179
|
+
const sortOption = options.find(
|
|
1180
|
+
(o) => o.type === "sort" && o.apply === "client"
|
|
1181
|
+
);
|
|
1182
|
+
const columnsOption = options.find(
|
|
1183
|
+
(o) => o.type === "columns" && o.apply === "client"
|
|
1184
|
+
);
|
|
1185
|
+
const sortValue = sortOption === void 0 ? void 0 : values[sortOption.key];
|
|
1186
|
+
const columnsValue = columnsOption === void 0 ? void 0 : values[columnsOption.key];
|
|
1187
|
+
let next = data;
|
|
1188
|
+
if (typeof sortValue === "string") {
|
|
1189
|
+
const sort = parseSortValue(sortValue);
|
|
1190
|
+
if (sort !== void 0 && next.kind === "BAR_LIST") {
|
|
1191
|
+
if (sort.column === "label" || sort.column === "value") {
|
|
1192
|
+
next = { ...next, items: sortBarItems(next.items, sort) };
|
|
1193
|
+
}
|
|
1194
|
+
} else if (sort !== void 0 && next.kind === "TABLE") {
|
|
1195
|
+
const index = tableColumnIndex(sort.column);
|
|
1196
|
+
if (index !== void 0 && index < next.columns.length) {
|
|
1197
|
+
const sorted = queryRows(tableDetailData(next), {
|
|
1198
|
+
page: 1,
|
|
1199
|
+
pageSize: Math.max(1, next.rows.length),
|
|
1200
|
+
sort
|
|
1201
|
+
});
|
|
1202
|
+
if (sorted.ok) next = { ...next, rows: sorted.value.rows };
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
if (Array.isArray(columnsValue) && next.kind === "TABLE") {
|
|
1207
|
+
const table = next;
|
|
1208
|
+
const indexes = columnsValue.map(tableColumnIndex).filter(
|
|
1209
|
+
(index) => index !== void 0 && index < table.columns.length
|
|
1210
|
+
);
|
|
1211
|
+
if (indexes.length > 0) {
|
|
1212
|
+
next = {
|
|
1213
|
+
...table,
|
|
1214
|
+
columns: indexes.map((i) => table.columns[i]),
|
|
1215
|
+
rows: table.rows.map((row) => indexes.map((i) => row[i]))
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
return next;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
945
1222
|
// src/core/validate.ts
|
|
946
1223
|
var MAX_REPORTED_PROBLEMS = 10;
|
|
947
1224
|
function describeType2(value) {
|
|
@@ -1230,6 +1507,284 @@ function checkLockedOption(locked, problems) {
|
|
|
1230
1507
|
optionalBoolean(locked, "hide", "locked", problems);
|
|
1231
1508
|
optionalBoolean(locked, "minimize", "locked", problems);
|
|
1232
1509
|
}
|
|
1510
|
+
function checkOptionColumns(option, path, kind, problems) {
|
|
1511
|
+
const columns = option["columns"];
|
|
1512
|
+
if (!Array.isArray(columns) || columns.length === 0) {
|
|
1513
|
+
problems.push(
|
|
1514
|
+
`${join(path, "columns")} must be a non-empty array of { key, label }, got ${describeType2(columns)}.`
|
|
1515
|
+
);
|
|
1516
|
+
return [];
|
|
1517
|
+
}
|
|
1518
|
+
const keys = [];
|
|
1519
|
+
columns.forEach((entry, index) => {
|
|
1520
|
+
const entryPath = `${path}.columns[${index}]`;
|
|
1521
|
+
if (!isRecord(entry)) {
|
|
1522
|
+
problems.push(
|
|
1523
|
+
`${entryPath} must be an object, got ${describeType2(entry)}.`
|
|
1524
|
+
);
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
requireString(entry, "label", entryPath, problems);
|
|
1528
|
+
const key = entry["key"];
|
|
1529
|
+
if (typeof key !== "string") {
|
|
1530
|
+
problems.push(
|
|
1531
|
+
`${entryPath}.key must be a string, got ${describeType2(key)}.`
|
|
1532
|
+
);
|
|
1533
|
+
} else if (typeof kind === "string" && !isColumnKeyFor(kind, key)) {
|
|
1534
|
+
problems.push(
|
|
1535
|
+
`${entryPath}.key "${key}" is not a column of a ${kind} widget (${kind === "TABLE" ? '"c0", "c1", ...' : '"label" or "value"'}).`
|
|
1536
|
+
);
|
|
1537
|
+
} else if (keys.includes(key)) {
|
|
1538
|
+
problems.push(`${entryPath}.key "${key}" is used twice.`);
|
|
1539
|
+
} else {
|
|
1540
|
+
keys.push(key);
|
|
1541
|
+
}
|
|
1542
|
+
});
|
|
1543
|
+
return keys;
|
|
1544
|
+
}
|
|
1545
|
+
function checkOption(option, path, kind, seenKeys, seenTypes, problems) {
|
|
1546
|
+
if (!isRecord(option)) {
|
|
1547
|
+
problems.push(`${path} must be an object, got ${describeType2(option)}.`);
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
const type = option["type"];
|
|
1551
|
+
if (typeof type !== "string" || !WIDGET_OPTION_TYPES.includes(type)) {
|
|
1552
|
+
requireOneOf(option, "type", WIDGET_OPTION_TYPES, path, problems);
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
const key = option["key"];
|
|
1556
|
+
if (typeof key !== "string" || !OPTION_KEY_PATTERN.test(key)) {
|
|
1557
|
+
problems.push(
|
|
1558
|
+
`${path}.key must be letters, digits, "_" or "-", starting with a letter (40 characters at most), got ${typeof key === "string" ? JSON.stringify(key) : describeType2(key)}.`
|
|
1559
|
+
);
|
|
1560
|
+
} else if (seenKeys.has(key)) {
|
|
1561
|
+
problems.push(`${path}.key "${key}" is used by another option.`);
|
|
1562
|
+
} else {
|
|
1563
|
+
seenKeys.add(key);
|
|
1564
|
+
}
|
|
1565
|
+
requireString(option, "label", path, problems);
|
|
1566
|
+
const fallback = option["default"];
|
|
1567
|
+
switch (type) {
|
|
1568
|
+
case "choice": {
|
|
1569
|
+
const choices = option["choices"];
|
|
1570
|
+
const values = [];
|
|
1571
|
+
if (!Array.isArray(choices) || choices.length === 0) {
|
|
1572
|
+
problems.push(
|
|
1573
|
+
`${path}.choices must be a non-empty array of { value, label }, got ${describeType2(choices)}.`
|
|
1574
|
+
);
|
|
1575
|
+
} else {
|
|
1576
|
+
choices.forEach((choice, index) => {
|
|
1577
|
+
const choicePath = `${path}.choices[${index}]`;
|
|
1578
|
+
if (!isRecord(choice)) {
|
|
1579
|
+
problems.push(
|
|
1580
|
+
`${choicePath} must be an object, got ${describeType2(choice)}.`
|
|
1581
|
+
);
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
requireString(choice, "label", choicePath, problems);
|
|
1585
|
+
const value = choice["value"];
|
|
1586
|
+
if (typeof value !== "string" || value === "") {
|
|
1587
|
+
problems.push(
|
|
1588
|
+
`${choicePath}.value must be a non-empty string, got ${describeType2(value)}.`
|
|
1589
|
+
);
|
|
1590
|
+
} else if (values.includes(value)) {
|
|
1591
|
+
problems.push(`${choicePath}.value "${value}" is used twice.`);
|
|
1592
|
+
} else {
|
|
1593
|
+
values.push(value);
|
|
1594
|
+
}
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
if (values.length > 0)
|
|
1598
|
+
requireOneOf(option, "default", values, path, problems);
|
|
1599
|
+
break;
|
|
1600
|
+
}
|
|
1601
|
+
case "number": {
|
|
1602
|
+
const { min, max, step } = option;
|
|
1603
|
+
requireNumber(option, "min", path, problems);
|
|
1604
|
+
requireNumber(option, "max", path, problems);
|
|
1605
|
+
if (typeof min === "number" && typeof max === "number" && min > max) {
|
|
1606
|
+
problems.push(
|
|
1607
|
+
`${path}.min (${min}) must not be more than max (${max}).`
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
if (step !== void 0 && !(typeof step === "number" && Number.isFinite(step) && step > 0)) {
|
|
1611
|
+
problems.push(
|
|
1612
|
+
`${path}.step must be a number above 0, got ${describeType2(step)}.`
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
if (typeof fallback !== "number" || !Number.isFinite(fallback)) {
|
|
1616
|
+
problems.push(
|
|
1617
|
+
`${path}.default must be a finite number, got ${describeType2(fallback)}.`
|
|
1618
|
+
);
|
|
1619
|
+
} else if (typeof min === "number" && typeof max === "number" && (fallback < min || fallback > max)) {
|
|
1620
|
+
problems.push(
|
|
1621
|
+
`${path}.default (${fallback}) must be between min (${min}) and max (${max}).`
|
|
1622
|
+
);
|
|
1623
|
+
} else if (typeof min === "number" && typeof step === "number" && step > 0 && Math.abs(
|
|
1624
|
+
(fallback - min) / step - Math.round((fallback - min) / step)
|
|
1625
|
+
) > 1e-9) {
|
|
1626
|
+
problems.push(
|
|
1627
|
+
`${path}.default (${fallback}) must be ${min} plus a whole number of steps of ${step}.`
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
break;
|
|
1631
|
+
}
|
|
1632
|
+
case "boolean":
|
|
1633
|
+
if (typeof fallback !== "boolean") {
|
|
1634
|
+
problems.push(
|
|
1635
|
+
`${path}.default must be a boolean, got ${describeType2(fallback)}.`
|
|
1636
|
+
);
|
|
1637
|
+
}
|
|
1638
|
+
break;
|
|
1639
|
+
case "text": {
|
|
1640
|
+
const maxLength = option["maxLength"];
|
|
1641
|
+
if (typeof maxLength !== "number" || !Number.isInteger(maxLength) || maxLength < 1 || maxLength > MAX_TEXT_OPTION_LENGTH) {
|
|
1642
|
+
problems.push(
|
|
1643
|
+
`${path}.maxLength must be a whole number from 1 to ${MAX_TEXT_OPTION_LENGTH}, got ${typeof maxLength === "number" ? maxLength : describeType2(maxLength)}.`
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
if (typeof fallback !== "string") {
|
|
1647
|
+
problems.push(
|
|
1648
|
+
`${path}.default must be a string, got ${describeType2(fallback)}.`
|
|
1649
|
+
);
|
|
1650
|
+
} else if (typeof maxLength === "number" && fallback.length > maxLength) {
|
|
1651
|
+
problems.push(
|
|
1652
|
+
`${path}.default is ${fallback.length} characters; maxLength is ${maxLength}.`
|
|
1653
|
+
);
|
|
1654
|
+
}
|
|
1655
|
+
break;
|
|
1656
|
+
}
|
|
1657
|
+
case "dateRange": {
|
|
1658
|
+
const presets = option["presets"];
|
|
1659
|
+
const offered = [];
|
|
1660
|
+
if (presets !== void 0) {
|
|
1661
|
+
if (!Array.isArray(presets) || presets.length === 0) {
|
|
1662
|
+
problems.push(
|
|
1663
|
+
`${path}.presets must be a non-empty array of { value, label }, got ${describeType2(presets)}.`
|
|
1664
|
+
);
|
|
1665
|
+
} else {
|
|
1666
|
+
presets.forEach((preset, index) => {
|
|
1667
|
+
const presetPath = `${path}.presets[${index}]`;
|
|
1668
|
+
if (!isRecord(preset)) {
|
|
1669
|
+
problems.push(
|
|
1670
|
+
`${presetPath} must be an object, got ${describeType2(preset)}.`
|
|
1671
|
+
);
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
requireString(preset, "label", presetPath, problems);
|
|
1675
|
+
requireOneOf(
|
|
1676
|
+
preset,
|
|
1677
|
+
"value",
|
|
1678
|
+
DATE_RANGE_PRESETS,
|
|
1679
|
+
presetPath,
|
|
1680
|
+
problems
|
|
1681
|
+
);
|
|
1682
|
+
if (typeof preset["value"] === "string")
|
|
1683
|
+
offered.push(preset["value"]);
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
const allowed = offered.length > 0 ? offered : DATE_RANGE_PRESETS;
|
|
1688
|
+
if (typeof fallback !== "string" || !(allowed.includes(fallback) || isIsoInterval(fallback))) {
|
|
1689
|
+
problems.push(
|
|
1690
|
+
`${path}.default must be one of ${allowed.map((p) => `"${p}"`).join(", ")} or an interval like "2026-01-01/2026-01-31", got ${typeof fallback === "string" ? JSON.stringify(fallback) : describeType2(fallback)}.`
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
break;
|
|
1694
|
+
}
|
|
1695
|
+
case "color":
|
|
1696
|
+
if (typeof fallback !== "string" || !/^#[0-9a-fA-F]{6}$/.test(fallback)) {
|
|
1697
|
+
problems.push(
|
|
1698
|
+
`${path}.default must be a color like "#1c5cab", got ${typeof fallback === "string" ? JSON.stringify(fallback) : describeType2(fallback)}.`
|
|
1699
|
+
);
|
|
1700
|
+
}
|
|
1701
|
+
break;
|
|
1702
|
+
case "sort":
|
|
1703
|
+
case "columns": {
|
|
1704
|
+
const supported = type === "sort" ? kind === "TABLE" || kind === "BAR_LIST" : kind === "TABLE";
|
|
1705
|
+
if (!supported && typeof kind === "string") {
|
|
1706
|
+
problems.push(
|
|
1707
|
+
`${path} is a ${type} option, which only applies to ${type === "sort" ? "TABLE and BAR_LIST" : "TABLE"} widgets, but kind is "${kind}".`
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
if (seenTypes.has(type)) {
|
|
1711
|
+
problems.push(
|
|
1712
|
+
`${path} is a second ${type} option; a widget has at most one.`
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
seenTypes.add(type);
|
|
1716
|
+
const columnKeys = checkOptionColumns(
|
|
1717
|
+
option,
|
|
1718
|
+
path,
|
|
1719
|
+
supported ? kind : void 0,
|
|
1720
|
+
problems
|
|
1721
|
+
);
|
|
1722
|
+
if (option["apply"] !== void 0) {
|
|
1723
|
+
requireOneOf(option, "apply", ["provider", "client"], path, problems);
|
|
1724
|
+
}
|
|
1725
|
+
if (type === "sort") {
|
|
1726
|
+
if (fallback !== void 0) {
|
|
1727
|
+
const sort = typeof fallback === "string" ? parseSortValue(fallback) : void 0;
|
|
1728
|
+
if (sort === void 0) {
|
|
1729
|
+
problems.push(
|
|
1730
|
+
`${path}.default must look like "c1:desc" ("column:asc" or "column:desc"), got ${typeof fallback === "string" ? JSON.stringify(fallback) : describeType2(fallback)}.`
|
|
1731
|
+
);
|
|
1732
|
+
} else if (columnKeys.length > 0 && !columnKeys.includes(sort.column)) {
|
|
1733
|
+
requireOneOf(
|
|
1734
|
+
{ default: sort.column },
|
|
1735
|
+
"default",
|
|
1736
|
+
columnKeys,
|
|
1737
|
+
path,
|
|
1738
|
+
problems
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
} else if (!Array.isArray(fallback) || fallback.length === 0 || fallback.some((k) => typeof k !== "string")) {
|
|
1743
|
+
problems.push(
|
|
1744
|
+
`${path}.default must be a non-empty array of column keys, got ${describeType2(fallback)}.`
|
|
1745
|
+
);
|
|
1746
|
+
} else {
|
|
1747
|
+
const shown = /* @__PURE__ */ new Set();
|
|
1748
|
+
for (const columnKey of fallback) {
|
|
1749
|
+
if (columnKeys.length > 0 && !columnKeys.includes(columnKey)) {
|
|
1750
|
+
problems.push(
|
|
1751
|
+
`${path}.default has "${columnKey}", which is not one of the columns (${columnKeys.map((c) => `"${c}"`).join(", ")}).`
|
|
1752
|
+
);
|
|
1753
|
+
} else if (shown.has(columnKey)) {
|
|
1754
|
+
problems.push(`${path}.default lists "${columnKey}" twice.`);
|
|
1755
|
+
}
|
|
1756
|
+
shown.add(columnKey);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
break;
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
function checkOptions(options, kind, problems) {
|
|
1764
|
+
if (options === void 0) return;
|
|
1765
|
+
if (!Array.isArray(options)) {
|
|
1766
|
+
problems.push(`options must be an array, got ${describeType2(options)}.`);
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
if (options.length > MAX_WIDGET_OPTIONS) {
|
|
1770
|
+
problems.push(
|
|
1771
|
+
`options lists ${options.length} options; the most a widget may declare is ${MAX_WIDGET_OPTIONS}.`
|
|
1772
|
+
);
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
1776
|
+
const seenTypes = /* @__PURE__ */ new Set();
|
|
1777
|
+
options.forEach((option, index) => {
|
|
1778
|
+
checkOption(
|
|
1779
|
+
option,
|
|
1780
|
+
`options[${index}]`,
|
|
1781
|
+
kind,
|
|
1782
|
+
seenKeys,
|
|
1783
|
+
seenTypes,
|
|
1784
|
+
problems
|
|
1785
|
+
);
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1233
1788
|
function formatProblems(prefix, problems) {
|
|
1234
1789
|
const shown = problems.slice(0, MAX_REPORTED_PROBLEMS);
|
|
1235
1790
|
const more = problems.length - shown.length;
|
|
@@ -1304,6 +1859,7 @@ function validateWidgetDefinition(value) {
|
|
|
1304
1859
|
checkDetailOption(value["detail"], problems);
|
|
1305
1860
|
checkTableControlsOption(value["tableControls"], value["kind"], problems);
|
|
1306
1861
|
checkLockedOption(value["locked"], problems);
|
|
1862
|
+
checkOptions(value["options"], value["kind"], problems);
|
|
1307
1863
|
if (value["page"] !== void 0) {
|
|
1308
1864
|
if (typeof value["page"] !== "string" || value["page"].trim() === "") {
|
|
1309
1865
|
problems.push(
|
|
@@ -1347,12 +1903,40 @@ function failedWidget(definition, error) {
|
|
|
1347
1903
|
function loadingWidgets(definitions) {
|
|
1348
1904
|
return definitions.map((definition) => ({ definition, status: "loading" }));
|
|
1349
1905
|
}
|
|
1350
|
-
function cacheKeyFor(definition, options) {
|
|
1351
|
-
|
|
1906
|
+
function cacheKeyFor(definition, options, values) {
|
|
1907
|
+
const base = options.cacheKey === void 0 ? definition.key : options.cacheKey(definition);
|
|
1908
|
+
return values === void 0 ? base : `${base}${optionsCacheSuffix(definition, values, options)}`;
|
|
1909
|
+
}
|
|
1910
|
+
function optionValuesFor(definition, options) {
|
|
1911
|
+
return resolveOptionValues(
|
|
1912
|
+
definition,
|
|
1913
|
+
options.optionValues?.[definition.key],
|
|
1914
|
+
options
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
1917
|
+
function finalizeWidget(widget, values) {
|
|
1918
|
+
if (Object.keys(values).length === 0) return widget;
|
|
1919
|
+
if (widget.status === "ok") {
|
|
1920
|
+
const data = applyClientOptions(
|
|
1921
|
+
widget.definition,
|
|
1922
|
+
widget.data,
|
|
1923
|
+
values
|
|
1924
|
+
);
|
|
1925
|
+
return { ...widget, data, options: values };
|
|
1926
|
+
}
|
|
1927
|
+
return { ...widget, options: values };
|
|
1352
1928
|
}
|
|
1353
1929
|
var LoadInterruption = class extends Error {
|
|
1354
1930
|
};
|
|
1355
1931
|
async function resolveWidget(definition, provider, context, options = {}) {
|
|
1932
|
+
const values = optionValuesFor(definition, options);
|
|
1933
|
+
return finalizeWidget(
|
|
1934
|
+
await resolveWidgetUnapplied(definition, provider, context, options),
|
|
1935
|
+
values
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
async function resolveWidgetUnapplied(definition, provider, context, options = {}) {
|
|
1939
|
+
const values = optionValuesFor(definition, options);
|
|
1356
1940
|
const quotedKey = JSON.stringify(definition.key);
|
|
1357
1941
|
if (provider === void 0) {
|
|
1358
1942
|
return failedWidget(
|
|
@@ -1387,7 +1971,10 @@ async function resolveWidget(definition, provider, context, options = {}) {
|
|
|
1387
1971
|
payload = await Promise.race([
|
|
1388
1972
|
Promise.resolve().then(() => {
|
|
1389
1973
|
if (controller.signal.aborted) throw new LoadInterruption();
|
|
1390
|
-
return provider(context, definition, {
|
|
1974
|
+
return provider(context, definition, {
|
|
1975
|
+
signal: controller.signal,
|
|
1976
|
+
options: values
|
|
1977
|
+
});
|
|
1391
1978
|
}),
|
|
1392
1979
|
interrupted
|
|
1393
1980
|
]);
|
|
@@ -1411,7 +1998,7 @@ async function resolveWidget(definition, provider, context, options = {}) {
|
|
|
1411
1998
|
const storedAt = Date.now();
|
|
1412
1999
|
if (options.cache !== void 0) {
|
|
1413
2000
|
try {
|
|
1414
|
-
await options.cache.set(cacheKeyFor(definition, options), {
|
|
2001
|
+
await options.cache.set(cacheKeyFor(definition, options, values), {
|
|
1415
2002
|
payload: resolved.data,
|
|
1416
2003
|
storedAt
|
|
1417
2004
|
});
|
|
@@ -1441,7 +2028,17 @@ async function resolveWidgets(definitions, providers, context, options = {}) {
|
|
|
1441
2028
|
|
|
1442
2029
|
// src/core/loader.ts
|
|
1443
2030
|
function createWidgetLoader(options) {
|
|
1444
|
-
const { definitions, providers, context, ...
|
|
2031
|
+
const { definitions, providers, context, ...baseOptions } = options;
|
|
2032
|
+
const chosen = new Map(
|
|
2033
|
+
Object.entries(baseOptions.optionValues ?? {}).flatMap(
|
|
2034
|
+
([key, values]) => values === void 0 ? [] : [[key, values]]
|
|
2035
|
+
)
|
|
2036
|
+
);
|
|
2037
|
+
const resolveOptions = baseOptions;
|
|
2038
|
+
function currentOptions() {
|
|
2039
|
+
return { ...resolveOptions, optionValues: Object.fromEntries(chosen) };
|
|
2040
|
+
}
|
|
2041
|
+
const unapplied = /* @__PURE__ */ new Map();
|
|
1445
2042
|
const visible = widgetsFor(definitions, context.roles);
|
|
1446
2043
|
let snapshot = loadingWidgets(visible);
|
|
1447
2044
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -1482,19 +2079,19 @@ function createWidgetLoader(options) {
|
|
|
1482
2079
|
const controller = new AbortController();
|
|
1483
2080
|
inFlight.set(key, controller);
|
|
1484
2081
|
const isCurrent = () => !disposed && generations.get(key) === generation;
|
|
1485
|
-
|
|
2082
|
+
const runOptions = currentOptions();
|
|
2083
|
+
const values = optionValuesFor(definition, runOptions);
|
|
2084
|
+
if (readCache && runOptions.cache !== void 0) {
|
|
1486
2085
|
try {
|
|
1487
|
-
const entry = await
|
|
1488
|
-
cacheKeyFor(definition,
|
|
2086
|
+
const entry = await runOptions.cache.get(
|
|
2087
|
+
cacheKeyFor(definition, runOptions, values)
|
|
1489
2088
|
);
|
|
1490
2089
|
if (entry !== void 0 && isCurrent()) {
|
|
1491
|
-
const cached = resolvePayload(
|
|
1492
|
-
definition,
|
|
1493
|
-
entry.payload,
|
|
1494
|
-
resolveOptions
|
|
1495
|
-
);
|
|
2090
|
+
const cached = resolvePayload(definition, entry.payload, runOptions);
|
|
1496
2091
|
if (cached.status === "ok") {
|
|
1497
|
-
|
|
2092
|
+
const stale = { ...cached, updatedAt: entry.storedAt, stale: true };
|
|
2093
|
+
unapplied.set(key, stale);
|
|
2094
|
+
replace(key, finalizeWidget(stale, values));
|
|
1498
2095
|
}
|
|
1499
2096
|
}
|
|
1500
2097
|
} catch {
|
|
@@ -1502,13 +2099,15 @@ function createWidgetLoader(options) {
|
|
|
1502
2099
|
}
|
|
1503
2100
|
if (!isCurrent()) return;
|
|
1504
2101
|
const provider = Object.prototype.hasOwnProperty.call(providers, key) ? providers[key] : void 0;
|
|
1505
|
-
const result = await
|
|
1506
|
-
...
|
|
2102
|
+
const result = await resolveWidgetUnapplied(definition, provider, context, {
|
|
2103
|
+
...runOptions,
|
|
1507
2104
|
signal: controller.signal
|
|
1508
2105
|
});
|
|
1509
2106
|
if (!isCurrent()) return;
|
|
1510
2107
|
inFlight.delete(key);
|
|
1511
|
-
|
|
2108
|
+
if (result.status === "ok") unapplied.set(key, result);
|
|
2109
|
+
else unapplied.delete(key);
|
|
2110
|
+
replace(key, finalizeWidget(result, values));
|
|
1512
2111
|
}
|
|
1513
2112
|
function start(definition, readCache) {
|
|
1514
2113
|
started.add(definition.key);
|
|
@@ -1543,6 +2142,29 @@ function createWidgetLoader(options) {
|
|
|
1543
2142
|
start(definition, false);
|
|
1544
2143
|
}
|
|
1545
2144
|
}
|
|
2145
|
+
function setOptions(key, values) {
|
|
2146
|
+
if (disposed) return;
|
|
2147
|
+
const definition = definitionFor(key);
|
|
2148
|
+
if (definition === void 0) return;
|
|
2149
|
+
const before = optionValuesFor(definition, currentOptions());
|
|
2150
|
+
chosen.set(key, { ...values });
|
|
2151
|
+
const runOptions = currentOptions();
|
|
2152
|
+
const after = optionValuesFor(definition, runOptions);
|
|
2153
|
+
if (sameOptionValues(before, after) || !started.has(key)) return;
|
|
2154
|
+
const kept = unapplied.get(key);
|
|
2155
|
+
if (kept !== void 0 && optionsCacheSuffix(definition, before, runOptions) === optionsCacheSuffix(definition, after, runOptions)) {
|
|
2156
|
+
replace(key, finalizeWidget(kept, after));
|
|
2157
|
+
return;
|
|
2158
|
+
}
|
|
2159
|
+
const current = snapshot.find(
|
|
2160
|
+
(widget) => widget.definition.key === key
|
|
2161
|
+
);
|
|
2162
|
+
if (current?.status === "ok") replace(key, { ...current, stale: true });
|
|
2163
|
+
else if (current?.status === "error") {
|
|
2164
|
+
replace(key, { definition, status: "loading" });
|
|
2165
|
+
}
|
|
2166
|
+
start(definition, true);
|
|
2167
|
+
}
|
|
1546
2168
|
function dispose() {
|
|
1547
2169
|
disposed = true;
|
|
1548
2170
|
abortAll(new Error("The widget loader was disposed."));
|
|
@@ -1560,6 +2182,7 @@ function createWidgetLoader(options) {
|
|
|
1560
2182
|
load,
|
|
1561
2183
|
loadAll,
|
|
1562
2184
|
refresh,
|
|
2185
|
+
setOptions,
|
|
1563
2186
|
dispose
|
|
1564
2187
|
};
|
|
1565
2188
|
}
|
|
@@ -2063,33 +2686,594 @@ function labelStride(count, maxLabels = 6) {
|
|
|
2063
2686
|
return Math.max(1, Math.ceil(count / Math.max(1, maxLabels)));
|
|
2064
2687
|
}
|
|
2065
2688
|
|
|
2689
|
+
// src/core/storage.ts
|
|
2690
|
+
function failure(code, error) {
|
|
2691
|
+
return { ok: false, code, error };
|
|
2692
|
+
}
|
|
2693
|
+
function utf8Length(text) {
|
|
2694
|
+
let bytes = 0;
|
|
2695
|
+
for (const char of text) {
|
|
2696
|
+
const point = char.codePointAt(0) ?? 0;
|
|
2697
|
+
bytes += point < 128 ? 1 : point < 2048 ? 2 : point < 65536 ? 3 : 4;
|
|
2698
|
+
}
|
|
2699
|
+
return bytes;
|
|
2700
|
+
}
|
|
2701
|
+
function memoryAdapter(initial = {}) {
|
|
2702
|
+
const entries = /* @__PURE__ */ new Map();
|
|
2703
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
2704
|
+
let counter = 0;
|
|
2705
|
+
for (const [key, value] of Object.entries(initial)) {
|
|
2706
|
+
counter += 1;
|
|
2707
|
+
entries.set(key, { value, revision: String(counter) });
|
|
2708
|
+
}
|
|
2709
|
+
const notify = (key, stored) => {
|
|
2710
|
+
for (const listener of listeners.get(key) ?? []) listener(stored);
|
|
2711
|
+
};
|
|
2712
|
+
return {
|
|
2713
|
+
get: (key) => Promise.resolve({ ok: true, value: entries.get(key) ?? null }),
|
|
2714
|
+
set: (key, value, options) => {
|
|
2715
|
+
const current = entries.get(key);
|
|
2716
|
+
if (options.ifRevision !== void 0 && (current?.revision ?? void 0) !== options.ifRevision) {
|
|
2717
|
+
return Promise.resolve(
|
|
2718
|
+
failure(
|
|
2719
|
+
"conflict",
|
|
2720
|
+
`Could not save "${key}": it changed since revision ${options.ifRevision} (now ${current?.revision ?? "none"}).`
|
|
2721
|
+
)
|
|
2722
|
+
);
|
|
2723
|
+
}
|
|
2724
|
+
counter += 1;
|
|
2725
|
+
const stored = { value, revision: String(counter) };
|
|
2726
|
+
entries.set(key, stored);
|
|
2727
|
+
notify(key, stored);
|
|
2728
|
+
return Promise.resolve({ ok: true, value: stored });
|
|
2729
|
+
},
|
|
2730
|
+
remove: (key) => {
|
|
2731
|
+
entries.delete(key);
|
|
2732
|
+
notify(key, null);
|
|
2733
|
+
return Promise.resolve({ ok: true, value: null });
|
|
2734
|
+
},
|
|
2735
|
+
subscribe: (key, onChange) => {
|
|
2736
|
+
const set = listeners.get(key) ?? /* @__PURE__ */ new Set();
|
|
2737
|
+
set.add(onChange);
|
|
2738
|
+
listeners.set(key, set);
|
|
2739
|
+
return () => {
|
|
2740
|
+
set.delete(onChange);
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
function withPrefix(adapter, prefix) {
|
|
2746
|
+
return {
|
|
2747
|
+
get: (key, options) => adapter.get(prefix + key, options),
|
|
2748
|
+
set: (key, value, options) => adapter.set(prefix + key, value, options),
|
|
2749
|
+
...adapter.remove === void 0 ? {} : {
|
|
2750
|
+
remove: (key, options) => adapter.remove?.(prefix + key, options) ?? Promise.resolve(failure("invalid", "remove is not supported."))
|
|
2751
|
+
},
|
|
2752
|
+
...adapter.subscribe === void 0 ? {} : {
|
|
2753
|
+
subscribe: (key, onChange) => adapter.subscribe?.(prefix + key, onChange) ?? (() => void 0)
|
|
2754
|
+
}
|
|
2755
|
+
};
|
|
2756
|
+
}
|
|
2757
|
+
function withFallback(primary, secondary) {
|
|
2758
|
+
return {
|
|
2759
|
+
async get(key, options) {
|
|
2760
|
+
const first = await primary.get(key, options);
|
|
2761
|
+
if (first.ok && first.value !== null) return first;
|
|
2762
|
+
const second = await secondary.get(key, options);
|
|
2763
|
+
if (second.ok && second.value !== null) return second;
|
|
2764
|
+
return first;
|
|
2765
|
+
},
|
|
2766
|
+
async set(key, value, options) {
|
|
2767
|
+
const first = await primary.set(key, value, options);
|
|
2768
|
+
if (!first.ok && first.code === "conflict") return first;
|
|
2769
|
+
const second = await secondary.set(key, value, {
|
|
2770
|
+
signal: options.signal
|
|
2771
|
+
});
|
|
2772
|
+
if (first.ok) return first;
|
|
2773
|
+
return second.ok ? second : first;
|
|
2774
|
+
},
|
|
2775
|
+
async remove(key, options) {
|
|
2776
|
+
const first = await primary.remove?.(key, options) ?? {
|
|
2777
|
+
ok: true,
|
|
2778
|
+
value: null
|
|
2779
|
+
};
|
|
2780
|
+
await secondary.remove?.(key, options);
|
|
2781
|
+
return first;
|
|
2782
|
+
},
|
|
2783
|
+
subscribe: (key, onChange) => {
|
|
2784
|
+
const stops = [primary, secondary].flatMap((adapter) => {
|
|
2785
|
+
const stop = adapter.subscribe?.(
|
|
2786
|
+
key,
|
|
2787
|
+
onChange
|
|
2788
|
+
);
|
|
2789
|
+
return stop === void 0 ? [] : [stop];
|
|
2790
|
+
});
|
|
2791
|
+
return () => {
|
|
2792
|
+
for (const stop of stops) stop();
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
};
|
|
2796
|
+
}
|
|
2797
|
+
function withReadCache(adapter) {
|
|
2798
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2799
|
+
return {
|
|
2800
|
+
async get(key, options) {
|
|
2801
|
+
const cached = cache.get(key);
|
|
2802
|
+
if (cached !== void 0) return { ok: true, value: cached };
|
|
2803
|
+
const result = await adapter.get(key, options);
|
|
2804
|
+
if (result.ok) cache.set(key, result.value);
|
|
2805
|
+
return result;
|
|
2806
|
+
},
|
|
2807
|
+
async set(key, value, options) {
|
|
2808
|
+
const result = await adapter.set(key, value, options);
|
|
2809
|
+
if (result.ok) cache.set(key, result.value);
|
|
2810
|
+
else cache.delete(key);
|
|
2811
|
+
return result;
|
|
2812
|
+
},
|
|
2813
|
+
async remove(key, options) {
|
|
2814
|
+
cache.delete(key);
|
|
2815
|
+
return await adapter.remove?.(key, options) ?? failure(
|
|
2816
|
+
"invalid",
|
|
2817
|
+
`Could not remove "${key}": remove is not supported.`
|
|
2818
|
+
);
|
|
2819
|
+
},
|
|
2820
|
+
...adapter.subscribe === void 0 ? {} : {
|
|
2821
|
+
subscribe: (key, onChange) => adapter.subscribe?.(key, (stored) => {
|
|
2822
|
+
cache.set(key, stored);
|
|
2823
|
+
onChange(stored);
|
|
2824
|
+
}) ?? (() => void 0)
|
|
2825
|
+
}
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
function readOnly(adapter) {
|
|
2829
|
+
return {
|
|
2830
|
+
get: (key, options) => adapter.get(key, options),
|
|
2831
|
+
set: (key) => Promise.resolve(
|
|
2832
|
+
failure(
|
|
2833
|
+
"forbidden",
|
|
2834
|
+
`Could not save "${key}": this store is read-only.`
|
|
2835
|
+
)
|
|
2836
|
+
),
|
|
2837
|
+
remove: (key) => Promise.resolve(
|
|
2838
|
+
failure(
|
|
2839
|
+
"forbidden",
|
|
2840
|
+
`Could not remove "${key}": this store is read-only.`
|
|
2841
|
+
)
|
|
2842
|
+
),
|
|
2843
|
+
...adapter.subscribe === void 0 ? {} : { subscribe: adapter.subscribe.bind(adapter) }
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
function withRetry(adapter, options) {
|
|
2847
|
+
const attempts = Math.max(1, Math.floor(options.attempts ?? 3));
|
|
2848
|
+
const baseDelay = options.delayMs ?? 200;
|
|
2849
|
+
const retrying = async (signal, run) => {
|
|
2850
|
+
let result = await run();
|
|
2851
|
+
for (let attempt = 1; attempt < attempts && !result.ok && result.code === "unavailable" && !signal.aborted; attempt += 1) {
|
|
2852
|
+
await options.sleep(baseDelay * 2 ** (attempt - 1), signal);
|
|
2853
|
+
result = await run();
|
|
2854
|
+
}
|
|
2855
|
+
return result;
|
|
2856
|
+
};
|
|
2857
|
+
return {
|
|
2858
|
+
get: (key, o) => retrying(o.signal, () => adapter.get(key, o)),
|
|
2859
|
+
set: (key, value, o) => retrying(o.signal, () => adapter.set(key, value, o)),
|
|
2860
|
+
...adapter.remove === void 0 ? {} : {
|
|
2861
|
+
remove: (key, o) => retrying(
|
|
2862
|
+
o.signal,
|
|
2863
|
+
() => adapter.remove?.(key, o) ?? Promise.resolve(failure("invalid", "remove is not supported."))
|
|
2864
|
+
)
|
|
2865
|
+
},
|
|
2866
|
+
...adapter.subscribe === void 0 ? {} : { subscribe: adapter.subscribe.bind(adapter) }
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2869
|
+
function withEncoding(adapter, codec) {
|
|
2870
|
+
const decodeStored = async (key, stored) => {
|
|
2871
|
+
try {
|
|
2872
|
+
const value = await codec.decode(stored.value);
|
|
2873
|
+
return {
|
|
2874
|
+
ok: true,
|
|
2875
|
+
value: stored.revision === void 0 ? { value } : { value, revision: stored.revision }
|
|
2876
|
+
};
|
|
2877
|
+
} catch (cause) {
|
|
2878
|
+
return failure(
|
|
2879
|
+
"corrupt",
|
|
2880
|
+
`Could not decode the value stored at "${key}": ${describeUnknownError(cause)}`
|
|
2881
|
+
);
|
|
2882
|
+
}
|
|
2883
|
+
};
|
|
2884
|
+
return {
|
|
2885
|
+
async get(key, options) {
|
|
2886
|
+
const result = await adapter.get(key, options);
|
|
2887
|
+
if (!result.ok || result.value === null) return result;
|
|
2888
|
+
return decodeStored(key, result.value);
|
|
2889
|
+
},
|
|
2890
|
+
async set(key, value, options) {
|
|
2891
|
+
let encoded;
|
|
2892
|
+
try {
|
|
2893
|
+
encoded = await codec.encode(value);
|
|
2894
|
+
} catch (cause) {
|
|
2895
|
+
return failure(
|
|
2896
|
+
"invalid",
|
|
2897
|
+
`Could not encode the value for "${key}": ${describeUnknownError(cause)}`
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2900
|
+
const result = await adapter.set(key, encoded, options);
|
|
2901
|
+
if (!result.ok) return result;
|
|
2902
|
+
return result.value.revision === void 0 ? { ok: true, value: { value } } : { ok: true, value: { value, revision: result.value.revision } };
|
|
2903
|
+
},
|
|
2904
|
+
...adapter.remove === void 0 ? {} : { remove: adapter.remove.bind(adapter) },
|
|
2905
|
+
...adapter.subscribe === void 0 ? {} : {
|
|
2906
|
+
subscribe: (key, onChange) => adapter.subscribe?.(key, (stored) => {
|
|
2907
|
+
if (stored === null) {
|
|
2908
|
+
onChange(null);
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2911
|
+
void decodeStored(key, stored).then((decoded) => {
|
|
2912
|
+
if (decoded.ok) onChange(decoded.value);
|
|
2913
|
+
});
|
|
2914
|
+
}) ?? (() => void 0)
|
|
2915
|
+
}
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2918
|
+
function withLogging(adapter, log) {
|
|
2919
|
+
const report = (operation, key, result) => {
|
|
2920
|
+
try {
|
|
2921
|
+
log(
|
|
2922
|
+
result.ok ? { operation, key, ok: true } : { operation, key, ok: false, code: result.code }
|
|
2923
|
+
);
|
|
2924
|
+
} catch {
|
|
2925
|
+
}
|
|
2926
|
+
return result;
|
|
2927
|
+
};
|
|
2928
|
+
return {
|
|
2929
|
+
get: async (key, o) => report("get", key, await adapter.get(key, o)),
|
|
2930
|
+
set: async (key, value, o) => report("set", key, await adapter.set(key, value, o)),
|
|
2931
|
+
...adapter.remove === void 0 ? {} : {
|
|
2932
|
+
remove: async (key, o) => report(
|
|
2933
|
+
"remove",
|
|
2934
|
+
key,
|
|
2935
|
+
await adapter.remove?.(key, o) ?? failure("invalid", "remove is not supported.")
|
|
2936
|
+
)
|
|
2937
|
+
},
|
|
2938
|
+
...adapter.subscribe === void 0 ? {} : { subscribe: adapter.subscribe.bind(adapter) }
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
var LAYOUT_ENVELOPE_VERSION = 1;
|
|
2942
|
+
var DEFAULT_MAX_LAYOUT_BYTES = 64 * 1024;
|
|
2943
|
+
function keyPart(text) {
|
|
2944
|
+
return encodeURIComponent(text);
|
|
2945
|
+
}
|
|
2946
|
+
function readEnvelope(key, stored) {
|
|
2947
|
+
let parsed;
|
|
2948
|
+
try {
|
|
2949
|
+
parsed = JSON.parse(stored.value);
|
|
2950
|
+
} catch (cause) {
|
|
2951
|
+
return failure(
|
|
2952
|
+
"corrupt",
|
|
2953
|
+
`The layout stored at "${key}" is not valid JSON: ${describeUnknownError(cause)}`
|
|
2954
|
+
);
|
|
2955
|
+
}
|
|
2956
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2957
|
+
return failure(
|
|
2958
|
+
"corrupt",
|
|
2959
|
+
`The layout stored at "${key}" is not an object.`
|
|
2960
|
+
);
|
|
2961
|
+
}
|
|
2962
|
+
const record = parsed;
|
|
2963
|
+
const version = record["v"];
|
|
2964
|
+
if (version === void 0) {
|
|
2965
|
+
return { ok: true, value: { layout: parseLayout(parsed) } };
|
|
2966
|
+
}
|
|
2967
|
+
if (typeof version !== "number" || !Number.isInteger(version)) {
|
|
2968
|
+
return failure(
|
|
2969
|
+
"corrupt",
|
|
2970
|
+
`The layout stored at "${key}" has an invalid version ${JSON.stringify(version)}.`
|
|
2971
|
+
);
|
|
2972
|
+
}
|
|
2973
|
+
if (version > LAYOUT_ENVELOPE_VERSION) {
|
|
2974
|
+
return failure(
|
|
2975
|
+
"invalid",
|
|
2976
|
+
`The layout stored at "${key}" is version ${version}, newer than this toolkit reads (${LAYOUT_ENVELOPE_VERSION}). Update the package.`
|
|
2977
|
+
);
|
|
2978
|
+
}
|
|
2979
|
+
const envelope = record;
|
|
2980
|
+
return {
|
|
2981
|
+
ok: true,
|
|
2982
|
+
value: {
|
|
2983
|
+
layout: parseLayout(envelope.layout),
|
|
2984
|
+
...typeof envelope.savedAt === "string" ? { savedAt: envelope.savedAt } : {}
|
|
2985
|
+
}
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
function createLayoutPersistence(adapter, options = {}) {
|
|
2989
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_LAYOUT_BYTES;
|
|
2990
|
+
const policy = options.onConflict ?? "ask";
|
|
2991
|
+
const fallbackSignal = new AbortController().signal;
|
|
2992
|
+
const signal = options.signal ?? fallbackSignal;
|
|
2993
|
+
const now = options.now ?? Date.now;
|
|
2994
|
+
const revisions = /* @__PURE__ */ new Map();
|
|
2995
|
+
const lastWritten = /* @__PURE__ */ new Map();
|
|
2996
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
2997
|
+
const pending = /* @__PURE__ */ new Map();
|
|
2998
|
+
const keyFor = (scope, part = "layout") => `dwt:${keyPart(scope.dashboardKey)}:${keyPart(scope.userKey ?? "_")}:${part}`;
|
|
2999
|
+
const checkScope = (scope) => scope.dashboardKey.trim() === "" ? failure("invalid", "The storage scope needs a non-empty dashboardKey.") : { ok: true, value: null };
|
|
3000
|
+
const encode = (layout, key) => {
|
|
3001
|
+
const envelope = {
|
|
3002
|
+
v: LAYOUT_ENVELOPE_VERSION,
|
|
3003
|
+
savedAt: new Date(now()).toISOString(),
|
|
3004
|
+
layout: JSON.parse(serializeLayout(layout))
|
|
3005
|
+
};
|
|
3006
|
+
const text = JSON.stringify(envelope);
|
|
3007
|
+
const size = utf8Length(text);
|
|
3008
|
+
if (size > maxBytes) {
|
|
3009
|
+
return failure(
|
|
3010
|
+
"invalid",
|
|
3011
|
+
`Could not save "${key}": the layout is ${size} bytes, over the ${maxBytes} byte limit.`
|
|
3012
|
+
);
|
|
3013
|
+
}
|
|
3014
|
+
return { ok: true, value: text };
|
|
3015
|
+
};
|
|
3016
|
+
const guarded = async (key, action, run) => {
|
|
3017
|
+
try {
|
|
3018
|
+
return await run();
|
|
3019
|
+
} catch (cause) {
|
|
3020
|
+
return failure(
|
|
3021
|
+
"unavailable",
|
|
3022
|
+
`Could not ${action} "${key}": the adapter threw ${describeUnknownError(cause)}`
|
|
3023
|
+
);
|
|
3024
|
+
}
|
|
3025
|
+
};
|
|
3026
|
+
const readLayout = async (scope, part) => {
|
|
3027
|
+
const key = keyFor(scope, part);
|
|
3028
|
+
const result = await guarded(
|
|
3029
|
+
key,
|
|
3030
|
+
"load",
|
|
3031
|
+
() => adapter.get(key, { signal })
|
|
3032
|
+
);
|
|
3033
|
+
if (!result.ok) return result;
|
|
3034
|
+
if (result.value === null) {
|
|
3035
|
+
if (part === "layout") revisions.delete(key);
|
|
3036
|
+
return { ok: true, value: { layout: null } };
|
|
3037
|
+
}
|
|
3038
|
+
const read = readEnvelope(key, result.value);
|
|
3039
|
+
if (!read.ok) return read;
|
|
3040
|
+
if (part === "layout" && result.value.revision !== void 0) {
|
|
3041
|
+
revisions.set(key, result.value.revision);
|
|
3042
|
+
}
|
|
3043
|
+
return {
|
|
3044
|
+
ok: true,
|
|
3045
|
+
value: {
|
|
3046
|
+
layout: read.value.layout,
|
|
3047
|
+
...result.value.revision === void 0 ? {} : { revision: result.value.revision },
|
|
3048
|
+
...read.value.savedAt === void 0 ? {} : { savedAt: read.value.savedAt }
|
|
3049
|
+
}
|
|
3050
|
+
};
|
|
3051
|
+
};
|
|
3052
|
+
const repair = (loaded, definitions, normalizeOptions) => loaded.layout === null ? loaded : {
|
|
3053
|
+
...loaded,
|
|
3054
|
+
layout: normalizeLayout(definitions, loaded.layout, normalizeOptions)
|
|
3055
|
+
};
|
|
3056
|
+
const write = async (scope, layout, force) => {
|
|
3057
|
+
const key = keyFor(scope);
|
|
3058
|
+
const text = encode(layout, key);
|
|
3059
|
+
if (!text.ok) return text;
|
|
3060
|
+
const known = revisions.get(key);
|
|
3061
|
+
lastWritten.set(key, text.value);
|
|
3062
|
+
const stored = await guarded(
|
|
3063
|
+
key,
|
|
3064
|
+
"save",
|
|
3065
|
+
() => adapter.set(key, text.value, {
|
|
3066
|
+
signal,
|
|
3067
|
+
...force || known === void 0 ? {} : { ifRevision: known }
|
|
3068
|
+
})
|
|
3069
|
+
);
|
|
3070
|
+
if (stored.ok) {
|
|
3071
|
+
if (stored.value.revision !== void 0) {
|
|
3072
|
+
revisions.set(key, stored.value.revision);
|
|
3073
|
+
}
|
|
3074
|
+
return {
|
|
3075
|
+
ok: true,
|
|
3076
|
+
value: {
|
|
3077
|
+
outcome: "saved",
|
|
3078
|
+
layout,
|
|
3079
|
+
...stored.value.revision === void 0 ? {} : { revision: stored.value.revision }
|
|
3080
|
+
}
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
if (stored.code !== "conflict") return stored;
|
|
3084
|
+
const theirs = await readLayout(scope, "layout");
|
|
3085
|
+
if (!theirs.ok) return theirs;
|
|
3086
|
+
const theirLayout = theirs.value.layout ?? layout;
|
|
3087
|
+
const theirRevision = theirs.value.revision;
|
|
3088
|
+
const asResult = (outcome) => ({
|
|
3089
|
+
ok: true,
|
|
3090
|
+
value: {
|
|
3091
|
+
outcome,
|
|
3092
|
+
layout: theirLayout,
|
|
3093
|
+
...theirRevision === void 0 ? {} : { revision: theirRevision }
|
|
3094
|
+
}
|
|
3095
|
+
});
|
|
3096
|
+
if (policy === "overwrite") return write(scope, layout, true);
|
|
3097
|
+
if (policy === "keep-theirs") return asResult("kept-theirs");
|
|
3098
|
+
if (policy === "ask") return asResult("conflict");
|
|
3099
|
+
let merged;
|
|
3100
|
+
try {
|
|
3101
|
+
merged = policy(layout, theirLayout);
|
|
3102
|
+
} catch (cause) {
|
|
3103
|
+
return failure(
|
|
3104
|
+
"invalid",
|
|
3105
|
+
`Could not save "${key}": the conflict merge function threw ${describeUnknownError(cause)}`
|
|
3106
|
+
);
|
|
3107
|
+
}
|
|
3108
|
+
const second = await write(scope, merged, false);
|
|
3109
|
+
if (second.ok && second.value.outcome === "saved") return second;
|
|
3110
|
+
return second.ok ? asResult("conflict") : second;
|
|
3111
|
+
};
|
|
3112
|
+
const drain = async (scope, key, first) => {
|
|
3113
|
+
let job = first;
|
|
3114
|
+
inFlight.add(key);
|
|
3115
|
+
while (job !== void 0) {
|
|
3116
|
+
const current = job;
|
|
3117
|
+
pending.delete(key);
|
|
3118
|
+
const result = await write(
|
|
3119
|
+
scope,
|
|
3120
|
+
current.layout,
|
|
3121
|
+
current.force
|
|
3122
|
+
);
|
|
3123
|
+
for (const waiter of current.waiters) waiter(result);
|
|
3124
|
+
job = pending.get(key);
|
|
3125
|
+
}
|
|
3126
|
+
inFlight.delete(key);
|
|
3127
|
+
};
|
|
3128
|
+
return {
|
|
3129
|
+
keyFor,
|
|
3130
|
+
async load(scope, definitions, normalizeOptions) {
|
|
3131
|
+
const valid = checkScope(scope);
|
|
3132
|
+
if (!valid.ok) return valid;
|
|
3133
|
+
const read = await readLayout(scope, "layout");
|
|
3134
|
+
if (!read.ok) return read;
|
|
3135
|
+
return {
|
|
3136
|
+
ok: true,
|
|
3137
|
+
value: repair(read.value, definitions, normalizeOptions)
|
|
3138
|
+
};
|
|
3139
|
+
},
|
|
3140
|
+
save(scope, layout, saveOptions = {}) {
|
|
3141
|
+
const valid = checkScope(scope);
|
|
3142
|
+
if (!valid.ok) return Promise.resolve(valid);
|
|
3143
|
+
const key = keyFor(scope);
|
|
3144
|
+
const force = saveOptions.force === true;
|
|
3145
|
+
return new Promise((resolve) => {
|
|
3146
|
+
const queued = pending.get(key);
|
|
3147
|
+
if (queued !== void 0) {
|
|
3148
|
+
queued.layout = layout;
|
|
3149
|
+
queued.force = queued.force || force;
|
|
3150
|
+
queued.waiters.push(resolve);
|
|
3151
|
+
return;
|
|
3152
|
+
}
|
|
3153
|
+
const job = { layout, force, waiters: [resolve] };
|
|
3154
|
+
if (inFlight.has(key)) {
|
|
3155
|
+
pending.set(key, job);
|
|
3156
|
+
return;
|
|
3157
|
+
}
|
|
3158
|
+
void drain(scope, key, job);
|
|
3159
|
+
});
|
|
3160
|
+
},
|
|
3161
|
+
async loadBackup(scope) {
|
|
3162
|
+
const valid = checkScope(scope);
|
|
3163
|
+
if (!valid.ok) return valid;
|
|
3164
|
+
const read = await readLayout(scope, "backup");
|
|
3165
|
+
return read.ok ? { ok: true, value: read.value.layout } : read;
|
|
3166
|
+
},
|
|
3167
|
+
async saveBackup(scope, layout) {
|
|
3168
|
+
const valid = checkScope(scope);
|
|
3169
|
+
if (!valid.ok) return valid;
|
|
3170
|
+
const key = keyFor(scope, "backup");
|
|
3171
|
+
const text = encode(layout, key);
|
|
3172
|
+
if (!text.ok) return text;
|
|
3173
|
+
const stored = await guarded(
|
|
3174
|
+
key,
|
|
3175
|
+
"save",
|
|
3176
|
+
() => adapter.set(key, text.value, { signal })
|
|
3177
|
+
);
|
|
3178
|
+
return stored.ok ? { ok: true, value: null } : stored;
|
|
3179
|
+
},
|
|
3180
|
+
async reset(scope) {
|
|
3181
|
+
const valid = checkScope(scope);
|
|
3182
|
+
if (!valid.ok) return valid;
|
|
3183
|
+
const layoutKey = keyFor(scope);
|
|
3184
|
+
const backupKey = keyFor(scope, "backup");
|
|
3185
|
+
if (adapter.remove === void 0) {
|
|
3186
|
+
return failure(
|
|
3187
|
+
"invalid",
|
|
3188
|
+
`Could not reset "${layoutKey}": the adapter has no remove method.`
|
|
3189
|
+
);
|
|
3190
|
+
}
|
|
3191
|
+
const remove = adapter.remove.bind(adapter);
|
|
3192
|
+
const first = await guarded(
|
|
3193
|
+
layoutKey,
|
|
3194
|
+
"remove",
|
|
3195
|
+
() => remove(layoutKey, { signal })
|
|
3196
|
+
);
|
|
3197
|
+
revisions.delete(layoutKey);
|
|
3198
|
+
if (!first.ok) return first;
|
|
3199
|
+
const second = await guarded(
|
|
3200
|
+
backupKey,
|
|
3201
|
+
"remove",
|
|
3202
|
+
() => remove(backupKey, { signal })
|
|
3203
|
+
);
|
|
3204
|
+
return second.ok ? { ok: true, value: null } : second;
|
|
3205
|
+
},
|
|
3206
|
+
watch(scope, definitions, onChange, normalizeOptions) {
|
|
3207
|
+
const key = keyFor(scope);
|
|
3208
|
+
const stop = adapter.subscribe?.(
|
|
3209
|
+
key,
|
|
3210
|
+
(stored) => {
|
|
3211
|
+
if (stored === null) {
|
|
3212
|
+
revisions.delete(key);
|
|
3213
|
+
onChange({ layout: null });
|
|
3214
|
+
return;
|
|
3215
|
+
}
|
|
3216
|
+
if (stored.value === lastWritten.get(key) || stored.revision !== void 0 && stored.revision === revisions.get(key)) {
|
|
3217
|
+
return;
|
|
3218
|
+
}
|
|
3219
|
+
const read = readEnvelope(key, stored);
|
|
3220
|
+
if (!read.ok) return;
|
|
3221
|
+
onChange(
|
|
3222
|
+
repair(
|
|
3223
|
+
{
|
|
3224
|
+
layout: read.value.layout,
|
|
3225
|
+
...stored.revision === void 0 ? {} : { revision: stored.revision },
|
|
3226
|
+
...read.value.savedAt === void 0 ? {} : { savedAt: read.value.savedAt }
|
|
3227
|
+
},
|
|
3228
|
+
definitions,
|
|
3229
|
+
normalizeOptions
|
|
3230
|
+
)
|
|
3231
|
+
);
|
|
3232
|
+
}
|
|
3233
|
+
);
|
|
3234
|
+
return stop ?? (() => void 0);
|
|
3235
|
+
}
|
|
3236
|
+
};
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
exports.DATE_RANGE_PRESETS = DATE_RANGE_PRESETS;
|
|
2066
3240
|
exports.DEFAULT_COMPARISON_LABEL = DEFAULT_COMPARISON_LABEL;
|
|
2067
3241
|
exports.DEFAULT_CURRENCY = DEFAULT_CURRENCY;
|
|
2068
3242
|
exports.DEFAULT_DETAIL_PAGE_SIZE = DEFAULT_DETAIL_PAGE_SIZE;
|
|
2069
3243
|
exports.DEFAULT_EMPTY_TEXT = DEFAULT_EMPTY_TEXT;
|
|
3244
|
+
exports.DEFAULT_MAX_LAYOUT_BYTES = DEFAULT_MAX_LAYOUT_BYTES;
|
|
2070
3245
|
exports.DEFAULT_PAGE_KEY = DEFAULT_PAGE_KEY;
|
|
2071
3246
|
exports.DEFAULT_PAGE_ROWS = DEFAULT_PAGE_ROWS;
|
|
2072
3247
|
exports.DETAIL_MODES = DETAIL_MODES;
|
|
2073
3248
|
exports.EMPTY_LAYOUT = EMPTY_LAYOUT;
|
|
3249
|
+
exports.LAYOUT_ENVELOPE_VERSION = LAYOUT_ENVELOPE_VERSION;
|
|
2074
3250
|
exports.MAX_DETAIL_PAGE_SIZE = MAX_DETAIL_PAGE_SIZE;
|
|
2075
3251
|
exports.MAX_GRAPH_SERIES = MAX_GRAPH_SERIES;
|
|
2076
3252
|
exports.MAX_PAGE_ROWS = MAX_PAGE_ROWS;
|
|
2077
3253
|
exports.MAX_PAGE_TITLE_LENGTH = MAX_PAGE_TITLE_LENGTH;
|
|
2078
3254
|
exports.MAX_PARSED_PAGES = MAX_PARSED_PAGES;
|
|
2079
3255
|
exports.MAX_REPORTED_PROBLEMS = MAX_REPORTED_PROBLEMS;
|
|
3256
|
+
exports.MAX_TEXT_OPTION_LENGTH = MAX_TEXT_OPTION_LENGTH;
|
|
3257
|
+
exports.MAX_WIDGET_OPTIONS = MAX_WIDGET_OPTIONS;
|
|
2080
3258
|
exports.MAX_WIDGET_WIDTH = MAX_WIDGET_WIDTH;
|
|
2081
3259
|
exports.MIN_WIDGET_WIDTH = MIN_WIDGET_WIDTH;
|
|
3260
|
+
exports.OPTION_KEY_PATTERN = OPTION_KEY_PATTERN;
|
|
2082
3261
|
exports.VALUE_FORMATS = VALUE_FORMATS;
|
|
2083
3262
|
exports.WIDGET_FILLS = WIDGET_FILLS;
|
|
2084
3263
|
exports.WIDGET_KINDS = WIDGET_KINDS;
|
|
3264
|
+
exports.WIDGET_OPTION_TYPES = WIDGET_OPTION_TYPES;
|
|
2085
3265
|
exports.WIDGET_SIZES = WIDGET_SIZES;
|
|
2086
3266
|
exports.WIDTH_COLUMNS = WIDTH_COLUMNS;
|
|
2087
3267
|
exports.addPage = addPage;
|
|
3268
|
+
exports.applyClientOptions = applyClientOptions;
|
|
2088
3269
|
exports.assignPages = assignPages;
|
|
2089
3270
|
exports.baseColumnSpan = baseColumnSpan;
|
|
2090
3271
|
exports.cacheKeyFor = cacheKeyFor;
|
|
3272
|
+
exports.coerceOptionValue = coerceOptionValue;
|
|
3273
|
+
exports.createLayoutPersistence = createLayoutPersistence;
|
|
2091
3274
|
exports.createWidgetLoader = createWidgetLoader;
|
|
2092
3275
|
exports.defaultDetailQuery = defaultDetailQuery;
|
|
3276
|
+
exports.defaultOptionValue = defaultOptionValue;
|
|
2093
3277
|
exports.defineWidget = defineWidget;
|
|
2094
3278
|
exports.deriveDetailData = deriveDetailData;
|
|
2095
3279
|
exports.describeUnknownError = describeUnknownError;
|
|
@@ -2102,6 +3286,7 @@ exports.fillColumnSpans = fillColumnSpans;
|
|
|
2102
3286
|
exports.fillWidthSpans = fillWidthSpans;
|
|
2103
3287
|
exports.fillsHeight = fillsHeight;
|
|
2104
3288
|
exports.fillsWidth = fillsWidth;
|
|
3289
|
+
exports.finalizeWidget = finalizeWidget;
|
|
2105
3290
|
exports.fitCount = fitCount;
|
|
2106
3291
|
exports.formatCount = formatCount;
|
|
2107
3292
|
exports.formatPercentChange = formatPercentChange;
|
|
@@ -2110,7 +3295,10 @@ exports.fromLegacyDefinition = fromLegacyDefinition;
|
|
|
2110
3295
|
exports.hasPages = hasPages;
|
|
2111
3296
|
exports.hiddenWidgets = hiddenWidgets;
|
|
2112
3297
|
exports.hideWidget = hideWidget;
|
|
3298
|
+
exports.isClientApplied = isClientApplied;
|
|
3299
|
+
exports.isColumnKeyFor = isColumnKeyFor;
|
|
2113
3300
|
exports.isHidden = isHidden;
|
|
3301
|
+
exports.isIsoInterval = isIsoInterval;
|
|
2114
3302
|
exports.isMinimized = isMinimized;
|
|
2115
3303
|
exports.isSafeHref = isSafeHref;
|
|
2116
3304
|
exports.isSafeImageUrl = isSafeImageUrl;
|
|
@@ -2122,6 +3310,7 @@ exports.itemWidth = itemWidth;
|
|
|
2122
3310
|
exports.kpiDelta = kpiDelta;
|
|
2123
3311
|
exports.labelStride = labelStride;
|
|
2124
3312
|
exports.loadingWidgets = loadingWidgets;
|
|
3313
|
+
exports.memoryAdapter = memoryAdapter;
|
|
2125
3314
|
exports.minimizeWidget = minimizeWidget;
|
|
2126
3315
|
exports.movePage = movePage;
|
|
2127
3316
|
exports.moveWidget = moveWidget;
|
|
@@ -2134,6 +3323,8 @@ exports.niceDomain = niceDomain;
|
|
|
2134
3323
|
exports.niceStep = niceStep;
|
|
2135
3324
|
exports.normalizeLayout = normalizeLayout;
|
|
2136
3325
|
exports.ok = ok;
|
|
3326
|
+
exports.optionValuesFor = optionValuesFor;
|
|
3327
|
+
exports.optionsCacheSuffix = optionsCacheSuffix;
|
|
2137
3328
|
exports.pageLayout = pageLayout;
|
|
2138
3329
|
exports.pageList = pageList;
|
|
2139
3330
|
exports.pageOf = pageOf;
|
|
@@ -2141,36 +3332,50 @@ exports.pageRoom = pageRoom;
|
|
|
2141
3332
|
exports.pageWidgets = pageWidgets;
|
|
2142
3333
|
exports.parseDetailQuery = parseDetailQuery;
|
|
2143
3334
|
exports.parseLayout = parseLayout;
|
|
3335
|
+
exports.parseSortValue = parseSortValue;
|
|
2144
3336
|
exports.percentChange = percentChange;
|
|
2145
3337
|
exports.placeRows = placeRows;
|
|
2146
3338
|
exports.pruneLayout = pruneLayout;
|
|
2147
3339
|
exports.queryRows = queryRows;
|
|
3340
|
+
exports.readOnly = readOnly;
|
|
2148
3341
|
exports.removePage = removePage;
|
|
2149
3342
|
exports.renamePage = renamePage;
|
|
3343
|
+
exports.resolveDateRange = resolveDateRange;
|
|
2150
3344
|
exports.resolveDetailOptions = resolveDetailOptions;
|
|
3345
|
+
exports.resolveOptionValues = resolveOptionValues;
|
|
2151
3346
|
exports.resolvePageKey = resolvePageKey;
|
|
2152
3347
|
exports.resolvePayload = resolvePayload;
|
|
2153
3348
|
exports.resolveTableControls = resolveTableControls;
|
|
2154
3349
|
exports.resolveWidget = resolveWidget;
|
|
2155
3350
|
exports.resolveWidgetLock = resolveWidgetLock;
|
|
3351
|
+
exports.resolveWidgetUnapplied = resolveWidgetUnapplied;
|
|
2156
3352
|
exports.resolveWidgets = resolveWidgets;
|
|
2157
3353
|
exports.restoreWidget = restoreWidget;
|
|
3354
|
+
exports.sameOptionValues = sameOptionValues;
|
|
2158
3355
|
exports.serializeDetailQuery = serializeDetailQuery;
|
|
2159
3356
|
exports.serializeLayout = serializeLayout;
|
|
2160
3357
|
exports.shareOf = shareOf;
|
|
2161
3358
|
exports.showWidget = showWidget;
|
|
2162
3359
|
exports.sortDefinitions = sortDefinitions;
|
|
3360
|
+
exports.sortFromOptions = sortFromOptions;
|
|
2163
3361
|
exports.tableDetailData = tableDetailData;
|
|
2164
3362
|
exports.toggleHidden = toggleHidden;
|
|
2165
3363
|
exports.toggleMinimized = toggleMinimized;
|
|
2166
3364
|
exports.upgradeLegacyWidgetData = upgradeLegacyWidgetData;
|
|
2167
3365
|
exports.usesWidthColumns = usesWidthColumns;
|
|
3366
|
+
exports.utf8Length = utf8Length;
|
|
2168
3367
|
exports.validateDetailData = validateDetailData;
|
|
2169
3368
|
exports.validateWidgetData = validateWidgetData;
|
|
2170
3369
|
exports.validateWidgetDefinition = validateWidgetDefinition;
|
|
2171
3370
|
exports.visibleWidgets = visibleWidgets;
|
|
2172
3371
|
exports.widgetsFor = widgetsFor;
|
|
2173
3372
|
exports.widthForSize = widthForSize;
|
|
3373
|
+
exports.withEncoding = withEncoding;
|
|
3374
|
+
exports.withFallback = withFallback;
|
|
3375
|
+
exports.withLogging = withLogging;
|
|
2174
3376
|
exports.withPageLayout = withPageLayout;
|
|
3377
|
+
exports.withPrefix = withPrefix;
|
|
3378
|
+
exports.withReadCache = withReadCache;
|
|
3379
|
+
exports.withRetry = withRetry;
|
|
2175
3380
|
//# sourceMappingURL=core.cjs.map
|
|
2176
3381
|
//# sourceMappingURL=core.cjs.map
|