@marktiderman/genesis-forms 0.4.0 → 0.4.1
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 +12 -2
- package/dist/destinations.cjs.map +1 -1
- package/dist/destinations.js.map +1 -1
- package/dist/index.cjs +124 -20
- 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 +123 -21
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +121 -23
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +121 -23
- package/dist/server.js.map +1 -1
- package/dist/testing.cjs +121 -23
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js +121 -23
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/testing.js
CHANGED
|
@@ -32,6 +32,7 @@ function tokenize(source) {
|
|
|
32
32
|
i++;
|
|
33
33
|
while (isDigit(source[i] ?? "")) i++;
|
|
34
34
|
}
|
|
35
|
+
if (isIdStart(source[i] ?? "")) throw new FormulaError("Write a number in digits only, like 1000000.", at);
|
|
35
36
|
const v = Number(source.slice(at, i));
|
|
36
37
|
if (!Number.isFinite(v)) throw new FormulaError("This number is too large.", at);
|
|
37
38
|
tokens.push({ t: "num", v, at });
|
|
@@ -230,6 +231,27 @@ function numbersIn(values, out = []) {
|
|
|
230
231
|
}
|
|
231
232
|
return out;
|
|
232
233
|
}
|
|
234
|
+
var listTotals = /* @__PURE__ */ new WeakMap();
|
|
235
|
+
var ownLists = /* @__PURE__ */ new WeakSet();
|
|
236
|
+
function ownFormulaList(list) {
|
|
237
|
+
Object.freeze(list);
|
|
238
|
+
ownLists.add(list);
|
|
239
|
+
return list;
|
|
240
|
+
}
|
|
241
|
+
function totalsOf(list) {
|
|
242
|
+
const own2 = ownLists.has(list);
|
|
243
|
+
let t = own2 ? listTotals.get(list) : void 0;
|
|
244
|
+
if (t) return t;
|
|
245
|
+
const nums = numbersIn(list);
|
|
246
|
+
t = { sum: 0, count: nums.length, min: Infinity, max: -Infinity };
|
|
247
|
+
for (const n of nums) {
|
|
248
|
+
t.sum += n;
|
|
249
|
+
if (n < t.min) t.min = n;
|
|
250
|
+
if (n > t.max) t.max = n;
|
|
251
|
+
}
|
|
252
|
+
if (own2) listTotals.set(list, t);
|
|
253
|
+
return t;
|
|
254
|
+
}
|
|
233
255
|
function compare(op, a, b) {
|
|
234
256
|
const blank2 = (v) => v === null || Array.isArray(v) || typeof v === "string" && v.trim() === "";
|
|
235
257
|
if (blank2(a) || blank2(b)) return op === "!=";
|
|
@@ -254,7 +276,7 @@ function evaluateFormula(ast, read) {
|
|
|
254
276
|
case "text":
|
|
255
277
|
return node.value;
|
|
256
278
|
case "ref":
|
|
257
|
-
return read(node
|
|
279
|
+
return read(node);
|
|
258
280
|
case "unary": {
|
|
259
281
|
const n = formulaNumber(scalar(run(node.arg)));
|
|
260
282
|
return n === null ? null : -n;
|
|
@@ -262,12 +284,31 @@ function evaluateFormula(ast, read) {
|
|
|
262
284
|
case "if":
|
|
263
285
|
return truthy(run(node.test)) ? run(node.then) : run(node.else);
|
|
264
286
|
case "call": {
|
|
265
|
-
|
|
266
|
-
|
|
287
|
+
let total = 0;
|
|
288
|
+
let count = 0;
|
|
289
|
+
let min = Infinity;
|
|
290
|
+
let max = -Infinity;
|
|
291
|
+
for (const arg of node.args) {
|
|
292
|
+
const v = run(arg);
|
|
293
|
+
if (Array.isArray(v)) {
|
|
294
|
+
const t = totalsOf(v);
|
|
295
|
+
total += t.sum;
|
|
296
|
+
count += t.count;
|
|
297
|
+
if (t.min < min) min = t.min;
|
|
298
|
+
if (t.max > max) max = t.max;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const n = formulaNumber(v);
|
|
302
|
+
if (n === null) continue;
|
|
303
|
+
total += n;
|
|
304
|
+
count++;
|
|
305
|
+
if (n < min) min = n;
|
|
306
|
+
if (n > max) max = n;
|
|
307
|
+
}
|
|
267
308
|
if (node.fn === "sum") return finite(total);
|
|
268
|
-
if (!
|
|
269
|
-
if (node.fn === "avg") return finite(total /
|
|
270
|
-
return
|
|
309
|
+
if (!count) return null;
|
|
310
|
+
if (node.fn === "avg") return finite(total / count);
|
|
311
|
+
return node.fn === "min" ? min : max;
|
|
271
312
|
}
|
|
272
313
|
case "binary": {
|
|
273
314
|
const a = run(node.left);
|
|
@@ -294,6 +335,7 @@ function evaluateFormula(ast, read) {
|
|
|
294
335
|
|
|
295
336
|
// src/calculated.ts
|
|
296
337
|
var CALCULATED_TYPE = "calculated";
|
|
338
|
+
var MAX_FORM_FORMULA_LENGTH = 2e4;
|
|
297
339
|
var MAX_CALCULATED_DECIMALS = 10;
|
|
298
340
|
function calculatedProps(field) {
|
|
299
341
|
const p = field.props ?? {};
|
|
@@ -332,23 +374,41 @@ function formatCalculated(value, field) {
|
|
|
332
374
|
return typeof value === "string" ? value : "";
|
|
333
375
|
}
|
|
334
376
|
var MAX_FORMULA_ITEMS = 1e4;
|
|
377
|
+
var MAX_SETTLE_ITEMS = 5e4;
|
|
378
|
+
var extract = (value, column) => column !== void 0 ? value.flatMap((row) => {
|
|
379
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) return [];
|
|
380
|
+
const cell = row[column];
|
|
381
|
+
return typeof cell === "string" || typeof cell === "number" ? [cell] : [];
|
|
382
|
+
}) : value.flatMap((v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean" ? [v] : []);
|
|
383
|
+
var extracted = /* @__PURE__ */ new WeakMap();
|
|
384
|
+
function cachedList(value, column) {
|
|
385
|
+
let byColumn = extracted.get(value);
|
|
386
|
+
if (!byColumn) extracted.set(value, byColumn = /* @__PURE__ */ new Map());
|
|
387
|
+
const key = column ?? "";
|
|
388
|
+
let list = byColumn.get(key);
|
|
389
|
+
if (!list) byColumn.set(key, list = ownFormulaList(extract(value, column)));
|
|
390
|
+
return list;
|
|
391
|
+
}
|
|
392
|
+
function readAnswer(value, column) {
|
|
393
|
+
if (Array.isArray(value)) return cachedList(value, column);
|
|
394
|
+
return column !== void 0 ? null : formulaInput(value);
|
|
395
|
+
}
|
|
335
396
|
function formulaInput(value, column) {
|
|
336
397
|
if (value === void 0 || value === null) return null;
|
|
337
398
|
if (Array.isArray(value) && value.length > MAX_FORMULA_ITEMS) return null;
|
|
338
|
-
if (
|
|
339
|
-
if (!Array.isArray(value)) return null;
|
|
340
|
-
return value.flatMap((row) => {
|
|
341
|
-
if (!row || typeof row !== "object" || Array.isArray(row)) return [];
|
|
342
|
-
const cell = row[column];
|
|
343
|
-
return typeof cell === "string" || typeof cell === "number" ? [cell] : [];
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
if (Array.isArray(value)) {
|
|
347
|
-
return value.flatMap((v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean" ? [v] : []);
|
|
348
|
-
}
|
|
399
|
+
if (Array.isArray(value)) return extract(value, void 0);
|
|
349
400
|
return typeof value === "object" ? null : value;
|
|
350
401
|
}
|
|
402
|
+
function refNodes(node, out = []) {
|
|
403
|
+
if (node.kind === "ref") out.push(node);
|
|
404
|
+
else if (node.kind === "unary") refNodes(node.arg, out);
|
|
405
|
+
else if (node.kind === "binary") refNodes(node.left, out), refNodes(node.right, out);
|
|
406
|
+
else if (node.kind === "if") refNodes(node.test, out), refNodes(node.then, out), refNodes(node.else, out);
|
|
407
|
+
else if (node.kind === "call") for (const a of node.args) refNodes(a, out);
|
|
408
|
+
return out;
|
|
409
|
+
}
|
|
351
410
|
var UNREAD = /* @__PURE__ */ Symbol("unread");
|
|
411
|
+
var OVER = /* @__PURE__ */ Symbol("over");
|
|
352
412
|
function calculator(definition) {
|
|
353
413
|
const compiled = [];
|
|
354
414
|
const idByKey = /* @__PURE__ */ new Map();
|
|
@@ -359,14 +419,39 @@ function calculator(definition) {
|
|
|
359
419
|
for (const block of definition.blocks) {
|
|
360
420
|
if (block.kind !== "field" || !isCalculated(block)) continue;
|
|
361
421
|
const parsed = parseFormula(calculatedProps(block).formula);
|
|
362
|
-
const reads =
|
|
363
|
-
|
|
422
|
+
const reads = [];
|
|
423
|
+
const slots = /* @__PURE__ */ new Map();
|
|
424
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
425
|
+
for (const ref of parsed.ok ? refNodes(parsed.ast) : []) {
|
|
426
|
+
const id = idByKey.get(ref.key);
|
|
427
|
+
const key = `${id ?? ""}\0${ref.column ?? ""}`;
|
|
428
|
+
let slot = byKey.get(key);
|
|
429
|
+
if (slot === void 0) {
|
|
430
|
+
slot = reads.push({ id, column: ref.column, key }) - 1;
|
|
431
|
+
byKey.set(key, slot);
|
|
432
|
+
}
|
|
433
|
+
slots.set(ref, slot);
|
|
434
|
+
}
|
|
435
|
+
compiled.push({ field: block, ast: parsed.ok ? parsed.ast : null, reads, slots, last: null });
|
|
364
436
|
}
|
|
365
437
|
const withCalculated = (values, path) => {
|
|
366
438
|
if (!compiled.length) return values;
|
|
367
439
|
const visible = new Set(path);
|
|
368
440
|
let next = values;
|
|
369
|
-
|
|
441
|
+
let budget = MAX_SETTLE_ITEMS;
|
|
442
|
+
const within = /* @__PURE__ */ new Map();
|
|
443
|
+
const input = ({ id, key }) => {
|
|
444
|
+
if (id === void 0 || !visible.has(id) || !Object.prototype.hasOwnProperty.call(next, id)) return UNREAD;
|
|
445
|
+
const value = next[id];
|
|
446
|
+
if (!Array.isArray(value)) return value;
|
|
447
|
+
let ok = within.get(key);
|
|
448
|
+
if (ok === void 0) {
|
|
449
|
+
ok = value.length <= MAX_FORMULA_ITEMS && value.length <= budget;
|
|
450
|
+
if (ok) budget -= value.length;
|
|
451
|
+
within.set(key, ok);
|
|
452
|
+
}
|
|
453
|
+
return ok ? value : OVER;
|
|
454
|
+
};
|
|
370
455
|
for (const entry of compiled) {
|
|
371
456
|
const { field, ast } = entry;
|
|
372
457
|
const inputs = entry.reads.map(input);
|
|
@@ -374,11 +459,18 @@ function calculator(definition) {
|
|
|
374
459
|
if (entry.last && entry.last.inputs.length === inputs.length && entry.last.inputs.every((v, i) => v === inputs[i])) {
|
|
375
460
|
value = entry.last.value;
|
|
376
461
|
} else {
|
|
462
|
+
const resolved = new Array(inputs.length);
|
|
377
463
|
const read = (ref) => {
|
|
378
|
-
const
|
|
379
|
-
|
|
464
|
+
const slot = entry.slots.get(ref);
|
|
465
|
+
if (slot === void 0) return null;
|
|
466
|
+
let v = resolved[slot];
|
|
467
|
+
if (v === void 0) {
|
|
468
|
+
const raw = inputs[slot];
|
|
469
|
+
v = resolved[slot] = raw === UNREAD ? null : readAnswer(raw, entry.reads[slot].column);
|
|
470
|
+
}
|
|
471
|
+
return v;
|
|
380
472
|
};
|
|
381
|
-
value = ast ? calculatedValue(evaluateFormula(ast, read), field) : void 0;
|
|
473
|
+
value = ast && !inputs.includes(OVER) ? calculatedValue(evaluateFormula(ast, read), field) : void 0;
|
|
382
474
|
entry.last = { inputs, value };
|
|
383
475
|
}
|
|
384
476
|
const current = Object.prototype.hasOwnProperty.call(next, field.id) ? next[field.id] : void 0;
|
|
@@ -1780,9 +1872,15 @@ function parseForm(input) {
|
|
|
1780
1872
|
issue(`${path}.kind`, "Unknown block kind.");
|
|
1781
1873
|
}
|
|
1782
1874
|
});
|
|
1875
|
+
let formulaChars = 0;
|
|
1783
1876
|
input.blocks.forEach((block, i) => {
|
|
1784
1877
|
if (!isObject2(block) || block.kind !== "field" || block.type !== CALCULATED_TYPE) return;
|
|
1785
1878
|
const path = `blocks[${i}].props.formula`;
|
|
1879
|
+
const source = isObject2(block.props) && typeof block.props.formula === "string" ? block.props.formula : "";
|
|
1880
|
+
formulaChars += source.length;
|
|
1881
|
+
if (formulaChars > MAX_FORM_FORMULA_LENGTH && formulaChars - source.length <= MAX_FORM_FORMULA_LENGTH) {
|
|
1882
|
+
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.`);
|
|
1883
|
+
}
|
|
1786
1884
|
if (block.required === true) {
|
|
1787
1885
|
warnings.push({ path: `blocks[${i}].required`, message: "A calculated field is worked out, not answered, so it is never required." });
|
|
1788
1886
|
}
|