@lotics/app-sdk 0.115.1 → 0.115.3

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.
Files changed (2) hide show
  1. package/dist/index.js +300 -122
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -716,19 +716,12 @@ function setKey(key, answer) {
716
716
  // src/written.ts
717
717
  import { useSyncExternalStore as useSyncExternalStore2 } from "react";
718
718
 
719
- // ../shared/src/add_operator.ts
720
- function addOrConcat(left, right, addNumbers) {
721
- if (typeof left === "string" || typeof right === "string") {
722
- return String(left ?? "") + String(right ?? "");
723
- }
724
- return addNumbers();
725
- }
726
-
727
719
  // ../shared/src/expression_limits.ts
728
720
  var MAX_RESULT_LENGTH = 1e5;
729
721
  var MAX_STRING_LENGTH = 1e7;
730
722
  var MAX_RANDOM_LENGTH = 1e3;
731
723
  var MAX_RUN_BUILT_BYTES = 512 * 1024 * 1024;
724
+ var MAX_IN_PLACE_BYTES = 1024 * 1024;
732
725
  var MAX_PERSISTED_JSON_LENGTH = 64 * 1024 * 1024;
733
726
  var ExpressionLimitError = class extends Error {
734
727
  constructor(message2) {
@@ -808,21 +801,54 @@ function escapedLength(text2) {
808
801
  function isLowSurrogate(code) {
809
802
  return code >= 56320 && code <= 57343;
810
803
  }
804
+ function textOf(value) {
805
+ if (!Array.isArray(value)) return ownText(value);
806
+ const parts = [];
807
+ walkJoined(value, (text2) => {
808
+ parts.push(text2);
809
+ return true;
810
+ });
811
+ return parts.join("");
812
+ }
811
813
  function textLengthUpTo(value, max3) {
812
- return textLength(value, max3, /* @__PURE__ */ new Set());
813
- }
814
- function textLength(value, max3, joining) {
815
- if (!Array.isArray(value)) return String(value).length;
816
- if (joining.has(value)) return 0;
817
- joining.add(value);
818
- let length2 = Math.max(0, value.length - 1);
819
- for (let index = 0; index < value.length && length2 <= max3; index++) {
820
- const element = value[index];
821
- if (element != null) length2 += textLength(element, max3 - length2, joining);
822
- }
823
- joining.delete(value);
814
+ if (!Array.isArray(value)) return ownText(value).length;
815
+ let length2 = 0;
816
+ walkJoined(value, (text2) => {
817
+ length2 += text2.length;
818
+ return length2 <= max3;
819
+ });
824
820
  return length2;
825
821
  }
822
+ function ownText(value) {
823
+ if (typeof value === "string") return value;
824
+ if (value instanceof Date) return Date.prototype.toString.call(value);
825
+ if (typeof value === "object" && value !== null) return Object.prototype.toString.call(value);
826
+ return String(value);
827
+ }
828
+ function walkJoined(array2, write2) {
829
+ const joining = /* @__PURE__ */ new Set([array2]);
830
+ const stack = [{ array: array2, at: 0 }];
831
+ while (stack.length > 0) {
832
+ const frame2 = stack[stack.length - 1];
833
+ if (frame2.at >= frame2.array.length) {
834
+ joining.delete(frame2.array);
835
+ stack.pop();
836
+ continue;
837
+ }
838
+ if (frame2.at > 0 && !write2(",")) return;
839
+ const element = frame2.array[frame2.at];
840
+ frame2.at += 1;
841
+ if (element === null || element === void 0) continue;
842
+ if (Array.isArray(element)) {
843
+ if (!joining.has(element)) {
844
+ joining.add(element);
845
+ stack.push({ array: element, at: 0 });
846
+ }
847
+ continue;
848
+ }
849
+ if (!write2(ownText(element))) return;
850
+ }
851
+ }
826
852
  function jsonPreview(value, max3) {
827
853
  if (typeof value === "string") {
828
854
  const text2 = JSON.stringify(value.slice(0, max3));
@@ -832,6 +858,14 @@ function jsonPreview(value, max3) {
832
858
  return Array.isArray(value) ? `[\u2026 ${value.length} items]` : "{\u2026}";
833
859
  }
834
860
 
861
+ // ../shared/src/add_operator.ts
862
+ function addOrConcat(left, right, addNumbers) {
863
+ if (typeof left === "string" || typeof right === "string") {
864
+ return textOf(left ?? "") + textOf(right ?? "");
865
+ }
866
+ return addNumbers();
867
+ }
868
+
835
869
  // ../../node_modules/jexpr/lib/constants.js
836
870
  var KEYWORDS = ["this"];
837
871
  var UNARY_OPERATORS = ["+", "-", "!"];
@@ -1691,6 +1725,16 @@ __export(expression_array_exports, {
1691
1725
  unique: () => unique,
1692
1726
  uniqueBy: () => uniqueBy
1693
1727
  });
1728
+
1729
+ // ../shared/src/own_property.ts
1730
+ function ownMember(value, key) {
1731
+ return Object.hasOwn(value, key) ? Reflect.get(value, key) : void 0;
1732
+ }
1733
+ function defineOwn(target, key, value) {
1734
+ Object.defineProperty(target, key, { value, writable: true, enumerable: true, configurable: true });
1735
+ }
1736
+
1737
+ // ../shared/src/expression_array.ts
1694
1738
  function getField(obj, field) {
1695
1739
  if (obj == null) return void 0;
1696
1740
  const parts = field.split(".");
@@ -1698,10 +1742,17 @@ function getField(obj, field) {
1698
1742
  for (const part of parts) {
1699
1743
  if (value == null) return void 0;
1700
1744
  if (typeof value !== "object") return void 0;
1701
- value = value[part];
1745
+ value = ownMember(value, part);
1702
1746
  }
1703
1747
  return value;
1704
1748
  }
1749
+ function countInto(result, key) {
1750
+ defineOwn(result, key, (Object.hasOwn(result, key) ? result[key] : 0) + 1);
1751
+ }
1752
+ function groupInto(result, key, item) {
1753
+ if (Object.hasOwn(result, key)) result[key].push(item);
1754
+ else defineOwn(result, key, [item]);
1755
+ }
1705
1756
  function compareValues(a, b) {
1706
1757
  if (a == null && b == null) return 0;
1707
1758
  if (a == null) return 1;
@@ -1863,7 +1914,7 @@ function countBy(arr, fieldOrCb) {
1863
1914
  const result2 = {};
1864
1915
  for (const val of keys2) {
1865
1916
  const key = val == null ? "null" : String(val);
1866
- result2[key] = (result2[key] || 0) + 1;
1917
+ countInto(result2, key);
1867
1918
  }
1868
1919
  return result2;
1869
1920
  });
@@ -1876,7 +1927,7 @@ function countBy(arr, fieldOrCb) {
1876
1927
  for (const item of arr) {
1877
1928
  const val = getField(item, field);
1878
1929
  const key = val == null ? "null" : String(val);
1879
- result[key] = (result[key] || 0) + 1;
1930
+ countInto(result, key);
1880
1931
  }
1881
1932
  return result;
1882
1933
  }
@@ -1996,7 +2047,7 @@ function groupBy(arr, fieldOrCb) {
1996
2047
  const result2 = {};
1997
2048
  for (let i = 0; i < arr.length; i++) {
1998
2049
  const key = keys2[i] == null ? "null" : String(keys2[i]);
1999
- (result2[key] ??= []).push(arr[i]);
2050
+ groupInto(result2, key, arr[i]);
2000
2051
  }
2001
2052
  return result2;
2002
2053
  });
@@ -2009,10 +2060,7 @@ function groupBy(arr, fieldOrCb) {
2009
2060
  for (const item of arr) {
2010
2061
  const val = getField(item, field);
2011
2062
  const key = val == null ? "null" : String(val);
2012
- if (!result[key]) {
2013
- result[key] = [];
2014
- }
2015
- result[key].push(item);
2063
+ groupInto(result, key, item);
2016
2064
  }
2017
2065
  return result;
2018
2066
  }
@@ -2946,7 +2994,7 @@ function getNestedValue(obj, path) {
2946
2994
  for (const part of parts) {
2947
2995
  if (value == null) return void 0;
2948
2996
  if (typeof value !== "object") return void 0;
2949
- value = value[part];
2997
+ value = ownMember(value, part);
2950
2998
  }
2951
2999
  return value;
2952
3000
  }
@@ -3002,9 +3050,7 @@ function pick(obj, fields) {
3002
3050
  const objRecord = obj;
3003
3051
  for (const field of fields) {
3004
3052
  const fieldStr = String(field);
3005
- if (fieldStr in objRecord) {
3006
- result[fieldStr] = objRecord[fieldStr];
3007
- }
3053
+ if (Object.hasOwn(objRecord, fieldStr)) defineOwn(result, fieldStr, objRecord[fieldStr]);
3008
3054
  }
3009
3055
  return result;
3010
3056
  }
@@ -3022,10 +3068,8 @@ function omit(obj, fields) {
3022
3068
  const result = {};
3023
3069
  const objRecord = obj;
3024
3070
  const fieldsSet = new Set(fields.map(String));
3025
- for (const key in objRecord) {
3026
- if (!fieldsSet.has(key)) {
3027
- result[key] = objRecord[key];
3028
- }
3071
+ for (const key of Object.keys(objRecord)) {
3072
+ if (!fieldsSet.has(key)) defineOwn(result, key, objRecord[key]);
3029
3073
  }
3030
3074
  return result;
3031
3075
  }
@@ -3153,7 +3197,7 @@ function toString(val) {
3153
3197
  if (typeof val !== "string" && textLengthUpTo(val, MAX_STRING_LENGTH) > MAX_STRING_LENGTH) {
3154
3198
  throw new ExpressionLimitError(`toString: result too long (max ${MAX_STRING_LENGTH} characters)`);
3155
3199
  }
3156
- return String(val);
3200
+ return textOf(val);
3157
3201
  }
3158
3202
  function isNull(val) {
3159
3203
  return val === null || val === void 0;
@@ -21918,20 +21962,31 @@ function sameReading(a, b) {
21918
21962
  function readingWords(reading) {
21919
21963
  return reading === "link" ? "a link" : JSON.stringify(reading);
21920
21964
  }
21921
- function settledReading(written, stored) {
21922
- const held2 = stored === void 0 ? void 0 : stored.byKeys ?? stored.stated;
21923
- const { stated, byKeys } = written;
21924
- const parted = partedReadings(written);
21925
- if (parted === void 0) return checkedReading(stated ?? byKeys ?? held2, held2);
21926
- const changed2 = stored === void 0 ? void 0 : sameReading(stated, stored.stated) ? byKeys : sameReading(byKeys, stored.byKeys) ? stated : void 0;
21927
- return changed2 === void 0 ? { refusal: parted } : checkedReading(changed2, held2);
21928
- }
21929
- function partedReadings(written) {
21930
- const { stated, byKeys } = written;
21931
- if (stated === void 0 || byKeys === void 0 || sameReading(stated, byKeys)) return void 0;
21932
- const key = stated === "link" ? "link" : "notation";
21965
+ function heldReading(stored) {
21966
+ return stored.byKeys ?? stored.stated;
21967
+ }
21968
+ function leftReading(written, stored) {
21969
+ if (stored === void 0) return { changed: written.stated, left: written.stated };
21970
+ const changed2 = sameReading(written.stated, stored.stated) ? void 0 : written.stated;
21971
+ return { changed: changed2, left: changed2 ?? heldReading(stored) ?? written.stated };
21972
+ }
21973
+ function keysRefusal(written, stored) {
21974
+ const { byKeys } = written;
21975
+ if (byKeys === void 0 || stored !== void 0 && sameReading(byKeys, stored.byKeys)) return void 0;
21976
+ const { changed: changed2, left } = leftReading(written, stored);
21977
+ if (sameReading(byKeys, left ?? DECIMAL)) return void 0;
21933
21978
  const keys2 = KEYS_BEFORE_NOTATION.flatMap((name) => written.keys[name] ? [`${name} ${JSON.stringify(written.keys[name])}`] : []);
21934
- return `"${key}" says ${readingWords(stated)} and ${keys2.join(", ")} say ${readingWords(byKeys)} \u2014 state "${key}" alone`;
21979
+ if (changed2 !== void 0) {
21980
+ const key = changed2 === "link" ? "link" : "notation";
21981
+ return `"${key}" says ${readingWords(changed2)} and ${keys2.join(", ")} ${keys2.length === 1 ? "says" : "say"} ${readingWords(byKeys)}, and the keys before "notation" are no longer read \u2014 state "${key}" alone`;
21982
+ }
21983
+ const state = byKeys === "link" ? '"link": true' : `"notation": ${JSON.stringify(byKeys)}`;
21984
+ return `${keys2.join(", ")} ${keys2.length === 1 ? "is" : "are"} no longer read \u2014 state ${state}`;
21985
+ }
21986
+ function settledReading(written, stored) {
21987
+ const refused = keysRefusal(written, stored);
21988
+ if (refused !== void 0) return { refusal: refused };
21989
+ return checkedReading(leftReading(written, stored).left ?? written.byKeys, stored === void 0 ? void 0 : heldReading(stored));
21935
21990
  }
21936
21991
  function checkedReading(reading, held2) {
21937
21992
  const refused = changedCurrencyRefusal(reading, held2);
@@ -24900,7 +24955,7 @@ function figureIssue(path) {
24900
24955
  for (const { key, message: message2 } of figureKeysRefusals(value)) ctx.addIssue({ code: "custom", message: message2, path: [...path, key] });
24901
24956
  };
24902
24957
  }
24903
- var BEFORE_NOTATION = 'Before "notation", and read as it: state "notation" instead.';
24958
+ var BEFORE_NOTATION = 'Replaced by "notation", and never read: naming another reading in it is refused.';
24904
24959
  function notationStatedIssue(path) {
24905
24960
  return (value, ctx) => {
24906
24961
  if (value.notation === void 0) return;
@@ -27330,6 +27385,12 @@ var WorkflowExpressionBudgetError = class extends Error {
27330
27385
  }
27331
27386
  budget;
27332
27387
  };
27388
+ var ExpressionNotCheapError = class extends Error {
27389
+ constructor() {
27390
+ super(`The expression walks or builds more than ${MAX_IN_PLACE_BYTES / 1024 / 1024}MB at once, past what an evaluation in place may.`);
27391
+ this.name = "ExpressionNotCheapError";
27392
+ }
27393
+ };
27333
27394
  function budgetMessage(budget) {
27334
27395
  switch (budget.exceeded) {
27335
27396
  case "bytes":
@@ -27377,6 +27438,66 @@ function builtSize(value) {
27377
27438
  if (typeof value === "object" && value !== null) return OBJECT_BYTES + Object.keys(value).length * SLOT_BYTES;
27378
27439
  return 0;
27379
27440
  }
27441
+ var LONGEST_NUMBER_TEXT = 24;
27442
+ function numberTextLength(value) {
27443
+ if (!Number.isSafeInteger(value)) return LONGEST_NUMBER_TEXT;
27444
+ let length2 = value < 0 ? 2 : 1;
27445
+ for (let rest = Math.abs(value); rest >= 10; rest = Math.floor(rest / 10)) length2 += 1;
27446
+ return length2;
27447
+ }
27448
+ function expandedSize(value, max3) {
27449
+ let bytes = 0;
27450
+ const pending = [value];
27451
+ while (pending.length > 0 && bytes <= max3) {
27452
+ const member = pending.pop();
27453
+ if (typeof member === "string") bytes += member.length * CHAR_BYTES;
27454
+ else if (typeof member === "number") bytes += numberTextLength(member) * CHAR_BYTES;
27455
+ else if (typeof member === "boolean") bytes += (member ? 4 : 5) * CHAR_BYTES;
27456
+ else if (typeof member === "bigint") bytes += String(member).length * CHAR_BYTES;
27457
+ else if (typeof member !== "object" || member === null) continue;
27458
+ else if (member instanceof Map) {
27459
+ bytes += OBJECT_BYTES + member.size * 2 * SLOT_BYTES;
27460
+ if (bytes <= max3) for (const [key, held2] of member) pending.push(key, held2);
27461
+ } else if (member instanceof Set) {
27462
+ bytes += OBJECT_BYTES + member.size * SLOT_BYTES;
27463
+ if (bytes <= max3) for (const held2 of member) pending.push(held2);
27464
+ } else if (Array.isArray(member)) {
27465
+ bytes += OBJECT_BYTES + member.length * SLOT_BYTES;
27466
+ if (bytes <= max3) for (let at2 = 0; at2 < member.length; at2++) pending.push(member[at2]);
27467
+ } else {
27468
+ const known = ownKeyBytes.get(member);
27469
+ if (known !== void 0 && bytes + known > max3) {
27470
+ bytes += known;
27471
+ continue;
27472
+ }
27473
+ const keys2 = Object.keys(member);
27474
+ let own = OBJECT_BYTES + keys2.length * SLOT_BYTES;
27475
+ for (const key of keys2) own += key.length * CHAR_BYTES;
27476
+ ownKeyBytes.set(member, own);
27477
+ bytes += own;
27478
+ if (bytes <= max3) for (const key of keys2) pending.push(Reflect.get(member, key));
27479
+ }
27480
+ }
27481
+ return bytes;
27482
+ }
27483
+ var ownKeyBytes = /* @__PURE__ */ new WeakMap();
27484
+ var OBJECT_TEXT_LENGTH = 64;
27485
+ function writtenSize(value, max3) {
27486
+ let bytes = 0;
27487
+ const pending = [value];
27488
+ while (pending.length > 0 && bytes <= max3) {
27489
+ const member = pending.pop();
27490
+ if (typeof member === "string") bytes += member.length * CHAR_BYTES;
27491
+ else if (typeof member === "number") bytes += numberTextLength(member) * CHAR_BYTES;
27492
+ else if (typeof member === "boolean") bytes += (member ? 4 : 5) * CHAR_BYTES;
27493
+ else if (typeof member === "bigint") bytes += String(member).length * CHAR_BYTES;
27494
+ else if (Array.isArray(member)) {
27495
+ bytes += OBJECT_BYTES + member.length * SLOT_BYTES;
27496
+ if (bytes <= max3) for (let at2 = 0; at2 < member.length; at2++) pending.push(member[at2]);
27497
+ } else if (typeof member === "object" && member !== null) bytes += OBJECT_TEXT_LENGTH * CHAR_BYTES;
27498
+ }
27499
+ return bytes;
27500
+ }
27380
27501
  var PARSED_NODE_BYTES = 40;
27381
27502
  var OPEN_BRACKET = "[".charCodeAt(0);
27382
27503
  var OPEN_BRACE = "{".charCodeAt(0);
@@ -27390,6 +27511,22 @@ function parsedSize(text2) {
27390
27511
  }
27391
27512
  return text2.length * CHAR_BYTES + nodes * PARSED_NODE_BYTES;
27392
27513
  }
27514
+ function assertCheap(budget, ...values2) {
27515
+ if (!budget.bounded) return;
27516
+ let bytes = 0;
27517
+ for (const value of values2) {
27518
+ bytes += writtenSize(value, MAX_IN_PLACE_BYTES - bytes);
27519
+ if (bytes > MAX_IN_PLACE_BYTES) throw new ExpressionNotCheapError();
27520
+ }
27521
+ }
27522
+ function charge(ctx, bytes) {
27523
+ const budget = ctx.evaluation_budget;
27524
+ assertUnsettled(budget);
27525
+ if (Date.now() > ctx.run_budget.deadline_ms) throw new WorkflowTimeBudgetError(ctx.run_budget.subject, ctx.run_budget.budget_ms);
27526
+ if (budget.bounded && budget.built + bytes > MAX_IN_PLACE_BYTES) throw new ExpressionNotCheapError();
27527
+ budget.built += bytes;
27528
+ chargeBuilt(ctx.run_budget, bytes);
27529
+ }
27393
27530
  function callbackWorkers() {
27394
27531
  let free = LINK_FETCH_CONCURRENCY - 1;
27395
27532
  return {
@@ -27403,10 +27540,16 @@ function callbackWorkers() {
27403
27540
  }
27404
27541
  };
27405
27542
  }
27406
- function evaluateWorkflowExpression(expr2, ctx) {
27407
- const budget = { workers: callbackWorkers(), settled: false };
27543
+ function evaluateWorkflowExpression(expr2, ctx, options = {}) {
27544
+ const budget = { workers: callbackWorkers(), bounded: options.bounded === true, built: 0, settled: false };
27408
27545
  return evaluateExpression(expr2, { ...ctx, evaluation_budget: budget }).finally(() => {
27409
27546
  budget.settled = true;
27547
+ }).then((answer) => {
27548
+ if (budget.bounded && expandedSize(answer, MAX_IN_PLACE_BYTES) > MAX_IN_PLACE_BYTES) throw new ExpressionNotCheapError();
27549
+ return answer;
27550
+ }).catch((error51) => {
27551
+ if (budget.bounded && ctx.run_budget.host === "shared") ctx.run_budget.built_bytes -= budget.built;
27552
+ throw error51;
27410
27553
  });
27411
27554
  }
27412
27555
  async function evaluateExpression(expr2, ctx) {
@@ -27432,15 +27575,13 @@ async function evaluateExpression(expr2, ctx) {
27432
27575
  return evaluateLambda(expr2.params, expr2.body, ctx, []);
27433
27576
  case "array_literal": {
27434
27577
  const elements = await Promise.all(expr2.elements.map((el) => evaluateExpression(el, ctx)));
27435
- chargeBuilt(ctx.run_budget, builtSize(elements));
27578
+ charge(ctx, builtSize(elements));
27436
27579
  return elements;
27437
27580
  }
27438
27581
  case "object_literal": {
27439
27582
  const out = {};
27440
- for (const entry of expr2.entries) {
27441
- out[entry.key] = await evaluateExpression(entry.value, ctx);
27442
- }
27443
- chargeBuilt(ctx.run_budget, builtSize(out));
27583
+ for (const entry of expr2.entries) defineOwn(out, entry.key, await evaluateExpression(entry.value, ctx));
27584
+ charge(ctx, builtSize(out));
27444
27585
  return out;
27445
27586
  }
27446
27587
  default:
@@ -27461,9 +27602,7 @@ function assertUnsettled(budget) {
27461
27602
  function evaluateLambda(params, body, ctx, items) {
27462
27603
  const call = async (...args) => {
27463
27604
  const frame2 = {};
27464
- for (let i = 0; i < params.length; i++) {
27465
- frame2[params[i]] = args[i];
27466
- }
27605
+ for (let i = 0; i < params.length; i++) defineOwn(frame2, params[i], args[i]);
27467
27606
  const stack = ctx.lambda_params ?? [];
27468
27607
  const newStack = [...stack, frame2];
27469
27608
  const innerCtx = {
@@ -27531,16 +27670,14 @@ function resolveReadRoot(source, ctx) {
27531
27670
  case "foreach_index":
27532
27671
  return resolveForeachFrame(ctx, void 0, "foreach_index").index;
27533
27672
  case "step_output": {
27534
- if (!(source.step_id in ctx.step_outputs)) {
27673
+ if (!Object.hasOwn(ctx.step_outputs, source.step_id)) {
27535
27674
  throw new WorkflowEvalError(
27536
27675
  `Read from step_output "${source.step_id}", but no such step has run`,
27537
27676
  { kind: "read", at: "step_output", extra: { step_id: source.step_id } }
27538
27677
  );
27539
27678
  }
27540
27679
  const stored = ctx.step_outputs[source.step_id];
27541
- if (stored && typeof stored === "object" && "output" in stored) {
27542
- return stored.output;
27543
- }
27680
+ if (stored && typeof stored === "object" && Object.hasOwn(stored, "output")) return ownMember(stored, "output");
27544
27681
  return stored;
27545
27682
  }
27546
27683
  case "runtime":
@@ -27550,7 +27687,7 @@ function resolveReadRoot(source, ctx) {
27550
27687
  if (stack !== void 0) {
27551
27688
  for (let i = stack.length - 1; i >= 0; i--) {
27552
27689
  const frame2 = stack[i];
27553
- if (source.name in frame2) return frame2[source.name];
27690
+ if (Object.hasOwn(frame2, source.name)) return frame2[source.name];
27554
27691
  }
27555
27692
  }
27556
27693
  throw new WorkflowEvalError(
@@ -27563,7 +27700,7 @@ function resolveReadRoot(source, ctx) {
27563
27700
  if (stack !== void 0) {
27564
27701
  for (let i = stack.length - 1; i >= 0; i--) {
27565
27702
  const frame2 = stack[i];
27566
- if (source.name in frame2) return frame2[source.name];
27703
+ if (Object.hasOwn(frame2, source.name)) return frame2[source.name];
27567
27704
  }
27568
27705
  }
27569
27706
  throw new WorkflowEvalError(
@@ -27665,63 +27802,74 @@ async function coerceFieldDisplay(value, field, ctx) {
27665
27802
  if (value === null || value === void 0) return value;
27666
27803
  switch (field.type) {
27667
27804
  case "select":
27668
- return coerceSelectDisplay(value, field.options);
27805
+ return coerceSelectDisplay(value, field.options, ctx);
27669
27806
  // One of its own options, named as a select's is.
27670
27807
  case "formula":
27671
- return field.formula.options === void 0 ? value : coerceSelectDisplay(value, field.formula.options);
27808
+ return field.formula.options === void 0 ? value : coerceSelectDisplay(value, field.formula.options, ctx);
27672
27809
  case "select_record_link":
27673
27810
  return coerceRecordLinkDisplay(value, field, ctx);
27674
27811
  case "select_member":
27675
27812
  return coerceMemberDisplay(value, ctx);
27676
27813
  case "lookup":
27677
- return field.lookup_field_options ? coerceOptionKeysDeep(value, field.lookup_field_options) : value;
27814
+ return field.lookup_field_options ? coerceOptionKeysDeep(value, field.lookup_field_options, ctx) : value;
27678
27815
  default:
27679
27816
  return value;
27680
27817
  }
27681
27818
  }
27682
- function coerceSelectDisplay(value, options) {
27819
+ function coerceSelectDisplay(value, options, ctx) {
27683
27820
  const optionMap = new Map(options.map((o) => [o.key, o.name]));
27684
27821
  const resolve = (key) => {
27685
27822
  if (typeof key !== "string") return "";
27686
27823
  return optionMap.get(key) ?? key;
27687
27824
  };
27688
- if (Array.isArray(value)) return value.map(resolve);
27689
- return resolve(value);
27825
+ if (!Array.isArray(value)) return resolve(value);
27826
+ assertCheap(ctx.evaluation_budget, value);
27827
+ const names = value.map(resolve);
27828
+ charge(ctx, builtSize(names));
27829
+ return names;
27690
27830
  }
27691
- function coerceOptionKeysDeep(value, options) {
27831
+ function coerceOptionKeysDeep(value, options, ctx) {
27832
+ assertCheap(ctx.evaluation_budget, value);
27692
27833
  const optionMap = new Map(options.map((o) => [o.key, o.name]));
27693
27834
  const resolve = (v) => {
27694
- if (Array.isArray(v)) return v.map(resolve);
27835
+ if (Array.isArray(v)) {
27836
+ const resolved = v.map(resolve);
27837
+ charge(ctx, builtSize(resolved));
27838
+ return resolved;
27839
+ }
27695
27840
  if (typeof v === "string") return optionMap.get(v) ?? v;
27696
27841
  return v;
27697
27842
  };
27698
27843
  return resolve(value);
27699
27844
  }
27700
27845
  async function coerceMemberDisplay(value, ctx) {
27846
+ if (Array.isArray(value)) assertCheap(ctx.evaluation_budget, value);
27701
27847
  const ids = Array.isArray(value) ? value.filter((v) => typeof v === "string") : typeof value === "string" ? [value] : [];
27702
27848
  if (ids.length === 0) return Array.isArray(value) ? [] : "";
27703
27849
  const nameMap = await ctx.member_lookup(ids);
27704
27850
  const names = new Set(nameMap.values());
27705
27851
  const resolve = (id) => nameMap.get(id) ?? (names.has(id) ? id : "");
27706
- return Array.isArray(value) ? value.map((v) => resolve(String(v))) : resolve(ids[0]);
27852
+ if (!Array.isArray(value)) return resolve(ids[0]);
27853
+ const shown = value.map((v) => resolve(textOf(v)));
27854
+ charge(ctx, builtSize(shown));
27855
+ return shown;
27707
27856
  }
27708
27857
  async function coerceRecordLinkDisplay(value, field, ctx) {
27709
27858
  if (!Array.isArray(value)) return value;
27859
+ assertCheap(ctx.evaluation_budget, value);
27710
27860
  const displayFieldKeys = field.display_field_keys ?? [];
27711
- return Promise.all(
27712
- value.map(async (id) => {
27713
- if (typeof id !== "string") return "";
27714
- const data = await fetchRecordData(id, ctx);
27715
- if (!data) return "";
27716
- if (displayFieldKeys.length === 0) {
27717
- return "";
27718
- }
27719
- return displayFieldKeys.map((key) => {
27720
- const v = data[key];
27721
- return v === null || v === void 0 ? "" : String(v);
27722
- }).filter((s) => s !== "").join(" ");
27723
- })
27861
+ const rows = await fetchRecordsBounded(
27862
+ value.map((id) => typeof id === "string" ? id : null),
27863
+ `Showing "${field.key}"`,
27864
+ ctx
27865
+ );
27866
+ const shown = rows.map((row2) => row2 === null ? [] : displayFieldKeys.map((key) => row2[key]));
27867
+ assertCheap(ctx.evaluation_budget, shown);
27868
+ const texts = shown.map(
27869
+ (values2) => values2.filter((v) => v !== null && v !== void 0).map(textOf).filter((text2) => text2 !== "").join(" ")
27724
27870
  );
27871
+ charge(ctx, texts.reduce((bytes, text2) => bytes + builtSize(text2), builtSize(texts)));
27872
+ return texts;
27725
27873
  }
27726
27874
  function advanceTable(segment, currentTable, ctx) {
27727
27875
  if (segment.at === "linked" && currentTable) {
@@ -27751,7 +27899,7 @@ async function walkSegment(value, segment, ctx) {
27751
27899
  return void 0;
27752
27900
  }
27753
27901
  if (typeof value !== "object") return void 0;
27754
- return value[segment.key];
27902
+ return ownMember(value, segment.key);
27755
27903
  }
27756
27904
  case "index": {
27757
27905
  if (value === null || value === void 0) return void 0;
@@ -27761,7 +27909,7 @@ async function walkSegment(value, segment, ctx) {
27761
27909
  }
27762
27910
  if (typeof value === "object") {
27763
27911
  const key = typeof segment.index === "number" ? String(segment.index) : await resolveObjectKeyExpression(segment.index, ctx);
27764
- return value[key];
27912
+ return ownMember(value, key);
27765
27913
  }
27766
27914
  throw new WorkflowEvalError(
27767
27915
  `Cannot index ${typeof value} \u2014 only an array or an object can be read by index.`,
@@ -27779,9 +27927,8 @@ async function resolveObjectKeyExpression(expr2, ctx) {
27779
27927
  throw new WorkflowEvalError(
27780
27928
  // Bounded: the value is whatever the expression produced — a whole record,
27781
27929
  // a page of rows — and the message goes into a persisted execution row and
27782
- // an author-facing error. The type is what names the fault; a prefix is
27783
- // enough to recognize which value it was.
27784
- `Computed object key must evaluate to a string, got ${value === null ? "null" : typeof value}: ${jsonPreview(value, VALUE_PREVIEW_LENGTH)}`,
27930
+ // an author-facing error.
27931
+ `Computed object key must evaluate to a string, got ${describeValue(value)}`,
27785
27932
  { kind: "read", at: "index" }
27786
27933
  );
27787
27934
  }
@@ -27790,7 +27937,7 @@ async function resolveIndexExpression(expr2, ctx, context) {
27790
27937
  const value = await evaluateExpression(expr2, ctx);
27791
27938
  if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
27792
27939
  throw new WorkflowEvalError(
27793
- `Computed ${context} must evaluate to a non-negative integer, got ${value === null ? "null" : typeof value}: ${jsonPreview(value, VALUE_PREVIEW_LENGTH)}`,
27940
+ `Computed ${context} must evaluate to a non-negative integer, got ${describeValue(value)}`,
27794
27941
  { kind: "read", at: "index" }
27795
27942
  );
27796
27943
  }
@@ -27804,17 +27951,17 @@ async function walkLinked(value, segment, ctx) {
27804
27951
  { kind: "read", at: "linked", extra: { via: segment.via } }
27805
27952
  );
27806
27953
  }
27807
- const linkValue = value[segment.via];
27808
- const ids = extractLinkIds(linkValue, segment.via);
27954
+ const ids = linkIds(ownMember(value, segment.via), segment.via);
27809
27955
  if (segment.index === "each") {
27810
- return fetchRecordsBounded(ids, segment.via, ctx);
27956
+ assertCheap(ctx.evaluation_budget, ids);
27957
+ return fetchRecordsBounded(ids.map((id) => assertLinkId(id, segment.via)), `linked(...) on "${segment.via}"`, ctx);
27811
27958
  }
27812
27959
  const idx = typeof segment.index === "number" ? segment.index : await resolveIndexExpression(segment.index, ctx, `linked index via "${segment.via}"`);
27813
27960
  const target = ids[idx];
27814
27961
  if (target === void 0) return void 0;
27815
- return fetchRecordData(target, ctx);
27962
+ return fetchRecordData(assertLinkId(target, segment.via), ctx);
27816
27963
  }
27817
- function extractLinkIds(value, via) {
27964
+ function linkIds(value, via) {
27818
27965
  if (value === void 0 || value === null) return [];
27819
27966
  if (!Array.isArray(value)) {
27820
27967
  throw new WorkflowEvalError(
@@ -27822,16 +27969,17 @@ function extractLinkIds(value, via) {
27822
27969
  { kind: "read", at: "linked", extra: { via } }
27823
27970
  );
27824
27971
  }
27825
- for (const item of value) {
27826
- if (typeof item !== "string") {
27827
- throw new WorkflowEvalError(
27828
- `Linked field "${via}" contains a non-string item \u2014 record data must be normalized before evaluation`,
27829
- { kind: "read", at: "linked", extra: { via, item_type: typeof item } }
27830
- );
27831
- }
27832
- }
27833
27972
  return value;
27834
27973
  }
27974
+ function assertLinkId(item, via) {
27975
+ if (typeof item !== "string") {
27976
+ throw new WorkflowEvalError(
27977
+ `Linked field "${via}" contains a non-string item \u2014 record data must be normalized before evaluation`,
27978
+ { kind: "read", at: "linked", extra: { via, item_type: typeof item } }
27979
+ );
27980
+ }
27981
+ return item;
27982
+ }
27835
27983
  async function fetchRecordData(recordId, ctx) {
27836
27984
  if (ctx.record_cache.has(recordId)) {
27837
27985
  return ctx.record_cache.get(recordId) ?? null;
@@ -27843,18 +27991,18 @@ async function fetchRecordData(recordId, ctx) {
27843
27991
  }
27844
27992
  var LINK_FETCH_CONCURRENCY = 20;
27845
27993
  var MAX_LINK_FAN_OUT = 1e4;
27846
- async function fetchRecordsBounded(ids, via, ctx) {
27994
+ async function fetchRecordsBounded(ids, reading, ctx) {
27847
27995
  if (ids.length > MAX_LINK_FAN_OUT) {
27848
27996
  throw new WorkflowEvalError(
27849
- `linked(...) on "${via}" would fetch ${ids.length} rows, over the cap of ${MAX_LINK_FAN_OUT}. Index one row \u2014 linked(...)[i] \u2014 or narrow the link.`,
27850
- { kind: "read", at: "linked", extra: { via } }
27997
+ `${reading} would fetch ${ids.length} rows, over the cap of ${MAX_LINK_FAN_OUT}. Index one row \u2014 linked(...)[i] \u2014 or narrow the link.`,
27998
+ { kind: "read", at: "linked", extra: { reading } }
27851
27999
  );
27852
28000
  }
27853
28001
  const results = [];
27854
28002
  for (let i = 0; i < ids.length; i += LINK_FETCH_CONCURRENCY) {
27855
28003
  for (let pause = checkClock(ctx); pause !== null; pause = checkClock(ctx)) await pause;
27856
28004
  const chunk = ids.slice(i, i + LINK_FETCH_CONCURRENCY);
27857
- results.push(...await Promise.all(chunk.map((id) => fetchRecordData(id, ctx))));
28005
+ results.push(...await Promise.all(chunk.map((id) => id === null ? null : fetchRecordData(id, ctx))));
27858
28006
  }
27859
28007
  return results;
27860
28008
  }
@@ -27874,12 +28022,12 @@ async function evaluateCall(fn, args, ctx) {
27874
28022
  );
27875
28023
  for (let pause = checkClock(ctx); pause !== null; pause = checkClock(ctx)) await pause;
27876
28024
  const cost = helperCost(fn);
27877
- if (cost === "parses") chargeBuilt(ctx.run_budget, parsedSize(evaluatedArgs[0]));
28025
+ if (cost === "parses") charge(ctx, parsedSize(evaluatedArgs[0]));
27878
28026
  const answer = await helper(...evaluatedArgs);
27879
28027
  if (cost !== "builds" && cost !== "nested") return answer;
27880
28028
  const handedBack = evaluatedArgs.indexOf(answer);
27881
28029
  if (handedBack !== -1) return evaluatedArgs[handedBack];
27882
- chargeBuilt(ctx.run_budget, builtSize(answer) + (cost === "nested" ? builtSize(evaluatedArgs[0]) : 0));
28030
+ charge(ctx, builtSize(answer) + (cost === "nested" ? builtSize(evaluatedArgs[0]) : 0));
27883
28031
  return answer;
27884
28032
  }
27885
28033
  async function evaluateBinary(op, leftExpr, rightExpr, ctx) {
@@ -27900,9 +28048,9 @@ async function evaluateBinary(op, leftExpr, rightExpr, ctx) {
27900
28048
  for (let pause = checkClock(ctx); pause !== null; pause = checkClock(ctx)) await pause;
27901
28049
  switch (op) {
27902
28050
  case "eq":
27903
- return looseEq(left, right);
27904
28051
  case "neq":
27905
- return !looseEq(left, right);
28052
+ assertCheap(ctx.evaluation_budget, looselyComparedText(left, right));
28053
+ return looseEq(left, right) === (op === "eq");
27906
28054
  case "seq":
27907
28055
  return strictEq(left, right);
27908
28056
  case "sneq":
@@ -27917,10 +28065,11 @@ async function evaluateBinary(op, leftExpr, rightExpr, ctx) {
27917
28065
  return compareOrdered(left, right) <= 0;
27918
28066
  case "add": {
27919
28067
  if (typeof left === "string" || typeof right === "string") {
28068
+ assertCheap(ctx.evaluation_budget, left, right);
27920
28069
  assertTextWithin("+", typeof left === "string" ? null : left, typeof right === "string" ? null : right);
27921
28070
  }
27922
28071
  const sum2 = addOrConcat(left, right, () => assertNumber(left, "add", "left") + assertNumber(right, "add", "right"));
27923
- chargeBuilt(ctx.run_budget, builtSize(sum2));
28072
+ charge(ctx, builtSize(sum2));
27924
28073
  return sum2;
27925
28074
  }
27926
28075
  case "sub":
@@ -27944,8 +28093,21 @@ async function evaluateBinary(op, leftExpr, rightExpr, ctx) {
27944
28093
  }
27945
28094
  }
27946
28095
  function looseEq(a, b) {
28096
+ if (isComposite(a) && b !== null && b !== void 0 && !isComposite(b)) return looseEq(textOf(a), b);
28097
+ if (isComposite(b) && a !== null && a !== void 0 && !isComposite(a)) return looseEq(a, textOf(b));
27947
28098
  return a == b;
27948
28099
  }
28100
+ function isComposite(value) {
28101
+ return typeof value === "object" && value !== null;
28102
+ }
28103
+ function looselyComparedText(a, b) {
28104
+ if (Array.isArray(a) && isScalar(b)) return a;
28105
+ if (Array.isArray(b) && isScalar(a)) return b;
28106
+ return null;
28107
+ }
28108
+ function isScalar(value) {
28109
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint";
28110
+ }
27949
28111
  function strictEq(a, b) {
27950
28112
  return a === b;
27951
28113
  }
@@ -27979,8 +28141,14 @@ function describeValue(value) {
27979
28141
  if (typeof value === "string") return `string ${jsonPreview(value, VALUE_PREVIEW_LENGTH)}`;
27980
28142
  if (typeof value === "number") return `number ${String(value)}`;
27981
28143
  if (typeof value === "boolean") return `boolean ${String(value)}`;
27982
- const kind = Array.isArray(value) ? "array" : typeof value;
27983
- return textLengthUpTo(value, VALUE_PREVIEW_LENGTH) <= VALUE_PREVIEW_LENGTH ? `${kind} ${String(value)}` : kind;
28144
+ if (!Array.isArray(value)) return typeof value;
28145
+ let length2 = 0;
28146
+ for (let at2 = 0; at2 < value.length && length2 <= VALUE_PREVIEW_LENGTH; at2++) {
28147
+ const element = value[at2];
28148
+ if (element !== null && element !== void 0 && !isScalar(element)) return "array";
28149
+ length2 += (typeof element === "string" ? element.length : LONGEST_NUMBER_TEXT) + 1;
28150
+ }
28151
+ return value.length > 0 && length2 <= VALUE_PREVIEW_LENGTH ? `array ${value.join(",")}` : "array";
27984
28152
  }
27985
28153
  async function evaluateUnary(op, argExpr, ctx) {
27986
28154
  const value = await evaluateExpression(argExpr, ctx);
@@ -28002,9 +28170,10 @@ async function evaluateTemplate(parts, ctx) {
28002
28170
  parts.map((part) => part.t === "text" ? part.s : evaluateExpression(part.e, ctx))
28003
28171
  );
28004
28172
  for (let pause = checkClock(ctx); pause !== null; pause = checkClock(ctx)) await pause;
28173
+ assertCheap(ctx.evaluation_budget, ...evaluated2);
28005
28174
  assertTextWithin("template", ...evaluated2);
28006
28175
  const text2 = evaluated2.map(stringifyForTemplate).join("");
28007
- chargeBuilt(ctx.run_budget, builtSize(text2));
28176
+ charge(ctx, builtSize(text2));
28008
28177
  return text2;
28009
28178
  }
28010
28179
  function assertTextWithin(writer, ...values2) {
@@ -28018,8 +28187,7 @@ function assertTextWithin(writer, ...values2) {
28018
28187
  }
28019
28188
  function stringifyForTemplate(value) {
28020
28189
  if (value === null || value === void 0) return "";
28021
- if (typeof value === "string") return value;
28022
- return String(value);
28190
+ return textOf(value);
28023
28191
  }
28024
28192
 
28025
28193
  // ../shared/src/id.ts
@@ -28079,6 +28247,11 @@ function knownCells(tables, tableId, cells) {
28079
28247
  },
28080
28248
  has(target, key) {
28081
28249
  return isField(key) || Reflect.has(target, key);
28250
+ },
28251
+ // An expression reads only what a row holds itself (`ownMember`).
28252
+ getOwnPropertyDescriptor(target, key) {
28253
+ if (isField(key) && !Object.hasOwn(target, key)) unread(key);
28254
+ return Reflect.getOwnPropertyDescriptor(target, key);
28082
28255
  }
28083
28256
  }
28084
28257
  );
@@ -28088,6 +28261,11 @@ function modelled(stepId, output) {
28088
28261
  get(target, key) {
28089
28262
  if (typeof key === "string" && key !== "then" && !(key in target)) unread(`${stepId}.${key}`);
28090
28263
  return Reflect.get(target, key);
28264
+ },
28265
+ // An expression reads only what an output holds itself (`ownMember`).
28266
+ getOwnPropertyDescriptor(target, key) {
28267
+ if (typeof key === "string" && key !== "then" && !Object.hasOwn(target, key)) unread(`${stepId}.${key}`);
28268
+ return Reflect.getOwnPropertyDescriptor(target, key);
28091
28269
  }
28092
28270
  });
28093
28271
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/app-sdk",
3
- "version": "0.115.1",
3
+ "version": "0.115.3",
4
4
  "description": "The SDK a Lotics custom-code app reads and writes through \u2014 typed hooks over the host bridge, cell readers, mount() and AppRouter",
5
5
  "type": "module",
6
6
  "exports": {