@marktiderman/genesis-forms 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -5
- package/dist/destinations.cjs +92 -4
- package/dist/destinations.cjs.map +1 -1
- package/dist/destinations.d.cts +1 -1
- package/dist/destinations.d.ts +1 -1
- package/dist/destinations.js +92 -4
- package/dist/destinations.js.map +1 -1
- package/dist/{engine-Dz9J5ej2.d.ts → engine-BDq0XJ21.d.cts} +25 -2
- package/dist/{engine-BkNuzlna.d.cts → engine-Qzp6bX6O.d.ts} +25 -2
- package/dist/index.cjs +383 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +121 -76
- package/dist/index.d.ts +121 -76
- package/dist/index.js +360 -60
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +387 -62
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +39 -5
- package/dist/server.d.ts +39 -5
- package/dist/server.js +385 -63
- package/dist/server.js.map +1 -1
- package/dist/testing.cjs +386 -61
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +4 -4
- package/dist/testing.d.ts +4 -4
- package/dist/testing.js +386 -61
- package/dist/testing.js.map +1 -1
- package/dist/{types-BBKBQ8sp.d.cts → types-BhYUbLJ-.d.cts} +21 -1
- package/dist/{types-BBKBQ8sp.d.ts → types-BhYUbLJ-.d.ts} +21 -1
- package/dist/validate-Bs1lyoOl.d.ts +131 -0
- package/dist/validate-BsZ0YTQS.d.cts +131 -0
- package/package.json +1 -1
- package/dist/validate-BXXqi0fq.d.cts +0 -30
- package/dist/validate-BjNwF1UE.d.ts +0 -30
package/dist/testing.cjs
CHANGED
|
@@ -354,8 +354,7 @@ var round = (n, decimals) => {
|
|
|
354
354
|
const r = Number(n.toFixed(decimals));
|
|
355
355
|
return Object.is(r, -0) ? 0 : r;
|
|
356
356
|
};
|
|
357
|
-
function
|
|
358
|
-
const { format, decimals } = calculatedProps(field);
|
|
357
|
+
function valueAs(result, { format, decimals }) {
|
|
359
358
|
if (result === null || Array.isArray(result)) return void 0;
|
|
360
359
|
if (format === "number" || format === "percent") {
|
|
361
360
|
const n = formulaNumber(result);
|
|
@@ -382,19 +381,6 @@ var extract = (value, column) => column !== void 0 ? value.flatMap((row) => {
|
|
|
382
381
|
const cell = row[column];
|
|
383
382
|
return typeof cell === "string" || typeof cell === "number" ? [cell] : [];
|
|
384
383
|
}) : value.flatMap((v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean" ? [v] : []);
|
|
385
|
-
var extracted = /* @__PURE__ */ new WeakMap();
|
|
386
|
-
function cachedList(value, column) {
|
|
387
|
-
let byColumn = extracted.get(value);
|
|
388
|
-
if (!byColumn) extracted.set(value, byColumn = /* @__PURE__ */ new Map());
|
|
389
|
-
const key = column ?? "";
|
|
390
|
-
let list = byColumn.get(key);
|
|
391
|
-
if (!list) byColumn.set(key, list = ownFormulaList(extract(value, column)));
|
|
392
|
-
return list;
|
|
393
|
-
}
|
|
394
|
-
function readAnswer(value, column) {
|
|
395
|
-
if (Array.isArray(value)) return cachedList(value, column);
|
|
396
|
-
return column !== void 0 ? null : formulaInput(value);
|
|
397
|
-
}
|
|
398
384
|
function formulaInput(value, column) {
|
|
399
385
|
if (value === void 0 || value === null) return null;
|
|
400
386
|
if (Array.isArray(value) && value.length > MAX_FORMULA_ITEMS) return null;
|
|
@@ -409,9 +395,28 @@ function refNodes(node, out = []) {
|
|
|
409
395
|
else if (node.kind === "call") for (const a of node.args) refNodes(a, out);
|
|
410
396
|
return out;
|
|
411
397
|
}
|
|
398
|
+
function formulaSize(node) {
|
|
399
|
+
if (node.kind === "unary") return 1 + formulaSize(node.arg);
|
|
400
|
+
if (node.kind === "binary") return 1 + formulaSize(node.left) + formulaSize(node.right);
|
|
401
|
+
if (node.kind === "if") return 1 + formulaSize(node.test) + formulaSize(node.then) + formulaSize(node.else);
|
|
402
|
+
if (node.kind === "call") return node.args.reduce((n, a) => n + formulaSize(a), 1);
|
|
403
|
+
return 1;
|
|
404
|
+
}
|
|
405
|
+
var ReplayWorkExceeded = class extends Error {
|
|
406
|
+
constructor(limit) {
|
|
407
|
+
super(`Replaying the response would take more than ${limit} steps of work.`);
|
|
408
|
+
this.limit = limit;
|
|
409
|
+
}
|
|
410
|
+
limit;
|
|
411
|
+
};
|
|
412
|
+
function spend(budget, amount) {
|
|
413
|
+
if (!budget) return;
|
|
414
|
+
budget.used += amount;
|
|
415
|
+
if (budget.used > budget.max) throw new ReplayWorkExceeded(budget.max);
|
|
416
|
+
}
|
|
412
417
|
var UNREAD = /* @__PURE__ */ Symbol("unread");
|
|
413
418
|
var OVER = /* @__PURE__ */ Symbol("over");
|
|
414
|
-
function calculator(definition) {
|
|
419
|
+
function calculator(definition, work) {
|
|
415
420
|
const compiled = [];
|
|
416
421
|
const idByKey = /* @__PURE__ */ new Map();
|
|
417
422
|
for (const block of definition.blocks) {
|
|
@@ -420,7 +425,8 @@ function calculator(definition) {
|
|
|
420
425
|
}
|
|
421
426
|
for (const block of definition.blocks) {
|
|
422
427
|
if (block.kind !== "field" || !isCalculated(block)) continue;
|
|
423
|
-
const
|
|
428
|
+
const props = calculatedProps(block);
|
|
429
|
+
const parsed = parseFormula(props.formula);
|
|
424
430
|
const reads = [];
|
|
425
431
|
const slots = /* @__PURE__ */ new Map();
|
|
426
432
|
const byKey = /* @__PURE__ */ new Map();
|
|
@@ -434,33 +440,65 @@ function calculator(definition) {
|
|
|
434
440
|
}
|
|
435
441
|
slots.set(ref, slot);
|
|
436
442
|
}
|
|
437
|
-
compiled.push({ field: block, ast: parsed.ok ? parsed.ast : null, reads, slots, last: null });
|
|
443
|
+
compiled.push({ field: block, props, ast: parsed.ok ? parsed.ast : null, size: parsed.ok ? formulaSize(parsed.ast) : 0, reads, slots, last: null });
|
|
438
444
|
}
|
|
445
|
+
const extracted = /* @__PURE__ */ new WeakMap();
|
|
446
|
+
const cachedList = (value, column) => {
|
|
447
|
+
let byColumn = extracted.get(value);
|
|
448
|
+
if (!byColumn) extracted.set(value, byColumn = /* @__PURE__ */ new Map());
|
|
449
|
+
const key = column ?? "";
|
|
450
|
+
let list = byColumn.get(key);
|
|
451
|
+
if (!list) {
|
|
452
|
+
byColumn.set(key, list = ownFormulaList(extract(value, column)));
|
|
453
|
+
}
|
|
454
|
+
return list;
|
|
455
|
+
};
|
|
456
|
+
const readAnswer = (value, column) => {
|
|
457
|
+
if (Array.isArray(value)) return cachedList(value, column);
|
|
458
|
+
return column !== void 0 ? null : formulaInput(value);
|
|
459
|
+
};
|
|
460
|
+
const forget = (value) => {
|
|
461
|
+
if (!value || typeof value !== "object") return;
|
|
462
|
+
if (Array.isArray(value)) extracted.delete(value);
|
|
463
|
+
for (const entry of compiled) if (entry.last?.inputs.includes(value)) entry.last = null;
|
|
464
|
+
};
|
|
439
465
|
const withCalculated = (values, path) => {
|
|
440
466
|
if (!compiled.length) return values;
|
|
441
467
|
const visible = new Set(path);
|
|
442
468
|
let next = values;
|
|
469
|
+
let copied = false;
|
|
443
470
|
let budget = MAX_SETTLE_ITEMS;
|
|
444
471
|
const within = /* @__PURE__ */ new Map();
|
|
445
|
-
const input = ({ id
|
|
472
|
+
const input = ({ id }) => {
|
|
446
473
|
if (id === void 0 || !visible.has(id) || !Object.prototype.hasOwnProperty.call(next, id)) return UNREAD;
|
|
447
474
|
const value = next[id];
|
|
448
475
|
if (!Array.isArray(value)) return value;
|
|
449
|
-
let ok = within.get(
|
|
476
|
+
let ok = within.get(id);
|
|
450
477
|
if (ok === void 0) {
|
|
451
478
|
ok = value.length <= MAX_FORMULA_ITEMS && value.length <= budget;
|
|
452
479
|
if (ok) budget -= value.length;
|
|
453
|
-
within.set(
|
|
480
|
+
within.set(id, ok);
|
|
454
481
|
}
|
|
455
482
|
return ok ? value : OVER;
|
|
456
483
|
};
|
|
457
484
|
for (const entry of compiled) {
|
|
458
485
|
const { field, ast } = entry;
|
|
459
|
-
const
|
|
486
|
+
const { reads } = entry;
|
|
487
|
+
spend(work, 1);
|
|
488
|
+
const inputs = new Array(reads.length);
|
|
489
|
+
let over = false;
|
|
490
|
+
let same2 = entry.last !== null;
|
|
491
|
+
for (let i = 0; i < reads.length; i++) {
|
|
492
|
+
const v = input(reads[i]);
|
|
493
|
+
inputs[i] = v;
|
|
494
|
+
if (v === OVER) over = true;
|
|
495
|
+
if (same2 && entry.last.inputs[i] !== v) same2 = false;
|
|
496
|
+
}
|
|
460
497
|
let value;
|
|
461
|
-
if (
|
|
498
|
+
if (same2) {
|
|
462
499
|
value = entry.last.value;
|
|
463
500
|
} else {
|
|
501
|
+
spend(work, entry.size);
|
|
464
502
|
const resolved = new Array(inputs.length);
|
|
465
503
|
const read = (ref) => {
|
|
466
504
|
const slot = entry.slots.get(ref);
|
|
@@ -468,19 +506,25 @@ function calculator(definition) {
|
|
|
468
506
|
let v = resolved[slot];
|
|
469
507
|
if (v === void 0) {
|
|
470
508
|
const raw = inputs[slot];
|
|
471
|
-
v = resolved[slot] = raw === UNREAD ? null : readAnswer(raw,
|
|
509
|
+
v = resolved[slot] = raw === UNREAD ? null : readAnswer(raw, reads[slot].column);
|
|
472
510
|
}
|
|
473
511
|
return v;
|
|
474
512
|
};
|
|
475
|
-
value = ast && !
|
|
513
|
+
value = ast && !over ? valueAs(evaluateFormula(ast, read), entry.props) : void 0;
|
|
476
514
|
entry.last = { inputs, value };
|
|
477
515
|
}
|
|
478
516
|
const current = Object.prototype.hasOwnProperty.call(next, field.id) ? next[field.id] : void 0;
|
|
479
|
-
if (current !== value)
|
|
517
|
+
if (current !== value) {
|
|
518
|
+
if (!copied) {
|
|
519
|
+
next = { ...next };
|
|
520
|
+
copied = true;
|
|
521
|
+
}
|
|
522
|
+
next[field.id] = value;
|
|
523
|
+
}
|
|
480
524
|
}
|
|
481
525
|
return next;
|
|
482
526
|
};
|
|
483
|
-
return { active: compiled.length > 0, withCalculated };
|
|
527
|
+
return { active: compiled.length > 0, ids: new Set(compiled.map((c) => c.field.id)), withCalculated, forget };
|
|
484
528
|
}
|
|
485
529
|
var numberOps = ["eq", "neq", "lt", "lte", "gt", "gte", "in", "answered", "empty"];
|
|
486
530
|
var calculatedFieldType = {
|
|
@@ -677,6 +721,80 @@ var structuredFieldTypes = [
|
|
|
677
721
|
}
|
|
678
722
|
];
|
|
679
723
|
|
|
724
|
+
// src/photo.ts
|
|
725
|
+
var PHOTO_TYPE = "photo";
|
|
726
|
+
var PHOTO_FORMATS = {
|
|
727
|
+
jpeg: ["image/jpeg"],
|
|
728
|
+
png: ["image/png"],
|
|
729
|
+
heic: ["image/heic", "image/heif"],
|
|
730
|
+
webp: ["image/webp"]
|
|
731
|
+
};
|
|
732
|
+
var FORMAT_NAMES = { jpeg: "JPEG", png: "PNG", heic: "HEIC", webp: "WebP" };
|
|
733
|
+
var ALL_FORMATS = Object.keys(PHOTO_FORMATS);
|
|
734
|
+
var DEFAULT_PHOTO_FILES = 3;
|
|
735
|
+
var MAX_PHOTO_FILES = 5;
|
|
736
|
+
var DEFAULT_PHOTO_SIZE_MB = 10;
|
|
737
|
+
var MAX_PHOTO_SIZE_MB = 25;
|
|
738
|
+
var MAX_PHOTO_ID_LENGTH = 200;
|
|
739
|
+
var MAX_PHOTO_NAME_LENGTH = 255;
|
|
740
|
+
var isPhotoFormat = (v) => typeof v === "string" && Object.prototype.hasOwnProperty.call(PHOTO_FORMATS, v);
|
|
741
|
+
var wholeIn = (v, min, max) => typeof v === "number" && Number.isInteger(v) && v >= min && v <= max;
|
|
742
|
+
function photoLimits(field) {
|
|
743
|
+
const maxFiles = wholeIn(field.props?.maxFiles, 1, MAX_PHOTO_FILES) ? field.props.maxFiles : DEFAULT_PHOTO_FILES;
|
|
744
|
+
const maxSizeMb = wholeIn(field.props?.maxSizeMb, 1, MAX_PHOTO_SIZE_MB) ? field.props.maxSizeMb : DEFAULT_PHOTO_SIZE_MB;
|
|
745
|
+
const raw = field.props?.formats;
|
|
746
|
+
const picked = Array.isArray(raw) ? ALL_FORMATS.filter((f) => raw.includes(f)) : [];
|
|
747
|
+
const formats = picked.length ? picked : [...ALL_FORMATS];
|
|
748
|
+
return {
|
|
749
|
+
maxFiles,
|
|
750
|
+
maxSizeMb,
|
|
751
|
+
maxBytes: maxSizeMb * 1024 * 1024,
|
|
752
|
+
formats,
|
|
753
|
+
mimeTypes: formats.flatMap((f) => PHOTO_FORMATS[f])
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function isPhotoRef(v) {
|
|
757
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return false;
|
|
758
|
+
const r = v;
|
|
759
|
+
return typeof r.id === "string" && r.id.length > 0 && r.id.length <= MAX_PHOTO_ID_LENGTH && typeof r.name === "string" && r.name.length <= MAX_PHOTO_NAME_LENGTH && typeof r.type === "string" && typeof r.size === "number" && Number.isInteger(r.size) && r.size > 0;
|
|
760
|
+
}
|
|
761
|
+
function photoRefs(value) {
|
|
762
|
+
if (!Array.isArray(value)) return [];
|
|
763
|
+
const refs = [];
|
|
764
|
+
for (const r of value) if (isPhotoRef(r)) refs.push({ id: r.id, name: r.name, type: r.type, size: r.size });
|
|
765
|
+
return refs;
|
|
766
|
+
}
|
|
767
|
+
var plural2 = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
768
|
+
function photoFormatList(formats) {
|
|
769
|
+
const names = formats.map((f) => FORMAT_NAMES[f]);
|
|
770
|
+
return names.length > 1 ? `${names.slice(0, -1).join(", ")} or ${names[names.length - 1]}` : names[0] ?? "";
|
|
771
|
+
}
|
|
772
|
+
var photoTypeMessage = (field) => `Use a ${photoFormatList(photoLimits(field).formats)} photo.`;
|
|
773
|
+
var photoSizeMessage = (field) => `Each photo must be ${photoLimits(field).maxSizeMb} MB or smaller.`;
|
|
774
|
+
var photoCountMessage = (field) => `Add at most ${plural2(photoLimits(field).maxFiles, "photo")}.`;
|
|
775
|
+
var photoFieldType = {
|
|
776
|
+
type: PHOTO_TYPE,
|
|
777
|
+
label: "Photo",
|
|
778
|
+
category: "media",
|
|
779
|
+
value: { json: true },
|
|
780
|
+
operators: ["answered", "empty"],
|
|
781
|
+
a11y: { semantics: "group" },
|
|
782
|
+
isEmpty: (value) => value === void 0 || value === null || Array.isArray(value) && value.length === 0,
|
|
783
|
+
validate: (value, field) => {
|
|
784
|
+
if (!Array.isArray(value) || !value.every(isPhotoRef)) return "Add a photo.";
|
|
785
|
+
const limits = photoLimits(field);
|
|
786
|
+
if (value.length > limits.maxFiles) return photoCountMessage(field);
|
|
787
|
+
const refs = value;
|
|
788
|
+
if (new Set(refs.map((r) => r.id)).size !== refs.length) return "Each photo can be added once.";
|
|
789
|
+
if (refs.some((r) => !limits.mimeTypes.includes(r.type.toLowerCase()))) return photoTypeMessage(field);
|
|
790
|
+
if (refs.some((r) => r.size > limits.maxBytes)) return photoSizeMessage(field);
|
|
791
|
+
return null;
|
|
792
|
+
},
|
|
793
|
+
normalize: (value) => Array.isArray(value) && value.every(isPhotoRef) ? photoRefs(value) : value,
|
|
794
|
+
summarize: (value) => photoRefs(value).map((r) => r.name || "Photo").join(", "),
|
|
795
|
+
samples: () => [[{ id: "sample-photo", name: "photo.jpg", type: "image/jpeg", size: 1024 }]]
|
|
796
|
+
};
|
|
797
|
+
|
|
680
798
|
// src/fields.ts
|
|
681
799
|
var REGISTRY = /* @__PURE__ */ Symbol.for("@marktiderman/genesis-forms/field-types");
|
|
682
800
|
var globalScope = globalThis;
|
|
@@ -713,6 +831,20 @@ function summarizeValue(value, field) {
|
|
|
713
831
|
if (Array.isArray(value)) return value.join(", ");
|
|
714
832
|
return String(value);
|
|
715
833
|
}
|
|
834
|
+
var OTHER_MAX_LENGTH = 200;
|
|
835
|
+
function otherOption(field) {
|
|
836
|
+
const other = field.props?.other;
|
|
837
|
+
if (other === true) return { label: "Other" };
|
|
838
|
+
if (other && typeof other === "object" && !Array.isArray(other)) {
|
|
839
|
+
const label = typeof other.label === "string" && other.label.trim() ? other.label.trim() : "Other";
|
|
840
|
+
return { label };
|
|
841
|
+
}
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
function isOtherValue(field, value) {
|
|
845
|
+
return otherOption(field) !== null && typeof value === "string" && !choiceOptions(field).some((o) => o.value === value);
|
|
846
|
+
}
|
|
847
|
+
var typedOther = (field, value) => isOtherValue(field, value) && value.trim() !== "" && value.length <= OTHER_MAX_LENGTH;
|
|
716
848
|
function choiceOptions(field) {
|
|
717
849
|
const raw = field.props?.options;
|
|
718
850
|
if (!Array.isArray(raw)) return [];
|
|
@@ -720,6 +852,42 @@ function choiceOptions(field) {
|
|
|
720
852
|
(o) => o && typeof o === "object" && !Array.isArray(o) && typeof o.value === "string" ? [{ value: o.value, label: typeof o.label === "string" ? o.label : void 0 }] : []
|
|
721
853
|
);
|
|
722
854
|
}
|
|
855
|
+
function optionsSource(field) {
|
|
856
|
+
const raw = field.props?.optionsFrom;
|
|
857
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
858
|
+
return raw.source === "host" && typeof raw.key === "string" && raw.key ? { source: "host", key: raw.key } : void 0;
|
|
859
|
+
}
|
|
860
|
+
function hostList(hostOptions, key) {
|
|
861
|
+
if (!hostOptions || !Object.prototype.hasOwnProperty.call(hostOptions, key)) return [];
|
|
862
|
+
const list = hostOptions[key];
|
|
863
|
+
if (!Array.isArray(list)) return [];
|
|
864
|
+
const seen = /* @__PURE__ */ new Set();
|
|
865
|
+
const out = [];
|
|
866
|
+
for (const o of list) {
|
|
867
|
+
if (!o || typeof o !== "object" || Array.isArray(o)) continue;
|
|
868
|
+
const { value, label } = o;
|
|
869
|
+
if (typeof value !== "string" || !value || seen.has(value)) continue;
|
|
870
|
+
seen.add(value);
|
|
871
|
+
out.push(typeof label === "string" ? { value, label } : { value });
|
|
872
|
+
}
|
|
873
|
+
return out;
|
|
874
|
+
}
|
|
875
|
+
function withHostOptions(definition, hostOptions) {
|
|
876
|
+
let changed = false;
|
|
877
|
+
const blocks = definition.blocks.map((block) => {
|
|
878
|
+
if (block.kind !== "field") return block;
|
|
879
|
+
const from = optionsSource(block);
|
|
880
|
+
if (!from) return block;
|
|
881
|
+
const own2 = choiceOptions(block);
|
|
882
|
+
const taken = new Set(own2.map((o) => o.value));
|
|
883
|
+
const added = hostList(hostOptions, from.key).filter((o) => !taken.has(o.value));
|
|
884
|
+
if (!added.length) return block;
|
|
885
|
+
changed = true;
|
|
886
|
+
const options = [...own2, ...added].map((o) => o.label === void 0 ? { value: o.value } : { value: o.value, label: o.label });
|
|
887
|
+
return { ...block, props: { ...block.props, options } };
|
|
888
|
+
});
|
|
889
|
+
return changed ? { ...definition, blocks } : definition;
|
|
890
|
+
}
|
|
723
891
|
function optionLabel(field, value) {
|
|
724
892
|
const option = choiceOptions(field).find((o) => o.value === value);
|
|
725
893
|
return option?.label ?? value;
|
|
@@ -838,7 +1006,7 @@ var builtins = [
|
|
|
838
1006
|
value: { json: true },
|
|
839
1007
|
operators: ["eq", "neq", "in", "answered", "empty"],
|
|
840
1008
|
a11y: { semantics: "group" },
|
|
841
|
-
validate: (value, field) => typeof value === "string" && choiceOptions(field).some((o) => o.value === value) ? null : "Choose one of the options.",
|
|
1009
|
+
validate: (value, field) => typeof value === "string" && (choiceOptions(field).some((o) => o.value === value) || typedOther(field, value)) ? null : "Choose one of the options.",
|
|
842
1010
|
summarize: (value, field) => optionLabel(field, String(value))
|
|
843
1011
|
},
|
|
844
1012
|
{
|
|
@@ -850,7 +1018,8 @@ var builtins = [
|
|
|
850
1018
|
a11y: { semantics: "group" },
|
|
851
1019
|
validate: (value, field) => {
|
|
852
1020
|
const allowed = new Set(choiceOptions(field).map((o) => o.value));
|
|
853
|
-
|
|
1021
|
+
const others = Array.isArray(value) ? value.filter((v) => typeof v === "string" && !allowed.has(v)) : [];
|
|
1022
|
+
return Array.isArray(value) && new Set(value).size === value.length && value.every((v) => typeof v === "string") && (others.length === 0 || others.length === 1 && typedOther(field, others[0])) ? null : "Choose from the options.";
|
|
854
1023
|
},
|
|
855
1024
|
summarize: (value, field) => Array.isArray(value) ? value.map((v) => optionLabel(field, String(v))).join(", ") : String(value)
|
|
856
1025
|
},
|
|
@@ -871,7 +1040,7 @@ var builtins = [
|
|
|
871
1040
|
value: { json: true },
|
|
872
1041
|
operators: ["eq", "neq", "in", "answered", "empty"],
|
|
873
1042
|
a11y: { semantics: "control" },
|
|
874
|
-
validate: (value, field) => typeof value === "string" && choiceOptions(field).some((o) => o.value === value) ? null : "Choose one of the options.",
|
|
1043
|
+
validate: (value, field) => typeof value === "string" && (choiceOptions(field).some((o) => o.value === value) || typedOther(field, value)) ? null : "Choose one of the options.",
|
|
875
1044
|
summarize: (value, field) => optionLabel(field, String(value))
|
|
876
1045
|
},
|
|
877
1046
|
{
|
|
@@ -978,7 +1147,7 @@ function dateLimits(field) {
|
|
|
978
1147
|
if (typeof max === "string" && isIsoDate(max)) out.max = max;
|
|
979
1148
|
return out;
|
|
980
1149
|
}
|
|
981
|
-
for (const manifest of [...builtins, ...structuredFieldTypes, calculatedFieldType]) registerFieldType(manifest);
|
|
1150
|
+
for (const manifest of [...builtins, ...structuredFieldTypes, calculatedFieldType, photoFieldType]) registerFieldType(manifest);
|
|
982
1151
|
|
|
983
1152
|
// src/events.ts
|
|
984
1153
|
function isPlainObject(value) {
|
|
@@ -1137,8 +1306,8 @@ function rulesAllow(def, blockId, scope) {
|
|
|
1137
1306
|
}
|
|
1138
1307
|
function walkPath(def, scope) {
|
|
1139
1308
|
const list = steps(def);
|
|
1140
|
-
|
|
1141
|
-
|
|
1309
|
+
let index;
|
|
1310
|
+
let position;
|
|
1142
1311
|
const jumps = (def.logic ?? []).filter(
|
|
1143
1312
|
(r) => r.effect === "jump"
|
|
1144
1313
|
);
|
|
@@ -1162,6 +1331,7 @@ function walkPath(def, scope) {
|
|
|
1162
1331
|
ending = jump.to.ending;
|
|
1163
1332
|
break;
|
|
1164
1333
|
}
|
|
1334
|
+
index ??= new Map(list.map((b, at) => [b.id, at]));
|
|
1165
1335
|
const target = index.get(jump.to);
|
|
1166
1336
|
if (target !== void 0 && target > i) {
|
|
1167
1337
|
i = nextVisible(target);
|
|
@@ -1171,6 +1341,7 @@ function walkPath(def, scope) {
|
|
|
1171
1341
|
i = nextVisible(i + 1);
|
|
1172
1342
|
}
|
|
1173
1343
|
const scopeAt = (blockId, inclusive = false) => {
|
|
1344
|
+
position ??= new Map(def.blocks.map((b, i2) => [b.id, i2]));
|
|
1174
1345
|
const at = position.get(blockId) ?? -1;
|
|
1175
1346
|
const values = /* @__PURE__ */ Object.create(null);
|
|
1176
1347
|
for (const id of path) {
|
|
@@ -1233,10 +1404,20 @@ function validateField(field, value, options = {}) {
|
|
|
1233
1404
|
}
|
|
1234
1405
|
|
|
1235
1406
|
// src/engine.ts
|
|
1407
|
+
function readsAny(node, ids) {
|
|
1408
|
+
if (Array.isArray(node)) return node.some((n) => readsAny(n, ids));
|
|
1409
|
+
if (!node || typeof node !== "object") return false;
|
|
1410
|
+
for (const [k, v] of Object.entries(node)) {
|
|
1411
|
+
if (k === "field" && typeof v === "string" && ids.has(v)) return true;
|
|
1412
|
+
if (typeof v === "object" && readsAny(v, ids)) return true;
|
|
1413
|
+
}
|
|
1414
|
+
return false;
|
|
1415
|
+
}
|
|
1236
1416
|
function createForm(definition, options = {}) {
|
|
1237
1417
|
return build(definition, options).engine;
|
|
1238
1418
|
}
|
|
1239
|
-
function build(
|
|
1419
|
+
function build(given, options, replaying = false, budget) {
|
|
1420
|
+
const definition = withHostOptions(given, options.hostOptions);
|
|
1240
1421
|
const fields = fieldMap(definition);
|
|
1241
1422
|
const byKey = new Map([...fields.values()].map((f) => [f.key, f]));
|
|
1242
1423
|
const blocks = new Map(definition.blocks.map((b) => [b.id, b]));
|
|
@@ -1254,15 +1435,24 @@ function build(definition, options, replaying = false) {
|
|
|
1254
1435
|
if (!declared.has(name)) initialHidden[name] = value;
|
|
1255
1436
|
}
|
|
1256
1437
|
Object.assign(initialHidden, coerceHidden(definition, options.hidden).values);
|
|
1257
|
-
const calc = calculator(definition);
|
|
1438
|
+
const calc = calculator(definition, budget);
|
|
1439
|
+
const pathOf = (values, hidden) => {
|
|
1440
|
+
spend(budget, definition.blocks.length);
|
|
1441
|
+
return computePath(definition, scopeOf(values, hidden));
|
|
1442
|
+
};
|
|
1443
|
+
const logicReadsCalculated = calc.active && readsAny(definition.logic, calc.ids);
|
|
1258
1444
|
const settle = (values, hidden) => {
|
|
1259
|
-
let { path } =
|
|
1445
|
+
let { path } = pathOf(values, hidden);
|
|
1260
1446
|
if (!calc.active) return { values, path };
|
|
1447
|
+
if (!logicReadsCalculated) return { values: calc.withCalculated(values, path), path };
|
|
1261
1448
|
for (let round2 = 0; round2 < 4; round2++) {
|
|
1262
1449
|
const next = calc.withCalculated(values, path);
|
|
1263
1450
|
if (next === values) break;
|
|
1264
1451
|
values = next;
|
|
1265
|
-
|
|
1452
|
+
const again = pathOf(values, hidden).path;
|
|
1453
|
+
const same2 = again.length === path.length && again.every((id, i) => id === path[i]);
|
|
1454
|
+
path = again;
|
|
1455
|
+
if (same2) break;
|
|
1266
1456
|
}
|
|
1267
1457
|
return { values, path };
|
|
1268
1458
|
};
|
|
@@ -1273,6 +1463,7 @@ function build(definition, options, replaying = false) {
|
|
|
1273
1463
|
path: first.path,
|
|
1274
1464
|
current: first.path[0] ?? null,
|
|
1275
1465
|
history: [],
|
|
1466
|
+
furthest: first.path[0] ?? null,
|
|
1276
1467
|
errors: {},
|
|
1277
1468
|
status: "in_progress",
|
|
1278
1469
|
ending: null,
|
|
@@ -1304,14 +1495,30 @@ function build(definition, options, replaying = false) {
|
|
|
1304
1495
|
return errors;
|
|
1305
1496
|
};
|
|
1306
1497
|
let unsettled = false;
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
unsettled
|
|
1310
|
-
|
|
1311
|
-
|
|
1498
|
+
let formulasStale = false;
|
|
1499
|
+
const settleNow = (needValues = true) => {
|
|
1500
|
+
if (unsettled && calc.active && !logicReadsCalculated && !needValues) {
|
|
1501
|
+
unsettled = false;
|
|
1502
|
+
formulasStale = true;
|
|
1503
|
+
state = { ...state, path: pathOf(state.values, state.hidden).path };
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
if (unsettled) {
|
|
1507
|
+
unsettled = false;
|
|
1508
|
+
formulasStale = false;
|
|
1509
|
+
const { values, path } = settle(state.values, state.hidden);
|
|
1510
|
+
state = { ...state, values, path };
|
|
1511
|
+
} else if (formulasStale && needValues) {
|
|
1512
|
+
formulasStale = false;
|
|
1513
|
+
state = { ...state, values: calc.withCalculated(state.values, state.path) };
|
|
1514
|
+
}
|
|
1312
1515
|
};
|
|
1313
1516
|
const answer = (id, value, event) => {
|
|
1314
|
-
if (state.status === "completed" ||
|
|
1517
|
+
if (state.status === "completed" || isCalculated(fields.get(id))) return;
|
|
1518
|
+
if (own(state.values, id) === value) {
|
|
1519
|
+
if (value === null || typeof value !== "object") return;
|
|
1520
|
+
calc.forget(value);
|
|
1521
|
+
}
|
|
1315
1522
|
const errors = { ...state.errors };
|
|
1316
1523
|
delete errors[id];
|
|
1317
1524
|
if (batch) {
|
|
@@ -1357,7 +1564,8 @@ function build(definition, options, replaying = false) {
|
|
|
1357
1564
|
emit({ ...state, events });
|
|
1358
1565
|
return true;
|
|
1359
1566
|
}
|
|
1360
|
-
|
|
1567
|
+
const furthest = state.furthest && state.path.indexOf(state.furthest) >= state.path.indexOf(target) ? state.furthest : target;
|
|
1568
|
+
emit({ ...state, current: target, history: [...state.history, state.current], furthest, events });
|
|
1361
1569
|
return true;
|
|
1362
1570
|
},
|
|
1363
1571
|
back() {
|
|
@@ -1377,7 +1585,7 @@ function build(definition, options, replaying = false) {
|
|
|
1377
1585
|
emit({ ...state, errors, current: firstError, events });
|
|
1378
1586
|
return false;
|
|
1379
1587
|
}
|
|
1380
|
-
const { ending } =
|
|
1588
|
+
const { ending } = pathOf(state.values, state.hidden);
|
|
1381
1589
|
emit({ ...state, errors: {}, status: "completed", ending, events });
|
|
1382
1590
|
return true;
|
|
1383
1591
|
},
|
|
@@ -1402,6 +1610,15 @@ function build(definition, options, replaying = false) {
|
|
|
1402
1610
|
settleNow();
|
|
1403
1611
|
batch = wasBatch;
|
|
1404
1612
|
}
|
|
1613
|
+
const resumeAt = options.resumeAt;
|
|
1614
|
+
if (resumeAt && !replaying && state.path.includes(resumeAt)) {
|
|
1615
|
+
for (let i = 0; i < state.path.length && state.current !== resumeAt; i++) {
|
|
1616
|
+
if (!engine.next()) {
|
|
1617
|
+
state = { ...state, errors: {} };
|
|
1618
|
+
break;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1405
1622
|
const apply = (event) => {
|
|
1406
1623
|
if (event.type === "answer") {
|
|
1407
1624
|
if (fields.has(event.field)) {
|
|
@@ -1409,10 +1626,11 @@ function build(definition, options, replaying = false) {
|
|
|
1409
1626
|
answer(event.field, event.value, logged);
|
|
1410
1627
|
}
|
|
1411
1628
|
} else if (event.type === "next") {
|
|
1412
|
-
settleNow();
|
|
1629
|
+
settleNow(false);
|
|
1630
|
+
if (formulasStale && isCalculated(fields.get(state.current ?? ""))) settleNow(true);
|
|
1413
1631
|
engine.next();
|
|
1414
1632
|
} else if (event.type === "back") {
|
|
1415
|
-
settleNow();
|
|
1633
|
+
settleNow(false);
|
|
1416
1634
|
engine.back();
|
|
1417
1635
|
} else if (event.type === "submit") {
|
|
1418
1636
|
settleNow();
|
|
@@ -1427,9 +1645,18 @@ function build(definition, options, replaying = false) {
|
|
|
1427
1645
|
return { engine, apply, finish };
|
|
1428
1646
|
}
|
|
1429
1647
|
function replay(definition, events, options = {}) {
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1648
|
+
return replayWithin(definition, events, options);
|
|
1649
|
+
}
|
|
1650
|
+
function replayWithin(definition, events, options, maxWork) {
|
|
1651
|
+
const budget = maxWork === void 0 ? void 0 : { used: 0, max: maxWork };
|
|
1652
|
+
const { engine, apply, finish } = build(definition, { ...options, initialValues: void 0, clock: () => void 0 }, true, budget);
|
|
1653
|
+
try {
|
|
1654
|
+
for (const event of events) apply(event);
|
|
1655
|
+
finish();
|
|
1656
|
+
} finally {
|
|
1657
|
+
if (budget) budget.used;
|
|
1658
|
+
}
|
|
1659
|
+
if (budget) budget.max = Infinity;
|
|
1433
1660
|
return engine;
|
|
1434
1661
|
}
|
|
1435
1662
|
|
|
@@ -1761,6 +1988,22 @@ function parseForm(input) {
|
|
|
1761
1988
|
}
|
|
1762
1989
|
}
|
|
1763
1990
|
if (type === "consent") optionalText(props.text, `${path}.text`);
|
|
1991
|
+
if (type === PHOTO_TYPE) {
|
|
1992
|
+
if (props.maxFiles !== void 0 && !(isInt(props.maxFiles) && props.maxFiles >= 1 && props.maxFiles <= MAX_PHOTO_FILES)) {
|
|
1993
|
+
issue(`${path}.maxFiles`, `Must be a whole number from 1 to ${MAX_PHOTO_FILES}.`);
|
|
1994
|
+
}
|
|
1995
|
+
if (props.maxSizeMb !== void 0 && !(isInt(props.maxSizeMb) && props.maxSizeMb >= 1 && props.maxSizeMb <= MAX_PHOTO_SIZE_MB)) {
|
|
1996
|
+
issue(`${path}.maxSizeMb`, `Must be a whole number from 1 to ${MAX_PHOTO_SIZE_MB}.`);
|
|
1997
|
+
}
|
|
1998
|
+
if (props.formats !== void 0) {
|
|
1999
|
+
const formats = props.formats;
|
|
2000
|
+
if (!Array.isArray(formats) || formats.length === 0 || !formats.every(isPhotoFormat)) {
|
|
2001
|
+
issue(`${path}.formats`, "Must be a list of one or more of jpeg, png, heic and webp.");
|
|
2002
|
+
} else if (new Set(formats).size !== formats.length) {
|
|
2003
|
+
issue(`${path}.formats`, "Name each format once.");
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
1764
2007
|
};
|
|
1765
2008
|
if (!isObject2(input)) {
|
|
1766
2009
|
issue("", "A form definition must be an object.");
|
|
@@ -1781,6 +2024,7 @@ function parseForm(input) {
|
|
|
1781
2024
|
else {
|
|
1782
2025
|
optionalBoolean(input.settings.anonymous, "settings.anonymous");
|
|
1783
2026
|
optionalBoolean(input.settings.clearHiddenValues, "settings.clearHiddenValues");
|
|
2027
|
+
optionalBoolean(input.settings.saveProgress, "settings.saveProgress");
|
|
1784
2028
|
}
|
|
1785
2029
|
}
|
|
1786
2030
|
if (input.meta !== void 0 && (!isObject2(input.meta) || !isJson(input.meta))) {
|
|
@@ -1821,6 +2065,14 @@ function parseForm(input) {
|
|
|
1821
2065
|
issue(`${path}.type`, `Unknown field type "${String(block.type)}".`);
|
|
1822
2066
|
}
|
|
1823
2067
|
if (block.props !== void 0) {
|
|
2068
|
+
const from = isObject2(block.props) ? block.props.optionsFrom : void 0;
|
|
2069
|
+
if (from !== void 0) {
|
|
2070
|
+
if (!isObject2(from) || from.source !== "host" || typeof from.key !== "string" || !from.key) {
|
|
2071
|
+
issue(`${path}.props.optionsFrom`, 'Must be { source: "host", key } with a non-empty key.');
|
|
2072
|
+
} else if (typeof block.type === "string" && !CHOICE_TYPES.has(block.type)) {
|
|
2073
|
+
warnings.push({ path: `${path}.props.optionsFrom`, message: "Only single choice, multiple choice and dropdown questions take options from the app." });
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
1824
2076
|
if (!isObject2(block.props) || !isJson(block.props)) issue(`${path}.props`, "Must be a JSON object.");
|
|
1825
2077
|
else if (block.props.options !== void 0) {
|
|
1826
2078
|
const options = block.props.options;
|
|
@@ -1831,10 +2083,20 @@ function parseForm(input) {
|
|
|
1831
2083
|
if (!isObject2(o) || typeof o.value !== "string") issue(at, "Each option needs a string value.");
|
|
1832
2084
|
else optionalText(o.label, `${at}.label`);
|
|
1833
2085
|
});
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
2086
|
+
if (from === void 0) {
|
|
2087
|
+
choices.set(
|
|
2088
|
+
block.id,
|
|
2089
|
+
new Set(options.flatMap((o) => isObject2(o) && typeof o.value === "string" ? [o.value] : []))
|
|
2090
|
+
);
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
const other = isObject2(block.props) ? block.props.other : void 0;
|
|
2095
|
+
if (other !== void 0) {
|
|
2096
|
+
if (other !== true && other !== false && !(isObject2(other) && (other.label === void 0 || typeof other.label === "string"))) {
|
|
2097
|
+
issue(`${path}.props.other`, "Must be true, false or { label }.");
|
|
2098
|
+
} else if (other !== false && typeof block.type === "string" && !CHOICE_TYPES.has(block.type)) {
|
|
2099
|
+
warnings.push({ path: `${path}.props.other`, message: 'Only single choice, multiple choice and dropdown questions take an "Other" answer.' });
|
|
1838
2100
|
}
|
|
1839
2101
|
}
|
|
1840
2102
|
const { max, maxLength: maxLength2 } = isObject2(block.props) ? block.props : {};
|
|
@@ -1909,6 +2171,8 @@ function parseForm(input) {
|
|
|
1909
2171
|
} else if (ref.column !== void 0) issue(path, `"${ref.key}" is not a table, so it has no column "${ref.column}".`);
|
|
1910
2172
|
else if (targetType === "media-notes" || targetType === "text-highlights") {
|
|
1911
2173
|
warnings.push({ path, message: `"${ref.key}" holds notes, not numbers; the formula reads it as blank.` });
|
|
2174
|
+
} else if (targetType === PHOTO_TYPE) {
|
|
2175
|
+
warnings.push({ path, message: `"${ref.key}" holds photos, not numbers; the formula reads it as blank.` });
|
|
1912
2176
|
}
|
|
1913
2177
|
}
|
|
1914
2178
|
});
|
|
@@ -2112,6 +2376,41 @@ function parseForm(input) {
|
|
|
2112
2376
|
|
|
2113
2377
|
// src/server.ts
|
|
2114
2378
|
var MAX_EVENTS = 1e4;
|
|
2379
|
+
var MAX_SUBMISSION_VALUES = 2e5;
|
|
2380
|
+
var MAX_REPLAY_WORK = 1e6;
|
|
2381
|
+
var RAW_EVENTS_PER_EVENT = 10;
|
|
2382
|
+
function countValues(value, limit) {
|
|
2383
|
+
let count = 0;
|
|
2384
|
+
const stack = [value];
|
|
2385
|
+
while (stack.length && count <= limit) {
|
|
2386
|
+
const v = stack.pop();
|
|
2387
|
+
if (v === void 0) continue;
|
|
2388
|
+
count++;
|
|
2389
|
+
if (Array.isArray(v)) for (const item of v) stack.push(item);
|
|
2390
|
+
else if (v !== null && typeof v === "object") for (const k in v) stack.push(v[k]);
|
|
2391
|
+
}
|
|
2392
|
+
return count;
|
|
2393
|
+
}
|
|
2394
|
+
var answerTo = (e) => isPlainObject(e) && e.type === "answer" && typeof e.field === "string" ? e.field : void 0;
|
|
2395
|
+
function leanEvents(events) {
|
|
2396
|
+
const out = [];
|
|
2397
|
+
for (let i = 0; i < events.length; i++) {
|
|
2398
|
+
const field = answerTo(events[i]);
|
|
2399
|
+
if (field !== void 0 && answerTo(events[i + 1]) === field) continue;
|
|
2400
|
+
out.push(events[i]);
|
|
2401
|
+
}
|
|
2402
|
+
return out;
|
|
2403
|
+
}
|
|
2404
|
+
function submissionValues(input, limit) {
|
|
2405
|
+
let count = countValues(input.hidden, limit);
|
|
2406
|
+
if (Array.isArray(input.events)) {
|
|
2407
|
+
for (const e of input.events) {
|
|
2408
|
+
if (count > limit) break;
|
|
2409
|
+
if (answerTo(e) !== void 0) count += countValues(e.value, limit - count);
|
|
2410
|
+
}
|
|
2411
|
+
} else count += countValues(input.answers, limit - count);
|
|
2412
|
+
return count;
|
|
2413
|
+
}
|
|
2115
2414
|
var MALFORMED = { ok: false, errors: { "": "Malformed submission." } };
|
|
2116
2415
|
var parsedDefinitions = /* @__PURE__ */ new WeakMap();
|
|
2117
2416
|
function checkDefinition(definition) {
|
|
@@ -2138,10 +2437,28 @@ function wellFormed(input) {
|
|
|
2138
2437
|
}
|
|
2139
2438
|
return { answers, events: known, hidden };
|
|
2140
2439
|
}
|
|
2141
|
-
function validateSubmission(
|
|
2142
|
-
const invalid = checkDefinition(
|
|
2440
|
+
function validateSubmission(published, submission, options = {}) {
|
|
2441
|
+
const invalid = checkDefinition(published);
|
|
2143
2442
|
if (invalid) return { ok: false, errors: { "": invalid } };
|
|
2144
|
-
const
|
|
2443
|
+
const definition = withHostOptions(published, options.hostOptions);
|
|
2444
|
+
if (!isPlainObject(submission)) return MALFORMED;
|
|
2445
|
+
const maxEvents = options.maxEvents ?? MAX_EVENTS;
|
|
2446
|
+
let sent = submission;
|
|
2447
|
+
if (Array.isArray(submission.events)) {
|
|
2448
|
+
const maxRaw = maxEvents * RAW_EVENTS_PER_EVENT;
|
|
2449
|
+
if (submission.events.length > maxRaw) {
|
|
2450
|
+
return { ok: false, errors: { "": `Too many events: ${submission.events.length} sent; the limit is ${maxRaw} before compaction.` } };
|
|
2451
|
+
}
|
|
2452
|
+
sent = { ...submission, events: leanEvents(submission.events) };
|
|
2453
|
+
}
|
|
2454
|
+
const maxValues = options.maxValues ?? MAX_SUBMISSION_VALUES;
|
|
2455
|
+
if (submissionValues(sent, maxValues) > maxValues) {
|
|
2456
|
+
return {
|
|
2457
|
+
ok: false,
|
|
2458
|
+
errors: { "": `Too much data: the answers hold more than ${maxValues} values (items, rows and cells); the limit is ${maxValues}.` }
|
|
2459
|
+
};
|
|
2460
|
+
}
|
|
2461
|
+
const input = wellFormed(sent);
|
|
2145
2462
|
if (!input) return MALFORMED;
|
|
2146
2463
|
const ignored = /* @__PURE__ */ new Set();
|
|
2147
2464
|
const client = coerceHidden(definition, input.hidden, { sources: ["url", "host"] });
|
|
@@ -2160,12 +2477,20 @@ function validateSubmission(definition, submission, options = {}) {
|
|
|
2160
2477
|
let engine;
|
|
2161
2478
|
if (input.events) {
|
|
2162
2479
|
const events = compactEvents(input.events);
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
return { ok: false, errors: { "": `Too many events: ${events.length} after compaction; the limit is ${max}.` } };
|
|
2480
|
+
if (events.length > maxEvents) {
|
|
2481
|
+
return { ok: false, errors: { "": `Too many events: ${events.length} after compaction; the limit is ${maxEvents}.` } };
|
|
2166
2482
|
}
|
|
2167
2483
|
const kept = events.filter((e) => e.type !== "answer" || accept(e.field) !== false);
|
|
2168
|
-
|
|
2484
|
+
const maxWork = options.maxReplayWork ?? MAX_REPLAY_WORK;
|
|
2485
|
+
try {
|
|
2486
|
+
engine = replayWithin(definition, kept, { hidden: engineHidden, validators: options.validators }, maxWork);
|
|
2487
|
+
} catch (e) {
|
|
2488
|
+
if (!(e instanceof ReplayWorkExceeded)) throw e;
|
|
2489
|
+
return {
|
|
2490
|
+
ok: false,
|
|
2491
|
+
errors: { "": `Too much work: replaying these events (the path and calculated fields) would take more than ${maxWork} steps; the limit is ${maxWork}.` }
|
|
2492
|
+
};
|
|
2493
|
+
}
|
|
2169
2494
|
} else {
|
|
2170
2495
|
const answers2 = {};
|
|
2171
2496
|
for (const id of Object.keys(input.answers ?? {})) {
|