@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/index.cjs
CHANGED
|
@@ -417,7 +417,9 @@ var round = (n, decimals) => {
|
|
|
417
417
|
return Object.is(r, -0) ? 0 : r;
|
|
418
418
|
};
|
|
419
419
|
function calculatedValue(result, field2) {
|
|
420
|
-
|
|
420
|
+
return valueAs(result, calculatedProps(field2));
|
|
421
|
+
}
|
|
422
|
+
function valueAs(result, { format, decimals }) {
|
|
421
423
|
if (result === null || Array.isArray(result)) return void 0;
|
|
422
424
|
if (format === "number" || format === "percent") {
|
|
423
425
|
const n = formulaNumber(result);
|
|
@@ -444,19 +446,6 @@ var extract = (value, column) => column !== void 0 ? value.flatMap((row) => {
|
|
|
444
446
|
const cell = row[column];
|
|
445
447
|
return typeof cell === "string" || typeof cell === "number" ? [cell] : [];
|
|
446
448
|
}) : value.flatMap((v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean" ? [v] : []);
|
|
447
|
-
var extracted = /* @__PURE__ */ new WeakMap();
|
|
448
|
-
function cachedList(value, column) {
|
|
449
|
-
let byColumn = extracted.get(value);
|
|
450
|
-
if (!byColumn) extracted.set(value, byColumn = /* @__PURE__ */ new Map());
|
|
451
|
-
const key = column ?? "";
|
|
452
|
-
let list = byColumn.get(key);
|
|
453
|
-
if (!list) byColumn.set(key, list = ownFormulaList(extract(value, column)));
|
|
454
|
-
return list;
|
|
455
|
-
}
|
|
456
|
-
function readAnswer(value, column) {
|
|
457
|
-
if (Array.isArray(value)) return cachedList(value, column);
|
|
458
|
-
return column !== void 0 ? null : formulaInput(value);
|
|
459
|
-
}
|
|
460
449
|
function formulaInput(value, column) {
|
|
461
450
|
if (value === void 0 || value === null) return null;
|
|
462
451
|
if (Array.isArray(value) && value.length > MAX_FORMULA_ITEMS) return null;
|
|
@@ -475,9 +464,19 @@ function refNodes(node, out = []) {
|
|
|
475
464
|
else if (node.kind === "call") for (const a of node.args) refNodes(a, out);
|
|
476
465
|
return out;
|
|
477
466
|
}
|
|
467
|
+
function formulaSize(node) {
|
|
468
|
+
if (node.kind === "unary") return 1 + formulaSize(node.arg);
|
|
469
|
+
if (node.kind === "binary") return 1 + formulaSize(node.left) + formulaSize(node.right);
|
|
470
|
+
if (node.kind === "if") return 1 + formulaSize(node.test) + formulaSize(node.then) + formulaSize(node.else);
|
|
471
|
+
if (node.kind === "call") return node.args.reduce((n, a) => n + formulaSize(a), 1);
|
|
472
|
+
return 1;
|
|
473
|
+
}
|
|
474
|
+
function spend(budget, amount) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
478
477
|
var UNREAD = /* @__PURE__ */ Symbol("unread");
|
|
479
478
|
var OVER = /* @__PURE__ */ Symbol("over");
|
|
480
|
-
function calculator(definition) {
|
|
479
|
+
function calculator(definition, work) {
|
|
481
480
|
const compiled = [];
|
|
482
481
|
const idByKey = /* @__PURE__ */ new Map();
|
|
483
482
|
for (const block of definition.blocks) {
|
|
@@ -486,7 +485,8 @@ function calculator(definition) {
|
|
|
486
485
|
}
|
|
487
486
|
for (const block of definition.blocks) {
|
|
488
487
|
if (block.kind !== "field" || !isCalculated(block)) continue;
|
|
489
|
-
const
|
|
488
|
+
const props2 = calculatedProps(block);
|
|
489
|
+
const parsed = parseFormula(props2.formula);
|
|
490
490
|
const reads = [];
|
|
491
491
|
const slots = /* @__PURE__ */ new Map();
|
|
492
492
|
const byKey = /* @__PURE__ */ new Map();
|
|
@@ -500,33 +500,64 @@ function calculator(definition) {
|
|
|
500
500
|
}
|
|
501
501
|
slots.set(ref, slot);
|
|
502
502
|
}
|
|
503
|
-
compiled.push({ field: block, ast: parsed.ok ? parsed.ast : null, reads, slots, last: null });
|
|
503
|
+
compiled.push({ field: block, props: props2, ast: parsed.ok ? parsed.ast : null, size: parsed.ok ? formulaSize(parsed.ast) : 0, reads, slots, last: null });
|
|
504
504
|
}
|
|
505
|
+
const extracted = /* @__PURE__ */ new WeakMap();
|
|
506
|
+
const cachedList = (value, column) => {
|
|
507
|
+
let byColumn = extracted.get(value);
|
|
508
|
+
if (!byColumn) extracted.set(value, byColumn = /* @__PURE__ */ new Map());
|
|
509
|
+
const key = column ?? "";
|
|
510
|
+
let list = byColumn.get(key);
|
|
511
|
+
if (!list) {
|
|
512
|
+
byColumn.set(key, list = ownFormulaList(extract(value, column)));
|
|
513
|
+
}
|
|
514
|
+
return list;
|
|
515
|
+
};
|
|
516
|
+
const readAnswer = (value, column) => {
|
|
517
|
+
if (Array.isArray(value)) return cachedList(value, column);
|
|
518
|
+
return column !== void 0 ? null : formulaInput(value);
|
|
519
|
+
};
|
|
520
|
+
const forget = (value) => {
|
|
521
|
+
if (!value || typeof value !== "object") return;
|
|
522
|
+
if (Array.isArray(value)) extracted.delete(value);
|
|
523
|
+
for (const entry of compiled) if (entry.last?.inputs.includes(value)) entry.last = null;
|
|
524
|
+
};
|
|
505
525
|
const withCalculated = (values, path) => {
|
|
506
526
|
if (!compiled.length) return values;
|
|
507
527
|
const visible = new Set(path);
|
|
508
528
|
let next = values;
|
|
529
|
+
let copied = false;
|
|
509
530
|
let budget = MAX_SETTLE_ITEMS;
|
|
510
531
|
const within = /* @__PURE__ */ new Map();
|
|
511
|
-
const input = ({ id
|
|
532
|
+
const input = ({ id }) => {
|
|
512
533
|
if (id === void 0 || !visible.has(id) || !Object.prototype.hasOwnProperty.call(next, id)) return UNREAD;
|
|
513
534
|
const value = next[id];
|
|
514
535
|
if (!Array.isArray(value)) return value;
|
|
515
|
-
let ok = within.get(
|
|
536
|
+
let ok = within.get(id);
|
|
516
537
|
if (ok === void 0) {
|
|
517
538
|
ok = value.length <= MAX_FORMULA_ITEMS && value.length <= budget;
|
|
518
539
|
if (ok) budget -= value.length;
|
|
519
|
-
within.set(
|
|
540
|
+
within.set(id, ok);
|
|
520
541
|
}
|
|
521
542
|
return ok ? value : OVER;
|
|
522
543
|
};
|
|
523
544
|
for (const entry of compiled) {
|
|
524
545
|
const { field: field2, ast } = entry;
|
|
525
|
-
const
|
|
546
|
+
const { reads } = entry;
|
|
547
|
+
const inputs = new Array(reads.length);
|
|
548
|
+
let over = false;
|
|
549
|
+
let same = entry.last !== null;
|
|
550
|
+
for (let i = 0; i < reads.length; i++) {
|
|
551
|
+
const v = input(reads[i]);
|
|
552
|
+
inputs[i] = v;
|
|
553
|
+
if (v === OVER) over = true;
|
|
554
|
+
if (same && entry.last.inputs[i] !== v) same = false;
|
|
555
|
+
}
|
|
526
556
|
let value;
|
|
527
|
-
if (
|
|
557
|
+
if (same) {
|
|
528
558
|
value = entry.last.value;
|
|
529
559
|
} else {
|
|
560
|
+
spend(work, entry.size);
|
|
530
561
|
const resolved = new Array(inputs.length);
|
|
531
562
|
const read = (ref) => {
|
|
532
563
|
const slot = entry.slots.get(ref);
|
|
@@ -534,19 +565,25 @@ function calculator(definition) {
|
|
|
534
565
|
let v = resolved[slot];
|
|
535
566
|
if (v === void 0) {
|
|
536
567
|
const raw = inputs[slot];
|
|
537
|
-
v = resolved[slot] = raw === UNREAD ? null : readAnswer(raw,
|
|
568
|
+
v = resolved[slot] = raw === UNREAD ? null : readAnswer(raw, reads[slot].column);
|
|
538
569
|
}
|
|
539
570
|
return v;
|
|
540
571
|
};
|
|
541
|
-
value = ast && !
|
|
572
|
+
value = ast && !over ? valueAs(evaluateFormula(ast, read), entry.props) : void 0;
|
|
542
573
|
entry.last = { inputs, value };
|
|
543
574
|
}
|
|
544
575
|
const current = Object.prototype.hasOwnProperty.call(next, field2.id) ? next[field2.id] : void 0;
|
|
545
|
-
if (current !== value)
|
|
576
|
+
if (current !== value) {
|
|
577
|
+
if (!copied) {
|
|
578
|
+
next = { ...next };
|
|
579
|
+
copied = true;
|
|
580
|
+
}
|
|
581
|
+
next[field2.id] = value;
|
|
582
|
+
}
|
|
546
583
|
}
|
|
547
584
|
return next;
|
|
548
585
|
};
|
|
549
|
-
return { active: compiled.length > 0, withCalculated };
|
|
586
|
+
return { active: compiled.length > 0, ids: new Set(compiled.map((c) => c.field.id)), withCalculated, forget };
|
|
550
587
|
}
|
|
551
588
|
var numberOps = ["eq", "neq", "lt", "lte", "gt", "gte", "in", "answered", "empty"];
|
|
552
589
|
var calculatedFieldType = {
|
|
@@ -1181,8 +1218,8 @@ function rulesAllow(def, blockId, scope) {
|
|
|
1181
1218
|
}
|
|
1182
1219
|
function walkPath(def, scope) {
|
|
1183
1220
|
const list = steps(def);
|
|
1184
|
-
|
|
1185
|
-
|
|
1221
|
+
let index;
|
|
1222
|
+
let position;
|
|
1186
1223
|
const jumps = (def.logic ?? []).filter(
|
|
1187
1224
|
(r) => r.effect === "jump"
|
|
1188
1225
|
);
|
|
@@ -1206,6 +1243,7 @@ function walkPath(def, scope) {
|
|
|
1206
1243
|
ending = jump.to.ending;
|
|
1207
1244
|
break;
|
|
1208
1245
|
}
|
|
1246
|
+
index ??= new Map(list.map((b, at) => [b.id, at]));
|
|
1209
1247
|
const target = index.get(jump.to);
|
|
1210
1248
|
if (target !== void 0 && target > i) {
|
|
1211
1249
|
i = nextVisible(target);
|
|
@@ -1215,6 +1253,7 @@ function walkPath(def, scope) {
|
|
|
1215
1253
|
i = nextVisible(i + 1);
|
|
1216
1254
|
}
|
|
1217
1255
|
const scopeAt = (blockId, inclusive = false) => {
|
|
1256
|
+
position ??= new Map(def.blocks.map((b, i2) => [b.id, i2]));
|
|
1218
1257
|
const at = position.get(blockId) ?? -1;
|
|
1219
1258
|
const values = /* @__PURE__ */ Object.create(null);
|
|
1220
1259
|
for (const id of path) {
|
|
@@ -2026,10 +2065,19 @@ function validateField(field2, value, options = {}) {
|
|
|
2026
2065
|
}
|
|
2027
2066
|
|
|
2028
2067
|
// src/engine.ts
|
|
2068
|
+
function readsAny(node, ids) {
|
|
2069
|
+
if (Array.isArray(node)) return node.some((n) => readsAny(n, ids));
|
|
2070
|
+
if (!node || typeof node !== "object") return false;
|
|
2071
|
+
for (const [k, v] of Object.entries(node)) {
|
|
2072
|
+
if (k === "field" && typeof v === "string" && ids.has(v)) return true;
|
|
2073
|
+
if (typeof v === "object" && readsAny(v, ids)) return true;
|
|
2074
|
+
}
|
|
2075
|
+
return false;
|
|
2076
|
+
}
|
|
2029
2077
|
function createForm(definition, options = {}) {
|
|
2030
2078
|
return build(definition, options).engine;
|
|
2031
2079
|
}
|
|
2032
|
-
function build(definition, options, replaying = false) {
|
|
2080
|
+
function build(definition, options, replaying = false, budget) {
|
|
2033
2081
|
const fields = fieldMap(definition);
|
|
2034
2082
|
const byKey = new Map([...fields.values()].map((f2) => [f2.key, f2]));
|
|
2035
2083
|
const blocks = new Map(definition.blocks.map((b) => [b.id, b]));
|
|
@@ -2047,15 +2095,24 @@ function build(definition, options, replaying = false) {
|
|
|
2047
2095
|
if (!declared.has(name)) initialHidden[name] = value;
|
|
2048
2096
|
}
|
|
2049
2097
|
Object.assign(initialHidden, coerceHidden(definition, options.hidden).values);
|
|
2050
|
-
const calc = calculator(definition);
|
|
2098
|
+
const calc = calculator(definition, budget);
|
|
2099
|
+
const pathOf = (values, hidden) => {
|
|
2100
|
+
spend(budget, definition.blocks.length);
|
|
2101
|
+
return computePath(definition, scopeOf(values, hidden));
|
|
2102
|
+
};
|
|
2103
|
+
const logicReadsCalculated = calc.active && readsAny(definition.logic, calc.ids);
|
|
2051
2104
|
const settle = (values, hidden) => {
|
|
2052
|
-
let { path } =
|
|
2105
|
+
let { path } = pathOf(values, hidden);
|
|
2053
2106
|
if (!calc.active) return { values, path };
|
|
2107
|
+
if (!logicReadsCalculated) return { values: calc.withCalculated(values, path), path };
|
|
2054
2108
|
for (let round2 = 0; round2 < 4; round2++) {
|
|
2055
2109
|
const next = calc.withCalculated(values, path);
|
|
2056
2110
|
if (next === values) break;
|
|
2057
2111
|
values = next;
|
|
2058
|
-
|
|
2112
|
+
const again = pathOf(values, hidden).path;
|
|
2113
|
+
const same = again.length === path.length && again.every((id, i) => id === path[i]);
|
|
2114
|
+
path = again;
|
|
2115
|
+
if (same) break;
|
|
2059
2116
|
}
|
|
2060
2117
|
return { values, path };
|
|
2061
2118
|
};
|
|
@@ -2097,14 +2154,30 @@ function build(definition, options, replaying = false) {
|
|
|
2097
2154
|
return errors;
|
|
2098
2155
|
};
|
|
2099
2156
|
let unsettled = false;
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
unsettled
|
|
2103
|
-
|
|
2104
|
-
|
|
2157
|
+
let formulasStale = false;
|
|
2158
|
+
const settleNow = (needValues = true) => {
|
|
2159
|
+
if (unsettled && calc.active && !logicReadsCalculated && !needValues) {
|
|
2160
|
+
unsettled = false;
|
|
2161
|
+
formulasStale = true;
|
|
2162
|
+
state = { ...state, path: pathOf(state.values, state.hidden).path };
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2165
|
+
if (unsettled) {
|
|
2166
|
+
unsettled = false;
|
|
2167
|
+
formulasStale = false;
|
|
2168
|
+
const { values, path } = settle(state.values, state.hidden);
|
|
2169
|
+
state = { ...state, values, path };
|
|
2170
|
+
} else if (formulasStale && needValues) {
|
|
2171
|
+
formulasStale = false;
|
|
2172
|
+
state = { ...state, values: calc.withCalculated(state.values, state.path) };
|
|
2173
|
+
}
|
|
2105
2174
|
};
|
|
2106
2175
|
const answer = (id, value, event) => {
|
|
2107
|
-
if (state.status === "completed" ||
|
|
2176
|
+
if (state.status === "completed" || isCalculated(fields.get(id))) return;
|
|
2177
|
+
if (own(state.values, id) === value) {
|
|
2178
|
+
if (value === null || typeof value !== "object") return;
|
|
2179
|
+
calc.forget(value);
|
|
2180
|
+
}
|
|
2108
2181
|
const errors = { ...state.errors };
|
|
2109
2182
|
delete errors[id];
|
|
2110
2183
|
if (batch) {
|
|
@@ -2170,7 +2243,7 @@ function build(definition, options, replaying = false) {
|
|
|
2170
2243
|
emit({ ...state, errors, current: firstError, events });
|
|
2171
2244
|
return false;
|
|
2172
2245
|
}
|
|
2173
|
-
const { ending } =
|
|
2246
|
+
const { ending } = pathOf(state.values, state.hidden);
|
|
2174
2247
|
emit({ ...state, errors: {}, status: "completed", ending, events });
|
|
2175
2248
|
return true;
|
|
2176
2249
|
},
|
|
@@ -2202,10 +2275,11 @@ function build(definition, options, replaying = false) {
|
|
|
2202
2275
|
answer(event.field, event.value, logged);
|
|
2203
2276
|
}
|
|
2204
2277
|
} else if (event.type === "next") {
|
|
2205
|
-
settleNow();
|
|
2278
|
+
settleNow(false);
|
|
2279
|
+
if (formulasStale && isCalculated(fields.get(state.current ?? ""))) settleNow(true);
|
|
2206
2280
|
engine.next();
|
|
2207
2281
|
} else if (event.type === "back") {
|
|
2208
|
-
settleNow();
|
|
2282
|
+
settleNow(false);
|
|
2209
2283
|
engine.back();
|
|
2210
2284
|
} else if (event.type === "submit") {
|
|
2211
2285
|
settleNow();
|
|
@@ -2220,9 +2294,16 @@ function build(definition, options, replaying = false) {
|
|
|
2220
2294
|
return { engine, apply, finish };
|
|
2221
2295
|
}
|
|
2222
2296
|
function replay(definition, events, options = {}) {
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2297
|
+
return replayWithin(definition, events, options);
|
|
2298
|
+
}
|
|
2299
|
+
function replayWithin(definition, events, options, maxWork) {
|
|
2300
|
+
const budget = void 0 ;
|
|
2301
|
+
const { engine, apply, finish } = build(definition, { ...options, initialValues: void 0, clock: () => void 0 }, true, budget);
|
|
2302
|
+
try {
|
|
2303
|
+
for (const event of events) apply(event);
|
|
2304
|
+
finish();
|
|
2305
|
+
} finally {
|
|
2306
|
+
}
|
|
2226
2307
|
return engine;
|
|
2227
2308
|
}
|
|
2228
2309
|
|