@marktiderman/genesis-forms 0.4.0 → 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 +31 -4
- package/dist/destinations.cjs.map +1 -1
- package/dist/destinations.js.map +1 -1
- package/dist/index.cjs +230 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +229 -46
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +303 -55
- 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 +301 -56
- package/dist/server.js.map +1 -1
- package/dist/testing.cjs +302 -54
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js +302 -54
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -553,6 +553,8 @@ declare const CALCULATED_TYPE = "calculated";
|
|
|
553
553
|
*/
|
|
554
554
|
declare const COMPUTED_FIELD_TYPES: readonly ["calculated"];
|
|
555
555
|
type CalculatedFormat = "number" | "percent" | "text";
|
|
556
|
+
/** Longest the formulas of one form may be, all together (`parseForm` checks it). */
|
|
557
|
+
declare const MAX_FORM_FORMULA_LENGTH = 20000;
|
|
556
558
|
/** Most decimal places a calculated value may keep. */
|
|
557
559
|
declare const MAX_CALCULATED_DECIMALS = 10;
|
|
558
560
|
interface CalculatedProps {
|
|
@@ -577,10 +579,18 @@ declare function formatCalculated(value: Json | undefined, field: FieldBlock): s
|
|
|
577
579
|
* server can't make every formula over it slow.
|
|
578
580
|
*/
|
|
579
581
|
declare const MAX_FORMULA_ITEMS = 10000;
|
|
582
|
+
/**
|
|
583
|
+
* Most list items and table rows all of a form's formulas read in one go
|
|
584
|
+
* (each list or table counted once, however many of its columns are read, in form order). A formula that reads
|
|
585
|
+
* an answer past the limit, or one over `MAX_FORMULA_ITEMS`, has no value
|
|
586
|
+
* (blank, never a partial total), so many big answers can't make working
|
|
587
|
+
* the values out slow either.
|
|
588
|
+
*/
|
|
589
|
+
declare const MAX_SETTLE_ITEMS = 50000;
|
|
580
590
|
/**
|
|
581
591
|
* The value a formula reads for an answer: numbers and text as they are, a
|
|
582
592
|
* list's items, one table column's cells. A list or table of more than
|
|
583
|
-
* `MAX_FORMULA_ITEMS` reads as blank.
|
|
593
|
+
* `MAX_FORMULA_ITEMS` reads as blank. Each call builds a new list.
|
|
584
594
|
*/
|
|
585
595
|
declare function formulaInput(value: Json | undefined, column?: string): FormulaValue;
|
|
586
596
|
|
|
@@ -666,4 +676,4 @@ declare function explain(source: FormEngine | ExplainInput, blockId: string, opt
|
|
|
666
676
|
/** A condition as a plain-English clause: `contact_me is not Yes`. */
|
|
667
677
|
declare function describeCondition(condition: Condition, def: FormDefinition, fields?: Map<string, FieldBlock>): string;
|
|
668
678
|
|
|
669
|
-
export { type AnswersOf, Block, CALCULATED_TYPE, COMPUTED_FIELD_TYPES, type CalculatedFormat, type CalculatedProps, type ChoiceFieldOptions, type ChoiceOption, type CoerceHiddenOptions, type CoercedHidden, Condition, ContentBlock, Ending, type ExplainInput, type ExplainOptions, type Explanation, FORMULA_FUNCTIONS, FieldBlock, type FieldOptions, type FieldSpec, type FieldTypeManifest, type FieldValidation, FieldValidator, type Form, FormDefinition, FormEngine, type FormInput, type FormIssue, FormLayout, FormSettings, type FormulaFunction, type FormulaNode, type FormulaParseResult, type FormulaReader, type FormulaRef, type FormulaValue, HIGHLIGHT_NOTE_MESSAGE, HiddenInput, type Highlight, Json, type JumpTrace, type KeyResolver, LogicRule, MAX_CALCULATED_DECIMALS, MAX_FORMULA_ITEMS, MAX_FORMULA_LENGTH, MAX_HIDDEN_STRING_LENGTH, MAX_HIGHLIGHT_NOTE_LENGTH, MAX_HIGHLIGHT_QUOTE_LENGTH, MAX_TABLE_CELL_LENGTH, MAX_TABLE_COLUMNS, MAX_TIMESTAMP_SECONDS, type MediaNote, Operator, PageBreakBlock, type ParseResult, type PathResult, type RuleSpec, type RuleTrace, STRUCTURED_FIELD_TYPES, type TableColumn, calculatedProps, calculatedValue, canonicalJson, choiceOptions, coerceHidden, coerceHiddenValue, computePath, dateLimits, defineForm, describeCondition, evaluate, evaluateFormula, explain, f, fingerprint, formatCalculated, formatTimestamp, formulaInput, formulaNumber, formulaRefs, getFieldType, isEmptyValue, isIsoDate, isSensitive, listFieldTypes, listLimits, normalizeValue, numberLimits, parseForm, parseFormula, parseTimestamp, ratingDisplay, recall, registerFieldType, renameFormulaKey, ruleVisible, scaleLabels, steps, summarizeValue, tableColumns, tableLimits, when };
|
|
679
|
+
export { type AnswersOf, Block, CALCULATED_TYPE, COMPUTED_FIELD_TYPES, type CalculatedFormat, type CalculatedProps, type ChoiceFieldOptions, type ChoiceOption, type CoerceHiddenOptions, type CoercedHidden, Condition, ContentBlock, Ending, type ExplainInput, type ExplainOptions, type Explanation, FORMULA_FUNCTIONS, FieldBlock, type FieldOptions, type FieldSpec, type FieldTypeManifest, type FieldValidation, FieldValidator, type Form, FormDefinition, FormEngine, type FormInput, type FormIssue, FormLayout, FormSettings, type FormulaFunction, type FormulaNode, type FormulaParseResult, type FormulaReader, type FormulaRef, type FormulaValue, HIGHLIGHT_NOTE_MESSAGE, HiddenInput, type Highlight, Json, type JumpTrace, type KeyResolver, LogicRule, MAX_CALCULATED_DECIMALS, MAX_FORMULA_ITEMS, MAX_FORMULA_LENGTH, MAX_FORM_FORMULA_LENGTH, MAX_HIDDEN_STRING_LENGTH, MAX_HIGHLIGHT_NOTE_LENGTH, MAX_HIGHLIGHT_QUOTE_LENGTH, MAX_SETTLE_ITEMS, MAX_TABLE_CELL_LENGTH, MAX_TABLE_COLUMNS, MAX_TIMESTAMP_SECONDS, type MediaNote, Operator, PageBreakBlock, type ParseResult, type PathResult, type RuleSpec, type RuleTrace, STRUCTURED_FIELD_TYPES, type TableColumn, calculatedProps, calculatedValue, canonicalJson, choiceOptions, coerceHidden, coerceHiddenValue, computePath, dateLimits, defineForm, describeCondition, evaluate, evaluateFormula, explain, f, fingerprint, formatCalculated, formatTimestamp, formulaInput, formulaNumber, formulaRefs, getFieldType, isEmptyValue, isIsoDate, isSensitive, listFieldTypes, listLimits, normalizeValue, numberLimits, parseForm, parseFormula, parseTimestamp, ratingDisplay, recall, registerFieldType, renameFormulaKey, ruleVisible, scaleLabels, steps, summarizeValue, tableColumns, tableLimits, when };
|
package/dist/index.d.ts
CHANGED
|
@@ -553,6 +553,8 @@ declare const CALCULATED_TYPE = "calculated";
|
|
|
553
553
|
*/
|
|
554
554
|
declare const COMPUTED_FIELD_TYPES: readonly ["calculated"];
|
|
555
555
|
type CalculatedFormat = "number" | "percent" | "text";
|
|
556
|
+
/** Longest the formulas of one form may be, all together (`parseForm` checks it). */
|
|
557
|
+
declare const MAX_FORM_FORMULA_LENGTH = 20000;
|
|
556
558
|
/** Most decimal places a calculated value may keep. */
|
|
557
559
|
declare const MAX_CALCULATED_DECIMALS = 10;
|
|
558
560
|
interface CalculatedProps {
|
|
@@ -577,10 +579,18 @@ declare function formatCalculated(value: Json | undefined, field: FieldBlock): s
|
|
|
577
579
|
* server can't make every formula over it slow.
|
|
578
580
|
*/
|
|
579
581
|
declare const MAX_FORMULA_ITEMS = 10000;
|
|
582
|
+
/**
|
|
583
|
+
* Most list items and table rows all of a form's formulas read in one go
|
|
584
|
+
* (each list or table counted once, however many of its columns are read, in form order). A formula that reads
|
|
585
|
+
* an answer past the limit, or one over `MAX_FORMULA_ITEMS`, has no value
|
|
586
|
+
* (blank, never a partial total), so many big answers can't make working
|
|
587
|
+
* the values out slow either.
|
|
588
|
+
*/
|
|
589
|
+
declare const MAX_SETTLE_ITEMS = 50000;
|
|
580
590
|
/**
|
|
581
591
|
* The value a formula reads for an answer: numbers and text as they are, a
|
|
582
592
|
* list's items, one table column's cells. A list or table of more than
|
|
583
|
-
* `MAX_FORMULA_ITEMS` reads as blank.
|
|
593
|
+
* `MAX_FORMULA_ITEMS` reads as blank. Each call builds a new list.
|
|
584
594
|
*/
|
|
585
595
|
declare function formulaInput(value: Json | undefined, column?: string): FormulaValue;
|
|
586
596
|
|
|
@@ -666,4 +676,4 @@ declare function explain(source: FormEngine | ExplainInput, blockId: string, opt
|
|
|
666
676
|
/** A condition as a plain-English clause: `contact_me is not Yes`. */
|
|
667
677
|
declare function describeCondition(condition: Condition, def: FormDefinition, fields?: Map<string, FieldBlock>): string;
|
|
668
678
|
|
|
669
|
-
export { type AnswersOf, Block, CALCULATED_TYPE, COMPUTED_FIELD_TYPES, type CalculatedFormat, type CalculatedProps, type ChoiceFieldOptions, type ChoiceOption, type CoerceHiddenOptions, type CoercedHidden, Condition, ContentBlock, Ending, type ExplainInput, type ExplainOptions, type Explanation, FORMULA_FUNCTIONS, FieldBlock, type FieldOptions, type FieldSpec, type FieldTypeManifest, type FieldValidation, FieldValidator, type Form, FormDefinition, FormEngine, type FormInput, type FormIssue, FormLayout, FormSettings, type FormulaFunction, type FormulaNode, type FormulaParseResult, type FormulaReader, type FormulaRef, type FormulaValue, HIGHLIGHT_NOTE_MESSAGE, HiddenInput, type Highlight, Json, type JumpTrace, type KeyResolver, LogicRule, MAX_CALCULATED_DECIMALS, MAX_FORMULA_ITEMS, MAX_FORMULA_LENGTH, MAX_HIDDEN_STRING_LENGTH, MAX_HIGHLIGHT_NOTE_LENGTH, MAX_HIGHLIGHT_QUOTE_LENGTH, MAX_TABLE_CELL_LENGTH, MAX_TABLE_COLUMNS, MAX_TIMESTAMP_SECONDS, type MediaNote, Operator, PageBreakBlock, type ParseResult, type PathResult, type RuleSpec, type RuleTrace, STRUCTURED_FIELD_TYPES, type TableColumn, calculatedProps, calculatedValue, canonicalJson, choiceOptions, coerceHidden, coerceHiddenValue, computePath, dateLimits, defineForm, describeCondition, evaluate, evaluateFormula, explain, f, fingerprint, formatCalculated, formatTimestamp, formulaInput, formulaNumber, formulaRefs, getFieldType, isEmptyValue, isIsoDate, isSensitive, listFieldTypes, listLimits, normalizeValue, numberLimits, parseForm, parseFormula, parseTimestamp, ratingDisplay, recall, registerFieldType, renameFormulaKey, ruleVisible, scaleLabels, steps, summarizeValue, tableColumns, tableLimits, when };
|
|
679
|
+
export { type AnswersOf, Block, CALCULATED_TYPE, COMPUTED_FIELD_TYPES, type CalculatedFormat, type CalculatedProps, type ChoiceFieldOptions, type ChoiceOption, type CoerceHiddenOptions, type CoercedHidden, Condition, ContentBlock, Ending, type ExplainInput, type ExplainOptions, type Explanation, FORMULA_FUNCTIONS, FieldBlock, type FieldOptions, type FieldSpec, type FieldTypeManifest, type FieldValidation, FieldValidator, type Form, FormDefinition, FormEngine, type FormInput, type FormIssue, FormLayout, FormSettings, type FormulaFunction, type FormulaNode, type FormulaParseResult, type FormulaReader, type FormulaRef, type FormulaValue, HIGHLIGHT_NOTE_MESSAGE, HiddenInput, type Highlight, Json, type JumpTrace, type KeyResolver, LogicRule, MAX_CALCULATED_DECIMALS, MAX_FORMULA_ITEMS, MAX_FORMULA_LENGTH, MAX_FORM_FORMULA_LENGTH, MAX_HIDDEN_STRING_LENGTH, MAX_HIGHLIGHT_NOTE_LENGTH, MAX_HIGHLIGHT_QUOTE_LENGTH, MAX_SETTLE_ITEMS, MAX_TABLE_CELL_LENGTH, MAX_TABLE_COLUMNS, MAX_TIMESTAMP_SECONDS, type MediaNote, Operator, PageBreakBlock, type ParseResult, type PathResult, type RuleSpec, type RuleTrace, STRUCTURED_FIELD_TYPES, type TableColumn, calculatedProps, calculatedValue, canonicalJson, choiceOptions, coerceHidden, coerceHiddenValue, computePath, dateLimits, defineForm, describeCondition, evaluate, evaluateFormula, explain, f, fingerprint, formatCalculated, formatTimestamp, formulaInput, formulaNumber, formulaRefs, getFieldType, isEmptyValue, isIsoDate, isSensitive, listFieldTypes, listLimits, normalizeValue, numberLimits, parseForm, parseFormula, parseTimestamp, ratingDisplay, recall, registerFieldType, renameFormulaKey, ruleVisible, scaleLabels, steps, summarizeValue, tableColumns, tableLimits, when };
|
package/dist/index.js
CHANGED
|
@@ -62,6 +62,7 @@ function tokenize(source) {
|
|
|
62
62
|
i++;
|
|
63
63
|
while (isDigit(source[i] ?? "")) i++;
|
|
64
64
|
}
|
|
65
|
+
if (isIdStart(source[i] ?? "")) throw new FormulaError("Write a number in digits only, like 1000000.", at);
|
|
65
66
|
const v = Number(source.slice(at, i));
|
|
66
67
|
if (!Number.isFinite(v)) throw new FormulaError("This number is too large.", at);
|
|
67
68
|
tokens.push({ t: "num", v, at });
|
|
@@ -291,6 +292,27 @@ function numbersIn(values, out = []) {
|
|
|
291
292
|
}
|
|
292
293
|
return out;
|
|
293
294
|
}
|
|
295
|
+
var listTotals = /* @__PURE__ */ new WeakMap();
|
|
296
|
+
var ownLists = /* @__PURE__ */ new WeakSet();
|
|
297
|
+
function ownFormulaList(list) {
|
|
298
|
+
Object.freeze(list);
|
|
299
|
+
ownLists.add(list);
|
|
300
|
+
return list;
|
|
301
|
+
}
|
|
302
|
+
function totalsOf(list) {
|
|
303
|
+
const own2 = ownLists.has(list);
|
|
304
|
+
let t = own2 ? listTotals.get(list) : void 0;
|
|
305
|
+
if (t) return t;
|
|
306
|
+
const nums = numbersIn(list);
|
|
307
|
+
t = { sum: 0, count: nums.length, min: Infinity, max: -Infinity };
|
|
308
|
+
for (const n of nums) {
|
|
309
|
+
t.sum += n;
|
|
310
|
+
if (n < t.min) t.min = n;
|
|
311
|
+
if (n > t.max) t.max = n;
|
|
312
|
+
}
|
|
313
|
+
if (own2) listTotals.set(list, t);
|
|
314
|
+
return t;
|
|
315
|
+
}
|
|
294
316
|
function compare(op, a, b) {
|
|
295
317
|
const blank2 = (v) => v === null || Array.isArray(v) || typeof v === "string" && v.trim() === "";
|
|
296
318
|
if (blank2(a) || blank2(b)) return op === "!=";
|
|
@@ -315,7 +337,7 @@ function evaluateFormula(ast, read) {
|
|
|
315
337
|
case "text":
|
|
316
338
|
return node.value;
|
|
317
339
|
case "ref":
|
|
318
|
-
return read(node
|
|
340
|
+
return read(node);
|
|
319
341
|
case "unary": {
|
|
320
342
|
const n = formulaNumber(scalar(run(node.arg)));
|
|
321
343
|
return n === null ? null : -n;
|
|
@@ -323,12 +345,31 @@ function evaluateFormula(ast, read) {
|
|
|
323
345
|
case "if":
|
|
324
346
|
return truthy(run(node.test)) ? run(node.then) : run(node.else);
|
|
325
347
|
case "call": {
|
|
326
|
-
|
|
327
|
-
|
|
348
|
+
let total = 0;
|
|
349
|
+
let count = 0;
|
|
350
|
+
let min = Infinity;
|
|
351
|
+
let max = -Infinity;
|
|
352
|
+
for (const arg of node.args) {
|
|
353
|
+
const v = run(arg);
|
|
354
|
+
if (Array.isArray(v)) {
|
|
355
|
+
const t = totalsOf(v);
|
|
356
|
+
total += t.sum;
|
|
357
|
+
count += t.count;
|
|
358
|
+
if (t.min < min) min = t.min;
|
|
359
|
+
if (t.max > max) max = t.max;
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
const n = formulaNumber(v);
|
|
363
|
+
if (n === null) continue;
|
|
364
|
+
total += n;
|
|
365
|
+
count++;
|
|
366
|
+
if (n < min) min = n;
|
|
367
|
+
if (n > max) max = n;
|
|
368
|
+
}
|
|
328
369
|
if (node.fn === "sum") return finite(total);
|
|
329
|
-
if (!
|
|
330
|
-
if (node.fn === "avg") return finite(total /
|
|
331
|
-
return
|
|
370
|
+
if (!count) return null;
|
|
371
|
+
if (node.fn === "avg") return finite(total / count);
|
|
372
|
+
return node.fn === "min" ? min : max;
|
|
332
373
|
}
|
|
333
374
|
case "binary": {
|
|
334
375
|
const a = run(node.left);
|
|
@@ -356,6 +397,7 @@ function evaluateFormula(ast, read) {
|
|
|
356
397
|
// src/calculated.ts
|
|
357
398
|
var CALCULATED_TYPE = "calculated";
|
|
358
399
|
var COMPUTED_FIELD_TYPES = [CALCULATED_TYPE];
|
|
400
|
+
var MAX_FORM_FORMULA_LENGTH = 2e4;
|
|
359
401
|
var MAX_CALCULATED_DECIMALS = 10;
|
|
360
402
|
function calculatedProps(field2) {
|
|
361
403
|
const p = field2.props ?? {};
|
|
@@ -373,7 +415,9 @@ var round = (n, decimals) => {
|
|
|
373
415
|
return Object.is(r, -0) ? 0 : r;
|
|
374
416
|
};
|
|
375
417
|
function calculatedValue(result, field2) {
|
|
376
|
-
|
|
418
|
+
return valueAs(result, calculatedProps(field2));
|
|
419
|
+
}
|
|
420
|
+
function valueAs(result, { format, decimals }) {
|
|
377
421
|
if (result === null || Array.isArray(result)) return void 0;
|
|
378
422
|
if (format === "number" || format === "percent") {
|
|
379
423
|
const n = formulaNumber(result);
|
|
@@ -394,24 +438,43 @@ function formatCalculated(value, field2) {
|
|
|
394
438
|
return typeof value === "string" ? value : "";
|
|
395
439
|
}
|
|
396
440
|
var MAX_FORMULA_ITEMS = 1e4;
|
|
441
|
+
var MAX_SETTLE_ITEMS = 5e4;
|
|
442
|
+
var extract = (value, column) => column !== void 0 ? value.flatMap((row) => {
|
|
443
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) return [];
|
|
444
|
+
const cell = row[column];
|
|
445
|
+
return typeof cell === "string" || typeof cell === "number" ? [cell] : [];
|
|
446
|
+
}) : value.flatMap((v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean" ? [v] : []);
|
|
397
447
|
function formulaInput(value, column) {
|
|
398
448
|
if (value === void 0 || value === null) return null;
|
|
399
449
|
if (Array.isArray(value) && value.length > MAX_FORMULA_ITEMS) return null;
|
|
400
450
|
if (column !== void 0) {
|
|
401
451
|
if (!Array.isArray(value)) return null;
|
|
402
|
-
return value
|
|
403
|
-
if (!row || typeof row !== "object" || Array.isArray(row)) return [];
|
|
404
|
-
const cell = row[column];
|
|
405
|
-
return typeof cell === "string" || typeof cell === "number" ? [cell] : [];
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
if (Array.isArray(value)) {
|
|
409
|
-
return value.flatMap((v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean" ? [v] : []);
|
|
452
|
+
return extract(value, column);
|
|
410
453
|
}
|
|
454
|
+
if (Array.isArray(value)) return extract(value, void 0);
|
|
411
455
|
return typeof value === "object" ? null : value;
|
|
412
456
|
}
|
|
457
|
+
function refNodes(node, out = []) {
|
|
458
|
+
if (node.kind === "ref") out.push(node);
|
|
459
|
+
else if (node.kind === "unary") refNodes(node.arg, out);
|
|
460
|
+
else if (node.kind === "binary") refNodes(node.left, out), refNodes(node.right, out);
|
|
461
|
+
else if (node.kind === "if") refNodes(node.test, out), refNodes(node.then, out), refNodes(node.else, out);
|
|
462
|
+
else if (node.kind === "call") for (const a of node.args) refNodes(a, out);
|
|
463
|
+
return out;
|
|
464
|
+
}
|
|
465
|
+
function formulaSize(node) {
|
|
466
|
+
if (node.kind === "unary") return 1 + formulaSize(node.arg);
|
|
467
|
+
if (node.kind === "binary") return 1 + formulaSize(node.left) + formulaSize(node.right);
|
|
468
|
+
if (node.kind === "if") return 1 + formulaSize(node.test) + formulaSize(node.then) + formulaSize(node.else);
|
|
469
|
+
if (node.kind === "call") return node.args.reduce((n, a) => n + formulaSize(a), 1);
|
|
470
|
+
return 1;
|
|
471
|
+
}
|
|
472
|
+
function spend(budget, amount) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
413
475
|
var UNREAD = /* @__PURE__ */ Symbol("unread");
|
|
414
|
-
|
|
476
|
+
var OVER = /* @__PURE__ */ Symbol("over");
|
|
477
|
+
function calculator(definition, work) {
|
|
415
478
|
const compiled = [];
|
|
416
479
|
const idByKey = /* @__PURE__ */ new Map();
|
|
417
480
|
for (const block of definition.blocks) {
|
|
@@ -420,35 +483,105 @@ function calculator(definition) {
|
|
|
420
483
|
}
|
|
421
484
|
for (const block of definition.blocks) {
|
|
422
485
|
if (block.kind !== "field" || !isCalculated(block)) continue;
|
|
423
|
-
const
|
|
424
|
-
const
|
|
425
|
-
|
|
486
|
+
const props2 = calculatedProps(block);
|
|
487
|
+
const parsed = parseFormula(props2.formula);
|
|
488
|
+
const reads = [];
|
|
489
|
+
const slots = /* @__PURE__ */ new Map();
|
|
490
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
491
|
+
for (const ref of parsed.ok ? refNodes(parsed.ast) : []) {
|
|
492
|
+
const id = idByKey.get(ref.key);
|
|
493
|
+
const key = `${id ?? ""}\0${ref.column ?? ""}`;
|
|
494
|
+
let slot = byKey.get(key);
|
|
495
|
+
if (slot === void 0) {
|
|
496
|
+
slot = reads.push({ id, column: ref.column, key }) - 1;
|
|
497
|
+
byKey.set(key, slot);
|
|
498
|
+
}
|
|
499
|
+
slots.set(ref, slot);
|
|
500
|
+
}
|
|
501
|
+
compiled.push({ field: block, props: props2, ast: parsed.ok ? parsed.ast : null, size: parsed.ok ? formulaSize(parsed.ast) : 0, reads, slots, last: null });
|
|
426
502
|
}
|
|
503
|
+
const extracted = /* @__PURE__ */ new WeakMap();
|
|
504
|
+
const cachedList = (value, column) => {
|
|
505
|
+
let byColumn = extracted.get(value);
|
|
506
|
+
if (!byColumn) extracted.set(value, byColumn = /* @__PURE__ */ new Map());
|
|
507
|
+
const key = column ?? "";
|
|
508
|
+
let list = byColumn.get(key);
|
|
509
|
+
if (!list) {
|
|
510
|
+
byColumn.set(key, list = ownFormulaList(extract(value, column)));
|
|
511
|
+
}
|
|
512
|
+
return list;
|
|
513
|
+
};
|
|
514
|
+
const readAnswer = (value, column) => {
|
|
515
|
+
if (Array.isArray(value)) return cachedList(value, column);
|
|
516
|
+
return column !== void 0 ? null : formulaInput(value);
|
|
517
|
+
};
|
|
518
|
+
const forget = (value) => {
|
|
519
|
+
if (!value || typeof value !== "object") return;
|
|
520
|
+
if (Array.isArray(value)) extracted.delete(value);
|
|
521
|
+
for (const entry of compiled) if (entry.last?.inputs.includes(value)) entry.last = null;
|
|
522
|
+
};
|
|
427
523
|
const withCalculated = (values, path) => {
|
|
428
524
|
if (!compiled.length) return values;
|
|
429
525
|
const visible = new Set(path);
|
|
430
526
|
let next = values;
|
|
431
|
-
|
|
527
|
+
let copied = false;
|
|
528
|
+
let budget = MAX_SETTLE_ITEMS;
|
|
529
|
+
const within = /* @__PURE__ */ new Map();
|
|
530
|
+
const input = ({ id }) => {
|
|
531
|
+
if (id === void 0 || !visible.has(id) || !Object.prototype.hasOwnProperty.call(next, id)) return UNREAD;
|
|
532
|
+
const value = next[id];
|
|
533
|
+
if (!Array.isArray(value)) return value;
|
|
534
|
+
let ok = within.get(id);
|
|
535
|
+
if (ok === void 0) {
|
|
536
|
+
ok = value.length <= MAX_FORMULA_ITEMS && value.length <= budget;
|
|
537
|
+
if (ok) budget -= value.length;
|
|
538
|
+
within.set(id, ok);
|
|
539
|
+
}
|
|
540
|
+
return ok ? value : OVER;
|
|
541
|
+
};
|
|
432
542
|
for (const entry of compiled) {
|
|
433
543
|
const { field: field2, ast } = entry;
|
|
434
|
-
const
|
|
544
|
+
const { reads } = entry;
|
|
545
|
+
const inputs = new Array(reads.length);
|
|
546
|
+
let over = false;
|
|
547
|
+
let same = entry.last !== null;
|
|
548
|
+
for (let i = 0; i < reads.length; i++) {
|
|
549
|
+
const v = input(reads[i]);
|
|
550
|
+
inputs[i] = v;
|
|
551
|
+
if (v === OVER) over = true;
|
|
552
|
+
if (same && entry.last.inputs[i] !== v) same = false;
|
|
553
|
+
}
|
|
435
554
|
let value;
|
|
436
|
-
if (
|
|
555
|
+
if (same) {
|
|
437
556
|
value = entry.last.value;
|
|
438
557
|
} else {
|
|
558
|
+
spend(work, entry.size);
|
|
559
|
+
const resolved = new Array(inputs.length);
|
|
439
560
|
const read = (ref) => {
|
|
440
|
-
const
|
|
441
|
-
|
|
561
|
+
const slot = entry.slots.get(ref);
|
|
562
|
+
if (slot === void 0) return null;
|
|
563
|
+
let v = resolved[slot];
|
|
564
|
+
if (v === void 0) {
|
|
565
|
+
const raw = inputs[slot];
|
|
566
|
+
v = resolved[slot] = raw === UNREAD ? null : readAnswer(raw, reads[slot].column);
|
|
567
|
+
}
|
|
568
|
+
return v;
|
|
442
569
|
};
|
|
443
|
-
value = ast ?
|
|
570
|
+
value = ast && !over ? valueAs(evaluateFormula(ast, read), entry.props) : void 0;
|
|
444
571
|
entry.last = { inputs, value };
|
|
445
572
|
}
|
|
446
573
|
const current = Object.prototype.hasOwnProperty.call(next, field2.id) ? next[field2.id] : void 0;
|
|
447
|
-
if (current !== value)
|
|
574
|
+
if (current !== value) {
|
|
575
|
+
if (!copied) {
|
|
576
|
+
next = { ...next };
|
|
577
|
+
copied = true;
|
|
578
|
+
}
|
|
579
|
+
next[field2.id] = value;
|
|
580
|
+
}
|
|
448
581
|
}
|
|
449
582
|
return next;
|
|
450
583
|
};
|
|
451
|
-
return { active: compiled.length > 0, withCalculated };
|
|
584
|
+
return { active: compiled.length > 0, ids: new Set(compiled.map((c) => c.field.id)), withCalculated, forget };
|
|
452
585
|
}
|
|
453
586
|
var numberOps = ["eq", "neq", "lt", "lte", "gt", "gte", "in", "answered", "empty"];
|
|
454
587
|
var calculatedFieldType = {
|
|
@@ -1083,8 +1216,8 @@ function rulesAllow(def, blockId, scope) {
|
|
|
1083
1216
|
}
|
|
1084
1217
|
function walkPath(def, scope) {
|
|
1085
1218
|
const list = steps(def);
|
|
1086
|
-
|
|
1087
|
-
|
|
1219
|
+
let index;
|
|
1220
|
+
let position;
|
|
1088
1221
|
const jumps = (def.logic ?? []).filter(
|
|
1089
1222
|
(r) => r.effect === "jump"
|
|
1090
1223
|
);
|
|
@@ -1108,6 +1241,7 @@ function walkPath(def, scope) {
|
|
|
1108
1241
|
ending = jump.to.ending;
|
|
1109
1242
|
break;
|
|
1110
1243
|
}
|
|
1244
|
+
index ??= new Map(list.map((b, at) => [b.id, at]));
|
|
1111
1245
|
const target = index.get(jump.to);
|
|
1112
1246
|
if (target !== void 0 && target > i) {
|
|
1113
1247
|
i = nextVisible(target);
|
|
@@ -1117,6 +1251,7 @@ function walkPath(def, scope) {
|
|
|
1117
1251
|
i = nextVisible(i + 1);
|
|
1118
1252
|
}
|
|
1119
1253
|
const scopeAt = (blockId, inclusive = false) => {
|
|
1254
|
+
position ??= new Map(def.blocks.map((b, i2) => [b.id, i2]));
|
|
1120
1255
|
const at = position.get(blockId) ?? -1;
|
|
1121
1256
|
const values = /* @__PURE__ */ Object.create(null);
|
|
1122
1257
|
for (const id of path) {
|
|
@@ -1421,9 +1556,15 @@ function parseForm(input) {
|
|
|
1421
1556
|
issue(`${path}.kind`, "Unknown block kind.");
|
|
1422
1557
|
}
|
|
1423
1558
|
});
|
|
1559
|
+
let formulaChars = 0;
|
|
1424
1560
|
input.blocks.forEach((block, i) => {
|
|
1425
1561
|
if (!isObject2(block) || block.kind !== "field" || block.type !== CALCULATED_TYPE) return;
|
|
1426
1562
|
const path = `blocks[${i}].props.formula`;
|
|
1563
|
+
const source = isObject2(block.props) && typeof block.props.formula === "string" ? block.props.formula : "";
|
|
1564
|
+
formulaChars += source.length;
|
|
1565
|
+
if (formulaChars > MAX_FORM_FORMULA_LENGTH && formulaChars - source.length <= MAX_FORM_FORMULA_LENGTH) {
|
|
1566
|
+
issue(path, `The form's formulas together are over ${MAX_FORM_FORMULA_LENGTH} characters; shorten some, or work part out once in an earlier calculated field.`);
|
|
1567
|
+
}
|
|
1427
1568
|
if (block.required === true) {
|
|
1428
1569
|
warnings.push({ path: `blocks[${i}].required`, message: "A calculated field is worked out, not answered, so it is never required." });
|
|
1429
1570
|
}
|
|
@@ -1922,10 +2063,19 @@ function validateField(field2, value, options = {}) {
|
|
|
1922
2063
|
}
|
|
1923
2064
|
|
|
1924
2065
|
// src/engine.ts
|
|
2066
|
+
function readsAny(node, ids) {
|
|
2067
|
+
if (Array.isArray(node)) return node.some((n) => readsAny(n, ids));
|
|
2068
|
+
if (!node || typeof node !== "object") return false;
|
|
2069
|
+
for (const [k, v] of Object.entries(node)) {
|
|
2070
|
+
if (k === "field" && typeof v === "string" && ids.has(v)) return true;
|
|
2071
|
+
if (typeof v === "object" && readsAny(v, ids)) return true;
|
|
2072
|
+
}
|
|
2073
|
+
return false;
|
|
2074
|
+
}
|
|
1925
2075
|
function createForm(definition, options = {}) {
|
|
1926
2076
|
return build(definition, options).engine;
|
|
1927
2077
|
}
|
|
1928
|
-
function build(definition, options, replaying = false) {
|
|
2078
|
+
function build(definition, options, replaying = false, budget) {
|
|
1929
2079
|
const fields = fieldMap(definition);
|
|
1930
2080
|
const byKey = new Map([...fields.values()].map((f2) => [f2.key, f2]));
|
|
1931
2081
|
const blocks = new Map(definition.blocks.map((b) => [b.id, b]));
|
|
@@ -1943,15 +2093,24 @@ function build(definition, options, replaying = false) {
|
|
|
1943
2093
|
if (!declared.has(name)) initialHidden[name] = value;
|
|
1944
2094
|
}
|
|
1945
2095
|
Object.assign(initialHidden, coerceHidden(definition, options.hidden).values);
|
|
1946
|
-
const calc = calculator(definition);
|
|
2096
|
+
const calc = calculator(definition, budget);
|
|
2097
|
+
const pathOf = (values, hidden) => {
|
|
2098
|
+
spend(budget, definition.blocks.length);
|
|
2099
|
+
return computePath(definition, scopeOf(values, hidden));
|
|
2100
|
+
};
|
|
2101
|
+
const logicReadsCalculated = calc.active && readsAny(definition.logic, calc.ids);
|
|
1947
2102
|
const settle = (values, hidden) => {
|
|
1948
|
-
let { path } =
|
|
2103
|
+
let { path } = pathOf(values, hidden);
|
|
1949
2104
|
if (!calc.active) return { values, path };
|
|
2105
|
+
if (!logicReadsCalculated) return { values: calc.withCalculated(values, path), path };
|
|
1950
2106
|
for (let round2 = 0; round2 < 4; round2++) {
|
|
1951
2107
|
const next = calc.withCalculated(values, path);
|
|
1952
2108
|
if (next === values) break;
|
|
1953
2109
|
values = next;
|
|
1954
|
-
|
|
2110
|
+
const again = pathOf(values, hidden).path;
|
|
2111
|
+
const same = again.length === path.length && again.every((id, i) => id === path[i]);
|
|
2112
|
+
path = again;
|
|
2113
|
+
if (same) break;
|
|
1955
2114
|
}
|
|
1956
2115
|
return { values, path };
|
|
1957
2116
|
};
|
|
@@ -1993,14 +2152,30 @@ function build(definition, options, replaying = false) {
|
|
|
1993
2152
|
return errors;
|
|
1994
2153
|
};
|
|
1995
2154
|
let unsettled = false;
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
unsettled
|
|
1999
|
-
|
|
2000
|
-
|
|
2155
|
+
let formulasStale = false;
|
|
2156
|
+
const settleNow = (needValues = true) => {
|
|
2157
|
+
if (unsettled && calc.active && !logicReadsCalculated && !needValues) {
|
|
2158
|
+
unsettled = false;
|
|
2159
|
+
formulasStale = true;
|
|
2160
|
+
state = { ...state, path: pathOf(state.values, state.hidden).path };
|
|
2161
|
+
return;
|
|
2162
|
+
}
|
|
2163
|
+
if (unsettled) {
|
|
2164
|
+
unsettled = false;
|
|
2165
|
+
formulasStale = false;
|
|
2166
|
+
const { values, path } = settle(state.values, state.hidden);
|
|
2167
|
+
state = { ...state, values, path };
|
|
2168
|
+
} else if (formulasStale && needValues) {
|
|
2169
|
+
formulasStale = false;
|
|
2170
|
+
state = { ...state, values: calc.withCalculated(state.values, state.path) };
|
|
2171
|
+
}
|
|
2001
2172
|
};
|
|
2002
2173
|
const answer = (id, value, event) => {
|
|
2003
|
-
if (state.status === "completed" ||
|
|
2174
|
+
if (state.status === "completed" || isCalculated(fields.get(id))) return;
|
|
2175
|
+
if (own(state.values, id) === value) {
|
|
2176
|
+
if (value === null || typeof value !== "object") return;
|
|
2177
|
+
calc.forget(value);
|
|
2178
|
+
}
|
|
2004
2179
|
const errors = { ...state.errors };
|
|
2005
2180
|
delete errors[id];
|
|
2006
2181
|
if (batch) {
|
|
@@ -2066,7 +2241,7 @@ function build(definition, options, replaying = false) {
|
|
|
2066
2241
|
emit({ ...state, errors, current: firstError, events });
|
|
2067
2242
|
return false;
|
|
2068
2243
|
}
|
|
2069
|
-
const { ending } =
|
|
2244
|
+
const { ending } = pathOf(state.values, state.hidden);
|
|
2070
2245
|
emit({ ...state, errors: {}, status: "completed", ending, events });
|
|
2071
2246
|
return true;
|
|
2072
2247
|
},
|
|
@@ -2098,10 +2273,11 @@ function build(definition, options, replaying = false) {
|
|
|
2098
2273
|
answer(event.field, event.value, logged);
|
|
2099
2274
|
}
|
|
2100
2275
|
} else if (event.type === "next") {
|
|
2101
|
-
settleNow();
|
|
2276
|
+
settleNow(false);
|
|
2277
|
+
if (formulasStale && isCalculated(fields.get(state.current ?? ""))) settleNow(true);
|
|
2102
2278
|
engine.next();
|
|
2103
2279
|
} else if (event.type === "back") {
|
|
2104
|
-
settleNow();
|
|
2280
|
+
settleNow(false);
|
|
2105
2281
|
engine.back();
|
|
2106
2282
|
} else if (event.type === "submit") {
|
|
2107
2283
|
settleNow();
|
|
@@ -2116,9 +2292,16 @@ function build(definition, options, replaying = false) {
|
|
|
2116
2292
|
return { engine, apply, finish };
|
|
2117
2293
|
}
|
|
2118
2294
|
function replay(definition, events, options = {}) {
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2295
|
+
return replayWithin(definition, events, options);
|
|
2296
|
+
}
|
|
2297
|
+
function replayWithin(definition, events, options, maxWork) {
|
|
2298
|
+
const budget = void 0 ;
|
|
2299
|
+
const { engine, apply, finish } = build(definition, { ...options, initialValues: void 0, clock: () => void 0 }, true, budget);
|
|
2300
|
+
try {
|
|
2301
|
+
for (const event of events) apply(event);
|
|
2302
|
+
finish();
|
|
2303
|
+
} finally {
|
|
2304
|
+
}
|
|
2122
2305
|
return engine;
|
|
2123
2306
|
}
|
|
2124
2307
|
|
|
@@ -2258,6 +2441,6 @@ function describeCondition(condition, def, fields = fieldMap(def)) {
|
|
|
2258
2441
|
return `${left} ${op} ${right}`;
|
|
2259
2442
|
}
|
|
2260
2443
|
|
|
2261
|
-
export { CALCULATED_TYPE, COMPUTED_FIELD_TYPES, FORMULA_FUNCTIONS, HIGHLIGHT_NOTE_MESSAGE, MAX_CALCULATED_DECIMALS, MAX_FORMULA_ITEMS, MAX_FORMULA_LENGTH, MAX_HIDDEN_STRING_LENGTH, MAX_HIGHLIGHT_NOTE_LENGTH, MAX_HIGHLIGHT_QUOTE_LENGTH, MAX_TABLE_CELL_LENGTH, MAX_TABLE_COLUMNS, MAX_TIMESTAMP_SECONDS, REQUIRED_MESSAGE, STRUCTURED_FIELD_TYPES, calculatedProps, calculatedValue, canonicalJson, choiceOptions, coerceHidden, coerceHiddenValue, computePath, createForm, dateLimits, defineForm, describeCondition, evaluate, evaluateFormula, explain, f, fingerprint, formatCalculated, formatTimestamp, formulaInput, formulaNumber, formulaRefs, getFieldType, isEmptyValue, isFormEvent, isIsoDate, isJson, isSensitive, listFieldTypes, listLimits, normalizeValue, numberLimits, parseForm, parseFormula, parseTimestamp, ratingDisplay, recall, registerFieldType, renameFormulaKey, replay, ruleVisible, scaleLabels, steps, summarizeValue, tableColumns, tableLimits, validateField, when };
|
|
2444
|
+
export { CALCULATED_TYPE, COMPUTED_FIELD_TYPES, FORMULA_FUNCTIONS, HIGHLIGHT_NOTE_MESSAGE, MAX_CALCULATED_DECIMALS, MAX_FORMULA_ITEMS, MAX_FORMULA_LENGTH, MAX_FORM_FORMULA_LENGTH, MAX_HIDDEN_STRING_LENGTH, MAX_HIGHLIGHT_NOTE_LENGTH, MAX_HIGHLIGHT_QUOTE_LENGTH, MAX_SETTLE_ITEMS, MAX_TABLE_CELL_LENGTH, MAX_TABLE_COLUMNS, MAX_TIMESTAMP_SECONDS, REQUIRED_MESSAGE, STRUCTURED_FIELD_TYPES, calculatedProps, calculatedValue, canonicalJson, choiceOptions, coerceHidden, coerceHiddenValue, computePath, createForm, dateLimits, defineForm, describeCondition, evaluate, evaluateFormula, explain, f, fingerprint, formatCalculated, formatTimestamp, formulaInput, formulaNumber, formulaRefs, getFieldType, isEmptyValue, isFormEvent, isIsoDate, isJson, isSensitive, listFieldTypes, listLimits, normalizeValue, numberLimits, parseForm, parseFormula, parseTimestamp, ratingDisplay, recall, registerFieldType, renameFormulaKey, replay, ruleVisible, scaleLabels, steps, summarizeValue, tableColumns, tableLimits, validateField, when };
|
|
2262
2445
|
//# sourceMappingURL=index.js.map
|
|
2263
2446
|
//# sourceMappingURL=index.js.map
|