@marktiderman/genesis-forms 0.4.1 → 0.4.2
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 +22 -5
- package/dist/destinations.cjs.map +1 -1
- package/dist/destinations.js.map +1 -1
- package/dist/index.cjs +125 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +125 -44
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +201 -51
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +29 -2
- package/dist/server.d.ts +29 -2
- package/dist/server.js +199 -52
- package/dist/server.js.map +1 -1
- package/dist/testing.cjs +200 -50
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js +200 -50
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
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 = {
|
|
@@ -1137,8 +1181,8 @@ function rulesAllow(def, blockId, scope) {
|
|
|
1137
1181
|
}
|
|
1138
1182
|
function walkPath(def, scope) {
|
|
1139
1183
|
const list = steps(def);
|
|
1140
|
-
|
|
1141
|
-
|
|
1184
|
+
let index;
|
|
1185
|
+
let position;
|
|
1142
1186
|
const jumps = (def.logic ?? []).filter(
|
|
1143
1187
|
(r) => r.effect === "jump"
|
|
1144
1188
|
);
|
|
@@ -1162,6 +1206,7 @@ function walkPath(def, scope) {
|
|
|
1162
1206
|
ending = jump.to.ending;
|
|
1163
1207
|
break;
|
|
1164
1208
|
}
|
|
1209
|
+
index ??= new Map(list.map((b, at) => [b.id, at]));
|
|
1165
1210
|
const target = index.get(jump.to);
|
|
1166
1211
|
if (target !== void 0 && target > i) {
|
|
1167
1212
|
i = nextVisible(target);
|
|
@@ -1171,6 +1216,7 @@ function walkPath(def, scope) {
|
|
|
1171
1216
|
i = nextVisible(i + 1);
|
|
1172
1217
|
}
|
|
1173
1218
|
const scopeAt = (blockId, inclusive = false) => {
|
|
1219
|
+
position ??= new Map(def.blocks.map((b, i2) => [b.id, i2]));
|
|
1174
1220
|
const at = position.get(blockId) ?? -1;
|
|
1175
1221
|
const values = /* @__PURE__ */ Object.create(null);
|
|
1176
1222
|
for (const id of path) {
|
|
@@ -1233,10 +1279,19 @@ function validateField(field, value, options = {}) {
|
|
|
1233
1279
|
}
|
|
1234
1280
|
|
|
1235
1281
|
// src/engine.ts
|
|
1282
|
+
function readsAny(node, ids) {
|
|
1283
|
+
if (Array.isArray(node)) return node.some((n) => readsAny(n, ids));
|
|
1284
|
+
if (!node || typeof node !== "object") return false;
|
|
1285
|
+
for (const [k, v] of Object.entries(node)) {
|
|
1286
|
+
if (k === "field" && typeof v === "string" && ids.has(v)) return true;
|
|
1287
|
+
if (typeof v === "object" && readsAny(v, ids)) return true;
|
|
1288
|
+
}
|
|
1289
|
+
return false;
|
|
1290
|
+
}
|
|
1236
1291
|
function createForm(definition, options = {}) {
|
|
1237
1292
|
return build(definition, options).engine;
|
|
1238
1293
|
}
|
|
1239
|
-
function build(definition, options, replaying = false) {
|
|
1294
|
+
function build(definition, options, replaying = false, budget) {
|
|
1240
1295
|
const fields = fieldMap(definition);
|
|
1241
1296
|
const byKey = new Map([...fields.values()].map((f) => [f.key, f]));
|
|
1242
1297
|
const blocks = new Map(definition.blocks.map((b) => [b.id, b]));
|
|
@@ -1254,15 +1309,24 @@ function build(definition, options, replaying = false) {
|
|
|
1254
1309
|
if (!declared.has(name)) initialHidden[name] = value;
|
|
1255
1310
|
}
|
|
1256
1311
|
Object.assign(initialHidden, coerceHidden(definition, options.hidden).values);
|
|
1257
|
-
const calc = calculator(definition);
|
|
1312
|
+
const calc = calculator(definition, budget);
|
|
1313
|
+
const pathOf = (values, hidden) => {
|
|
1314
|
+
spend(budget, definition.blocks.length);
|
|
1315
|
+
return computePath(definition, scopeOf(values, hidden));
|
|
1316
|
+
};
|
|
1317
|
+
const logicReadsCalculated = calc.active && readsAny(definition.logic, calc.ids);
|
|
1258
1318
|
const settle = (values, hidden) => {
|
|
1259
|
-
let { path } =
|
|
1319
|
+
let { path } = pathOf(values, hidden);
|
|
1260
1320
|
if (!calc.active) return { values, path };
|
|
1321
|
+
if (!logicReadsCalculated) return { values: calc.withCalculated(values, path), path };
|
|
1261
1322
|
for (let round2 = 0; round2 < 4; round2++) {
|
|
1262
1323
|
const next = calc.withCalculated(values, path);
|
|
1263
1324
|
if (next === values) break;
|
|
1264
1325
|
values = next;
|
|
1265
|
-
|
|
1326
|
+
const again = pathOf(values, hidden).path;
|
|
1327
|
+
const same2 = again.length === path.length && again.every((id, i) => id === path[i]);
|
|
1328
|
+
path = again;
|
|
1329
|
+
if (same2) break;
|
|
1266
1330
|
}
|
|
1267
1331
|
return { values, path };
|
|
1268
1332
|
};
|
|
@@ -1304,14 +1368,30 @@ function build(definition, options, replaying = false) {
|
|
|
1304
1368
|
return errors;
|
|
1305
1369
|
};
|
|
1306
1370
|
let unsettled = false;
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
unsettled
|
|
1310
|
-
|
|
1311
|
-
|
|
1371
|
+
let formulasStale = false;
|
|
1372
|
+
const settleNow = (needValues = true) => {
|
|
1373
|
+
if (unsettled && calc.active && !logicReadsCalculated && !needValues) {
|
|
1374
|
+
unsettled = false;
|
|
1375
|
+
formulasStale = true;
|
|
1376
|
+
state = { ...state, path: pathOf(state.values, state.hidden).path };
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
if (unsettled) {
|
|
1380
|
+
unsettled = false;
|
|
1381
|
+
formulasStale = false;
|
|
1382
|
+
const { values, path } = settle(state.values, state.hidden);
|
|
1383
|
+
state = { ...state, values, path };
|
|
1384
|
+
} else if (formulasStale && needValues) {
|
|
1385
|
+
formulasStale = false;
|
|
1386
|
+
state = { ...state, values: calc.withCalculated(state.values, state.path) };
|
|
1387
|
+
}
|
|
1312
1388
|
};
|
|
1313
1389
|
const answer = (id, value, event) => {
|
|
1314
|
-
if (state.status === "completed" ||
|
|
1390
|
+
if (state.status === "completed" || isCalculated(fields.get(id))) return;
|
|
1391
|
+
if (own(state.values, id) === value) {
|
|
1392
|
+
if (value === null || typeof value !== "object") return;
|
|
1393
|
+
calc.forget(value);
|
|
1394
|
+
}
|
|
1315
1395
|
const errors = { ...state.errors };
|
|
1316
1396
|
delete errors[id];
|
|
1317
1397
|
if (batch) {
|
|
@@ -1377,7 +1457,7 @@ function build(definition, options, replaying = false) {
|
|
|
1377
1457
|
emit({ ...state, errors, current: firstError, events });
|
|
1378
1458
|
return false;
|
|
1379
1459
|
}
|
|
1380
|
-
const { ending } =
|
|
1460
|
+
const { ending } = pathOf(state.values, state.hidden);
|
|
1381
1461
|
emit({ ...state, errors: {}, status: "completed", ending, events });
|
|
1382
1462
|
return true;
|
|
1383
1463
|
},
|
|
@@ -1409,10 +1489,11 @@ function build(definition, options, replaying = false) {
|
|
|
1409
1489
|
answer(event.field, event.value, logged);
|
|
1410
1490
|
}
|
|
1411
1491
|
} else if (event.type === "next") {
|
|
1412
|
-
settleNow();
|
|
1492
|
+
settleNow(false);
|
|
1493
|
+
if (formulasStale && isCalculated(fields.get(state.current ?? ""))) settleNow(true);
|
|
1413
1494
|
engine.next();
|
|
1414
1495
|
} else if (event.type === "back") {
|
|
1415
|
-
settleNow();
|
|
1496
|
+
settleNow(false);
|
|
1416
1497
|
engine.back();
|
|
1417
1498
|
} else if (event.type === "submit") {
|
|
1418
1499
|
settleNow();
|
|
@@ -1427,9 +1508,18 @@ function build(definition, options, replaying = false) {
|
|
|
1427
1508
|
return { engine, apply, finish };
|
|
1428
1509
|
}
|
|
1429
1510
|
function replay(definition, events, options = {}) {
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1511
|
+
return replayWithin(definition, events, options);
|
|
1512
|
+
}
|
|
1513
|
+
function replayWithin(definition, events, options, maxWork) {
|
|
1514
|
+
const budget = maxWork === void 0 ? void 0 : { used: 0, max: maxWork };
|
|
1515
|
+
const { engine, apply, finish } = build(definition, { ...options, initialValues: void 0, clock: () => void 0 }, true, budget);
|
|
1516
|
+
try {
|
|
1517
|
+
for (const event of events) apply(event);
|
|
1518
|
+
finish();
|
|
1519
|
+
} finally {
|
|
1520
|
+
if (budget) budget.used;
|
|
1521
|
+
}
|
|
1522
|
+
if (budget) budget.max = Infinity;
|
|
1433
1523
|
return engine;
|
|
1434
1524
|
}
|
|
1435
1525
|
|
|
@@ -2112,6 +2202,41 @@ function parseForm(input) {
|
|
|
2112
2202
|
|
|
2113
2203
|
// src/server.ts
|
|
2114
2204
|
var MAX_EVENTS = 1e4;
|
|
2205
|
+
var MAX_SUBMISSION_VALUES = 2e5;
|
|
2206
|
+
var MAX_REPLAY_WORK = 1e6;
|
|
2207
|
+
var RAW_EVENTS_PER_EVENT = 10;
|
|
2208
|
+
function countValues(value, limit) {
|
|
2209
|
+
let count = 0;
|
|
2210
|
+
const stack = [value];
|
|
2211
|
+
while (stack.length && count <= limit) {
|
|
2212
|
+
const v = stack.pop();
|
|
2213
|
+
if (v === void 0) continue;
|
|
2214
|
+
count++;
|
|
2215
|
+
if (Array.isArray(v)) for (const item of v) stack.push(item);
|
|
2216
|
+
else if (v !== null && typeof v === "object") for (const k in v) stack.push(v[k]);
|
|
2217
|
+
}
|
|
2218
|
+
return count;
|
|
2219
|
+
}
|
|
2220
|
+
var answerTo = (e) => isPlainObject(e) && e.type === "answer" && typeof e.field === "string" ? e.field : void 0;
|
|
2221
|
+
function leanEvents(events) {
|
|
2222
|
+
const out = [];
|
|
2223
|
+
for (let i = 0; i < events.length; i++) {
|
|
2224
|
+
const field = answerTo(events[i]);
|
|
2225
|
+
if (field !== void 0 && answerTo(events[i + 1]) === field) continue;
|
|
2226
|
+
out.push(events[i]);
|
|
2227
|
+
}
|
|
2228
|
+
return out;
|
|
2229
|
+
}
|
|
2230
|
+
function submissionValues(input, limit) {
|
|
2231
|
+
let count = countValues(input.hidden, limit);
|
|
2232
|
+
if (Array.isArray(input.events)) {
|
|
2233
|
+
for (const e of input.events) {
|
|
2234
|
+
if (count > limit) break;
|
|
2235
|
+
if (answerTo(e) !== void 0) count += countValues(e.value, limit - count);
|
|
2236
|
+
}
|
|
2237
|
+
} else count += countValues(input.answers, limit - count);
|
|
2238
|
+
return count;
|
|
2239
|
+
}
|
|
2115
2240
|
var MALFORMED = { ok: false, errors: { "": "Malformed submission." } };
|
|
2116
2241
|
var parsedDefinitions = /* @__PURE__ */ new WeakMap();
|
|
2117
2242
|
function checkDefinition(definition) {
|
|
@@ -2141,7 +2266,24 @@ function wellFormed(input) {
|
|
|
2141
2266
|
function validateSubmission(definition, submission, options = {}) {
|
|
2142
2267
|
const invalid = checkDefinition(definition);
|
|
2143
2268
|
if (invalid) return { ok: false, errors: { "": invalid } };
|
|
2144
|
-
|
|
2269
|
+
if (!isPlainObject(submission)) return MALFORMED;
|
|
2270
|
+
const maxEvents = options.maxEvents ?? MAX_EVENTS;
|
|
2271
|
+
let sent = submission;
|
|
2272
|
+
if (Array.isArray(submission.events)) {
|
|
2273
|
+
const maxRaw = maxEvents * RAW_EVENTS_PER_EVENT;
|
|
2274
|
+
if (submission.events.length > maxRaw) {
|
|
2275
|
+
return { ok: false, errors: { "": `Too many events: ${submission.events.length} sent; the limit is ${maxRaw} before compaction.` } };
|
|
2276
|
+
}
|
|
2277
|
+
sent = { ...submission, events: leanEvents(submission.events) };
|
|
2278
|
+
}
|
|
2279
|
+
const maxValues = options.maxValues ?? MAX_SUBMISSION_VALUES;
|
|
2280
|
+
if (submissionValues(sent, maxValues) > maxValues) {
|
|
2281
|
+
return {
|
|
2282
|
+
ok: false,
|
|
2283
|
+
errors: { "": `Too much data: the answers hold more than ${maxValues} values (items, rows and cells); the limit is ${maxValues}.` }
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
const input = wellFormed(sent);
|
|
2145
2287
|
if (!input) return MALFORMED;
|
|
2146
2288
|
const ignored = /* @__PURE__ */ new Set();
|
|
2147
2289
|
const client = coerceHidden(definition, input.hidden, { sources: ["url", "host"] });
|
|
@@ -2160,12 +2302,20 @@ function validateSubmission(definition, submission, options = {}) {
|
|
|
2160
2302
|
let engine;
|
|
2161
2303
|
if (input.events) {
|
|
2162
2304
|
const events = compactEvents(input.events);
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
return { ok: false, errors: { "": `Too many events: ${events.length} after compaction; the limit is ${max}.` } };
|
|
2305
|
+
if (events.length > maxEvents) {
|
|
2306
|
+
return { ok: false, errors: { "": `Too many events: ${events.length} after compaction; the limit is ${maxEvents}.` } };
|
|
2166
2307
|
}
|
|
2167
2308
|
const kept = events.filter((e) => e.type !== "answer" || accept(e.field) !== false);
|
|
2168
|
-
|
|
2309
|
+
const maxWork = options.maxReplayWork ?? MAX_REPLAY_WORK;
|
|
2310
|
+
try {
|
|
2311
|
+
engine = replayWithin(definition, kept, { hidden: engineHidden, validators: options.validators }, maxWork);
|
|
2312
|
+
} catch (e) {
|
|
2313
|
+
if (!(e instanceof ReplayWorkExceeded)) throw e;
|
|
2314
|
+
return {
|
|
2315
|
+
ok: false,
|
|
2316
|
+
errors: { "": `Too much work: replaying these events (the path and calculated fields) would take more than ${maxWork} steps; the limit is ${maxWork}.` }
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2169
2319
|
} else {
|
|
2170
2320
|
const answers2 = {};
|
|
2171
2321
|
for (const id of Object.keys(input.answers ?? {})) {
|