@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/server.cjs
CHANGED
|
@@ -34,6 +34,7 @@ function tokenize(source) {
|
|
|
34
34
|
i++;
|
|
35
35
|
while (isDigit(source[i] ?? "")) i++;
|
|
36
36
|
}
|
|
37
|
+
if (isIdStart(source[i] ?? "")) throw new FormulaError("Write a number in digits only, like 1000000.", at);
|
|
37
38
|
const v = Number(source.slice(at, i));
|
|
38
39
|
if (!Number.isFinite(v)) throw new FormulaError("This number is too large.", at);
|
|
39
40
|
tokens.push({ t: "num", v, at });
|
|
@@ -232,6 +233,27 @@ function numbersIn(values, out = []) {
|
|
|
232
233
|
}
|
|
233
234
|
return out;
|
|
234
235
|
}
|
|
236
|
+
var listTotals = /* @__PURE__ */ new WeakMap();
|
|
237
|
+
var ownLists = /* @__PURE__ */ new WeakSet();
|
|
238
|
+
function ownFormulaList(list) {
|
|
239
|
+
Object.freeze(list);
|
|
240
|
+
ownLists.add(list);
|
|
241
|
+
return list;
|
|
242
|
+
}
|
|
243
|
+
function totalsOf(list) {
|
|
244
|
+
const own2 = ownLists.has(list);
|
|
245
|
+
let t = own2 ? listTotals.get(list) : void 0;
|
|
246
|
+
if (t) return t;
|
|
247
|
+
const nums = numbersIn(list);
|
|
248
|
+
t = { sum: 0, count: nums.length, min: Infinity, max: -Infinity };
|
|
249
|
+
for (const n of nums) {
|
|
250
|
+
t.sum += n;
|
|
251
|
+
if (n < t.min) t.min = n;
|
|
252
|
+
if (n > t.max) t.max = n;
|
|
253
|
+
}
|
|
254
|
+
if (own2) listTotals.set(list, t);
|
|
255
|
+
return t;
|
|
256
|
+
}
|
|
235
257
|
function compare(op, a, b) {
|
|
236
258
|
const blank2 = (v) => v === null || Array.isArray(v) || typeof v === "string" && v.trim() === "";
|
|
237
259
|
if (blank2(a) || blank2(b)) return op === "!=";
|
|
@@ -256,7 +278,7 @@ function evaluateFormula(ast, read) {
|
|
|
256
278
|
case "text":
|
|
257
279
|
return node.value;
|
|
258
280
|
case "ref":
|
|
259
|
-
return read(node
|
|
281
|
+
return read(node);
|
|
260
282
|
case "unary": {
|
|
261
283
|
const n = formulaNumber(scalar(run(node.arg)));
|
|
262
284
|
return n === null ? null : -n;
|
|
@@ -264,12 +286,31 @@ function evaluateFormula(ast, read) {
|
|
|
264
286
|
case "if":
|
|
265
287
|
return truthy(run(node.test)) ? run(node.then) : run(node.else);
|
|
266
288
|
case "call": {
|
|
267
|
-
|
|
268
|
-
|
|
289
|
+
let total = 0;
|
|
290
|
+
let count = 0;
|
|
291
|
+
let min = Infinity;
|
|
292
|
+
let max = -Infinity;
|
|
293
|
+
for (const arg of node.args) {
|
|
294
|
+
const v = run(arg);
|
|
295
|
+
if (Array.isArray(v)) {
|
|
296
|
+
const t = totalsOf(v);
|
|
297
|
+
total += t.sum;
|
|
298
|
+
count += t.count;
|
|
299
|
+
if (t.min < min) min = t.min;
|
|
300
|
+
if (t.max > max) max = t.max;
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const n = formulaNumber(v);
|
|
304
|
+
if (n === null) continue;
|
|
305
|
+
total += n;
|
|
306
|
+
count++;
|
|
307
|
+
if (n < min) min = n;
|
|
308
|
+
if (n > max) max = n;
|
|
309
|
+
}
|
|
269
310
|
if (node.fn === "sum") return finite(total);
|
|
270
|
-
if (!
|
|
271
|
-
if (node.fn === "avg") return finite(total /
|
|
272
|
-
return
|
|
311
|
+
if (!count) return null;
|
|
312
|
+
if (node.fn === "avg") return finite(total / count);
|
|
313
|
+
return node.fn === "min" ? min : max;
|
|
273
314
|
}
|
|
274
315
|
case "binary": {
|
|
275
316
|
const a = run(node.left);
|
|
@@ -296,6 +337,7 @@ function evaluateFormula(ast, read) {
|
|
|
296
337
|
|
|
297
338
|
// src/calculated.ts
|
|
298
339
|
var CALCULATED_TYPE = "calculated";
|
|
340
|
+
var MAX_FORM_FORMULA_LENGTH = 2e4;
|
|
299
341
|
var MAX_CALCULATED_DECIMALS = 10;
|
|
300
342
|
function calculatedProps(field) {
|
|
301
343
|
const p = field.props ?? {};
|
|
@@ -334,23 +376,41 @@ function formatCalculated(value, field) {
|
|
|
334
376
|
return typeof value === "string" ? value : "";
|
|
335
377
|
}
|
|
336
378
|
var MAX_FORMULA_ITEMS = 1e4;
|
|
379
|
+
var MAX_SETTLE_ITEMS = 5e4;
|
|
380
|
+
var extract = (value, column) => column !== void 0 ? value.flatMap((row) => {
|
|
381
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) return [];
|
|
382
|
+
const cell = row[column];
|
|
383
|
+
return typeof cell === "string" || typeof cell === "number" ? [cell] : [];
|
|
384
|
+
}) : 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
|
+
}
|
|
337
398
|
function formulaInput(value, column) {
|
|
338
399
|
if (value === void 0 || value === null) return null;
|
|
339
400
|
if (Array.isArray(value) && value.length > MAX_FORMULA_ITEMS) return null;
|
|
340
|
-
if (
|
|
341
|
-
if (!Array.isArray(value)) return null;
|
|
342
|
-
return value.flatMap((row) => {
|
|
343
|
-
if (!row || typeof row !== "object" || Array.isArray(row)) return [];
|
|
344
|
-
const cell = row[column];
|
|
345
|
-
return typeof cell === "string" || typeof cell === "number" ? [cell] : [];
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
if (Array.isArray(value)) {
|
|
349
|
-
return value.flatMap((v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean" ? [v] : []);
|
|
350
|
-
}
|
|
401
|
+
if (Array.isArray(value)) return extract(value, void 0);
|
|
351
402
|
return typeof value === "object" ? null : value;
|
|
352
403
|
}
|
|
404
|
+
function refNodes(node, out = []) {
|
|
405
|
+
if (node.kind === "ref") out.push(node);
|
|
406
|
+
else if (node.kind === "unary") refNodes(node.arg, out);
|
|
407
|
+
else if (node.kind === "binary") refNodes(node.left, out), refNodes(node.right, out);
|
|
408
|
+
else if (node.kind === "if") refNodes(node.test, out), refNodes(node.then, out), refNodes(node.else, out);
|
|
409
|
+
else if (node.kind === "call") for (const a of node.args) refNodes(a, out);
|
|
410
|
+
return out;
|
|
411
|
+
}
|
|
353
412
|
var UNREAD = /* @__PURE__ */ Symbol("unread");
|
|
413
|
+
var OVER = /* @__PURE__ */ Symbol("over");
|
|
354
414
|
function calculator(definition) {
|
|
355
415
|
const compiled = [];
|
|
356
416
|
const idByKey = /* @__PURE__ */ new Map();
|
|
@@ -361,14 +421,39 @@ function calculator(definition) {
|
|
|
361
421
|
for (const block of definition.blocks) {
|
|
362
422
|
if (block.kind !== "field" || !isCalculated(block)) continue;
|
|
363
423
|
const parsed = parseFormula(calculatedProps(block).formula);
|
|
364
|
-
const reads =
|
|
365
|
-
|
|
424
|
+
const reads = [];
|
|
425
|
+
const slots = /* @__PURE__ */ new Map();
|
|
426
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
427
|
+
for (const ref of parsed.ok ? refNodes(parsed.ast) : []) {
|
|
428
|
+
const id = idByKey.get(ref.key);
|
|
429
|
+
const key = `${id ?? ""}\0${ref.column ?? ""}`;
|
|
430
|
+
let slot = byKey.get(key);
|
|
431
|
+
if (slot === void 0) {
|
|
432
|
+
slot = reads.push({ id, column: ref.column, key }) - 1;
|
|
433
|
+
byKey.set(key, slot);
|
|
434
|
+
}
|
|
435
|
+
slots.set(ref, slot);
|
|
436
|
+
}
|
|
437
|
+
compiled.push({ field: block, ast: parsed.ok ? parsed.ast : null, reads, slots, last: null });
|
|
366
438
|
}
|
|
367
439
|
const withCalculated = (values, path) => {
|
|
368
440
|
if (!compiled.length) return values;
|
|
369
441
|
const visible = new Set(path);
|
|
370
442
|
let next = values;
|
|
371
|
-
|
|
443
|
+
let budget = MAX_SETTLE_ITEMS;
|
|
444
|
+
const within = /* @__PURE__ */ new Map();
|
|
445
|
+
const input = ({ id, key }) => {
|
|
446
|
+
if (id === void 0 || !visible.has(id) || !Object.prototype.hasOwnProperty.call(next, id)) return UNREAD;
|
|
447
|
+
const value = next[id];
|
|
448
|
+
if (!Array.isArray(value)) return value;
|
|
449
|
+
let ok = within.get(key);
|
|
450
|
+
if (ok === void 0) {
|
|
451
|
+
ok = value.length <= MAX_FORMULA_ITEMS && value.length <= budget;
|
|
452
|
+
if (ok) budget -= value.length;
|
|
453
|
+
within.set(key, ok);
|
|
454
|
+
}
|
|
455
|
+
return ok ? value : OVER;
|
|
456
|
+
};
|
|
372
457
|
for (const entry of compiled) {
|
|
373
458
|
const { field, ast } = entry;
|
|
374
459
|
const inputs = entry.reads.map(input);
|
|
@@ -376,11 +461,18 @@ function calculator(definition) {
|
|
|
376
461
|
if (entry.last && entry.last.inputs.length === inputs.length && entry.last.inputs.every((v, i) => v === inputs[i])) {
|
|
377
462
|
value = entry.last.value;
|
|
378
463
|
} else {
|
|
464
|
+
const resolved = new Array(inputs.length);
|
|
379
465
|
const read = (ref) => {
|
|
380
|
-
const
|
|
381
|
-
|
|
466
|
+
const slot = entry.slots.get(ref);
|
|
467
|
+
if (slot === void 0) return null;
|
|
468
|
+
let v = resolved[slot];
|
|
469
|
+
if (v === void 0) {
|
|
470
|
+
const raw = inputs[slot];
|
|
471
|
+
v = resolved[slot] = raw === UNREAD ? null : readAnswer(raw, entry.reads[slot].column);
|
|
472
|
+
}
|
|
473
|
+
return v;
|
|
382
474
|
};
|
|
383
|
-
value = ast ? calculatedValue(evaluateFormula(ast, read), field) : void 0;
|
|
475
|
+
value = ast && !inputs.includes(OVER) ? calculatedValue(evaluateFormula(ast, read), field) : void 0;
|
|
384
476
|
entry.last = { inputs, value };
|
|
385
477
|
}
|
|
386
478
|
const current = Object.prototype.hasOwnProperty.call(next, field.id) ? next[field.id] : void 0;
|
|
@@ -1603,9 +1695,15 @@ function parseForm(input) {
|
|
|
1603
1695
|
issue(`${path}.kind`, "Unknown block kind.");
|
|
1604
1696
|
}
|
|
1605
1697
|
});
|
|
1698
|
+
let formulaChars = 0;
|
|
1606
1699
|
input.blocks.forEach((block, i) => {
|
|
1607
1700
|
if (!isObject2(block) || block.kind !== "field" || block.type !== CALCULATED_TYPE) return;
|
|
1608
1701
|
const path = `blocks[${i}].props.formula`;
|
|
1702
|
+
const source = isObject2(block.props) && typeof block.props.formula === "string" ? block.props.formula : "";
|
|
1703
|
+
formulaChars += source.length;
|
|
1704
|
+
if (formulaChars > MAX_FORM_FORMULA_LENGTH && formulaChars - source.length <= MAX_FORM_FORMULA_LENGTH) {
|
|
1705
|
+
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.`);
|
|
1706
|
+
}
|
|
1609
1707
|
if (block.required === true) {
|
|
1610
1708
|
warnings.push({ path: `blocks[${i}].required`, message: "A calculated field is worked out, not answered, so it is never required." });
|
|
1611
1709
|
}
|