@hyperscale0/hsx 1.0.258 → 1.0.284

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 (47) hide show
  1. package/CHANGELOG.md +39 -4
  2. package/README.md +33 -4
  3. package/SECURITY.md +10 -21
  4. package/bases/cards.hsx +38 -0
  5. package/bases/collections.hsx +51 -0
  6. package/bases/escrow.hsx +23 -0
  7. package/bases/financing.hsx +36 -0
  8. package/bases/insurance.hsx +22 -0
  9. package/bases/lending.hsx +73 -0
  10. package/bases/marketplace.hsx +38 -0
  11. package/bases/money.hsx +12 -0
  12. package/bases/reporting.hsx +40 -0
  13. package/bases/savings.hsx +20 -0
  14. package/bases/wallet.hsx +23 -0
  15. package/dist/src/bases-bundle.d.ts +3 -0
  16. package/dist/src/bases-bundle.d.ts.map +1 -0
  17. package/dist/src/bases-bundle.js +14 -0
  18. package/dist/src/bases-bundle.js.map +1 -0
  19. package/dist/src/bases.d.ts +10 -0
  20. package/dist/src/bases.d.ts.map +1 -0
  21. package/dist/src/bases.js +2 -0
  22. package/dist/src/bases.js.map +1 -0
  23. package/dist/src/compile.d.ts.map +1 -1
  24. package/dist/src/compile.js +108 -14
  25. package/dist/src/compile.js.map +1 -1
  26. package/dist/src/examples-bundle.js +3 -3
  27. package/dist/src/examples-bundle.js.map +1 -1
  28. package/dist/src/std-bundle.d.ts.map +1 -1
  29. package/dist/src/std-bundle.js +7 -6
  30. package/dist/src/std-bundle.js.map +1 -1
  31. package/docs/README.md +40 -31
  32. package/examples/library.hsx +7 -1
  33. package/examples/rental-deposit.hsx +5 -12
  34. package/examples/serviced.hsx +7 -1
  35. package/package.json +13 -7
  36. package/src/bases-bundle.ts +15 -0
  37. package/src/bases.ts +9 -0
  38. package/src/compile.ts +138 -19
  39. package/src/examples-bundle.ts +3 -3
  40. package/src/std-bundle.ts +7 -6
  41. package/std/booking.hsx +8 -3
  42. package/std/collections.hsx +1 -1
  43. package/std/escrow.hsx +16 -29
  44. package/std/financing.hsx +21 -48
  45. package/std/money.hsx +6 -8
  46. package/std/purchase.hsx +89 -0
  47. package/std/reporting.hsx +1 -11
@@ -135,6 +135,24 @@ const camel = (name) => name.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
135
135
  const automatic = (actor) => actor === "clock" ||
136
136
  (actor !== null && typeof actor === "object" && "parent" in actor);
137
137
  const title = (name) => name[0].toUpperCase() + name.slice(1).replaceAll("_", " ");
138
+ /** Parties an instrument names itself: account owners, actors, move endpoints. */
139
+ function namedParties(instrument) {
140
+ const names = instrument.fields.flatMap((field) => field.type === "account" && typeof field.owner === "string"
141
+ ? [field.owner]
142
+ : []);
143
+ for (const action of Object.values(instrument.actions)) {
144
+ if (typeof action.actor === "object" && "party" in action.actor)
145
+ names.push(action.actor.party);
146
+ for (const move of action.moves) {
147
+ if (move.economics)
148
+ names.push(move.economics.sourceParty);
149
+ for (const endpoint of "from" in move ? [move.from, move.to] : [])
150
+ if (endpoint.startsWith("party."))
151
+ names.push(endpoint.slice(6));
152
+ }
153
+ }
154
+ return names;
155
+ }
138
156
  function entries(block) {
139
157
  const map = new Map();
140
158
  for (const row of block.entries) {
@@ -174,6 +192,8 @@ function text(expr) {
174
192
  return fail(expr, "expected a name or literal", "write a single name or literal");
175
193
  }
176
194
  function decimal(raw, scale, expr) {
195
+ if (raw.startsWith("-"))
196
+ fail(expr, "literal cannot be negative", "write a positive value; a move's from and to set its direction");
177
197
  const [whole = "", fraction = ""] = raw.split(".");
178
198
  if (!/^\d+$/.test(whole) || fraction.length > scale)
179
199
  fail(expr, `literal has more than ${scale} decimal places`, `write at most ${scale} decimal places`);
@@ -243,6 +263,8 @@ function literal(expr, expectedType) {
243
263
  return expr.value;
244
264
  if (expr.kind === "name" && ["true", "false"].includes(expr.value))
245
265
  return expr.value === "true";
266
+ if (expr.kind === "name" && expr.value === "runtime")
267
+ fail(expr, "this value is fixed when the program compiles, so it cannot be runtime", "write a literal value");
246
268
  return fail(expr, "expected a literal", "write a typed constant");
247
269
  }
248
270
  export function compile(source, options = {}) {
@@ -990,7 +1012,7 @@ export function compile(source, options = {}) {
990
1012
  if (bounds &&
991
1013
  (BigInt(String(value)) < BigInt(bounds.minimum) ||
992
1014
  BigInt(String(value)) > BigInt(bounds.maximum)))
993
- fail(v, `${param.key} is outside ${bounds.minimum}..${bounds.maximum}`, "choose a value inside the tunable's bounds");
1015
+ fail(v, `value is outside ${bounds.minimum}..${bounds.maximum}`, "choose a value inside the tunable's bounds");
994
1016
  }
995
1017
  catch (error) {
996
1018
  if (error instanceof CompileFailure)
@@ -1039,7 +1061,7 @@ export function compile(source, options = {}) {
1039
1061
  const left = environment.get(constraint.key), right = environment.get(text(rule.args[0]));
1040
1062
  if (!left || !right)
1041
1063
  fail(rule, "constraint needs two declared tunables", "name the compared tunables");
1042
- const numericKind = (expr) => expr.kind === "name" && /^P/.test(expr.value)
1064
+ const numericKind = (expr) => expr.kind === "name" && /^P(?:\d|T)/.test(expr.value)
1043
1065
  ? "duration"
1044
1066
  : expr.kind;
1045
1067
  if (!["money", "number", "percent", "duration"].includes(numericKind(left)) ||
@@ -1106,6 +1128,7 @@ export function compile(source, options = {}) {
1106
1128
  return lowered;
1107
1129
  };
1108
1130
  const fields = [];
1131
+ const boundaryReceipts = new Map();
1109
1132
  const calculations = [];
1110
1133
  let currentAction;
1111
1134
  let currentActionName;
@@ -1486,6 +1509,38 @@ export function compile(source, options = {}) {
1486
1509
  actions: Object.create(null),
1487
1510
  actionOrder: [],
1488
1511
  };
1512
+ const actionTargets = new Set();
1513
+ const collectTargets = (block) => {
1514
+ for (const entry of block.entries)
1515
+ if (entry.key.startsWith("when "))
1516
+ collectTargets(asBlock(entry.value));
1517
+ else if (entry.key === "calculate")
1518
+ for (const item of entry.value.kind === "list"
1519
+ ? entry.value.items
1520
+ : [entry.value]) {
1521
+ const target = item.kind === "block" ? entries(item).get("target") : undefined;
1522
+ if (target)
1523
+ actionTargets.add(String(data(target)));
1524
+ }
1525
+ };
1526
+ for (const row of decl.body.entries)
1527
+ if (row.key.startsWith("action "))
1528
+ collectTargets(asBlock(row.value));
1529
+ // Instrument calculations run before every action, create included, so
1530
+ // a fee or split calculates there only on an amount held from create.
1531
+ const heldFromCreate = (amount) => {
1532
+ if (!("field" in amount))
1533
+ return true;
1534
+ const [root, name, ...rest] = amount.field.split(".");
1535
+ const field = fields.find((item) => item.name === name);
1536
+ return (root === "self" &&
1537
+ !rest.length &&
1538
+ !!field &&
1539
+ !actionTargets.has(name) &&
1540
+ (!field.optional ||
1541
+ "value" in field ||
1542
+ calculations.some((node) => node.target === name)));
1543
+ };
1489
1544
  for (const row of decl.body.entries.filter((e) => e.key.startsWith("action "))) {
1490
1545
  const name = row.key.slice(7);
1491
1546
  const selected = (block) => ({
@@ -1744,7 +1799,7 @@ export function compile(source, options = {}) {
1744
1799
  ...(actionSubject ? { subject: actionSubject } : {}),
1745
1800
  };
1746
1801
  authored.set(a, row.value);
1747
- const actionFeeCalculations = [];
1802
+ const moveCalculations = [];
1748
1803
  for (const binding of boundaryBindings)
1749
1804
  if (!a.subject?.adapters.find((entry) => entry.binding === binding)
1750
1805
  ?.snapshot)
@@ -1775,8 +1830,20 @@ export function compile(source, options = {}) {
1775
1830
  const op = parts.has("operation")
1776
1831
  ? String(data(parts.get("operation")))
1777
1832
  : "internal_transfer.create";
1778
- if (op === "internal_transfer.reserve" && !parts.has("capture"))
1779
- fail(move, "reserve needs a receipt field", "add capture: receipt_name");
1833
+ if (op === "internal_transfer.reserve" && !parts.has("capture")) {
1834
+ if (!parts.has("boundary"))
1835
+ fail(move, "reserve needs a receipt field", "add capture: receipt_name");
1836
+ const capture = `${name}Receipt`;
1837
+ if (fields.some((field) => field.name === capture))
1838
+ fail(move, "boundary receipt name is already used", "name each reservation with an explicit capture");
1839
+ fields.push({ name: capture, type: "text", optional: true });
1840
+ boundaryReceipts.set(name, capture);
1841
+ parts.set("capture", {
1842
+ kind: "text",
1843
+ value: capture,
1844
+ span: move.span,
1845
+ });
1846
+ }
1780
1847
  if ((op === "internal_transfer.post" ||
1781
1848
  op === "internal_transfer.void") &&
1782
1849
  !parts.has("transfer"))
@@ -1890,6 +1957,7 @@ export function compile(source, options = {}) {
1890
1957
  fail(move, "split needs 1 to 64 recipients", "name the receiving parties and percentages");
1891
1958
  let total = 0;
1892
1959
  const pieces = [];
1960
+ const perAction = !heldFromCreate(val(amount));
1893
1961
  for (const [index, [party, rate]] of shares.entries()) {
1894
1962
  const recipient = resolve({
1895
1963
  kind: "name",
@@ -1920,8 +1988,12 @@ export function compile(source, options = {}) {
1920
1988
  const target = `${name}_${key}_share${index + 1}`;
1921
1989
  if (fields.some((f) => f.name === target))
1922
1990
  fail(move, "split field name conflicts", "rename the move key");
1923
- fields.push({ name: target, type: "money" });
1924
- calculations.push(index === shares.length - 1 && pieces.length
1991
+ fields.push({
1992
+ name: target,
1993
+ type: "money",
1994
+ ...(perAction ? { optional: true } : {}),
1995
+ });
1996
+ (perAction ? moveCalculations : calculations).push(index === shares.length - 1 && pieces.length
1925
1997
  ? {
1926
1998
  target,
1927
1999
  op: "subtract",
@@ -1993,14 +2065,18 @@ export function compile(source, options = {}) {
1993
2065
  if (terms.has("seller") === terms.has("buyer"))
1994
2066
  fail(fee, "fee needs exactly one paying party", "write seller: 1% or buyer: 1%");
1995
2067
  const quoted = terms.get(paidBy);
2068
+ // An account the agreement owns holds only what was moved
2069
+ // into it, so a charge on top has nothing to come from.
2070
+ if (paidBy === "buyer" &&
2071
+ fields.some((field) => `self.${field.name}` === transfer.from &&
2072
+ field.type === "account" &&
2073
+ field.owner === "self"))
2074
+ fail(quoted, `a buyer fee cannot come from \`${transfer.from.slice("self.".length)}\`, which holds only the moved amount`, "use a seller fee, which comes out of the held amount");
1996
2075
  const rate = quoted.kind === "capped" ? quoted.rate : quoted;
1997
2076
  if (rate.kind !== "percent")
1998
2077
  fail(rate, "fee rate needs a percentage", "write a percentage such as 1%");
1999
2078
  const prefix = `${name}_${key}`;
2000
- // Instrument calculations run at create, before any action
2001
- // input exists, so a fee on an input amount runs with its action.
2002
- const perAction = "field" in transfer.amount &&
2003
- transfer.amount.field.startsWith("input.");
2079
+ const perAction = !heldFromCreate(transfer.amount);
2004
2080
  const derived = (suffix, label, calculation) => {
2005
2081
  const target = prefix + "_" + suffix;
2006
2082
  if (fields.some((f) => f.name === target))
@@ -2011,7 +2087,7 @@ export function compile(source, options = {}) {
2011
2087
  label,
2012
2088
  ...(perAction ? { optional: true } : {}),
2013
2089
  });
2014
- (perAction ? actionFeeCalculations : calculations).push({
2090
+ (perAction ? moveCalculations : calculations).push({
2015
2091
  ...calculation,
2016
2092
  target,
2017
2093
  });
@@ -2120,8 +2196,8 @@ export function compile(source, options = {}) {
2120
2196
  else
2121
2197
  a[key] = data(expr);
2122
2198
  }
2123
- if (actionFeeCalculations.length)
2124
- a.calculate = [...(a.calculate ?? []), ...actionFeeCalculations];
2199
+ if (moveCalculations.length)
2200
+ a.calculate = [...(a.calculate ?? []), ...moveCalculations];
2125
2201
  if (attachmentInfo) {
2126
2202
  if (attachmentInfo.exposed.has(name)) {
2127
2203
  if (automatic(a.actor))
@@ -2199,6 +2275,13 @@ export function compile(source, options = {}) {
2199
2275
  inst.actions[name] = a;
2200
2276
  inst.actionOrder.push(name);
2201
2277
  }
2278
+ for (const action of Object.values(inst.actions))
2279
+ for (const move of action.moves)
2280
+ if ("transfer" in move) {
2281
+ const receipt = boundaryReceipts.get(move.transfer.replace(/^self\./, ""));
2282
+ if (receipt)
2283
+ move.transfer = `self.${receipt}`;
2284
+ }
2202
2285
  if (attachmentInfo && !attachmentInfo.child)
2203
2286
  applyAttachmentEconomics(document, inst, attachmentInfo.parties, attachmentInfo.economics, data);
2204
2287
  for (const key of [
@@ -2403,6 +2486,17 @@ export function compile(source, options = {}) {
2403
2486
  bindingDiagnostics.push(error.diagnostic);
2404
2487
  continue;
2405
2488
  }
2489
+ // A business the instrument names itself binds like a parameter, so
2490
+ // Build asks for it and the engine resolves its account and principal.
2491
+ // programOperator and programTax are the company's own accounts.
2492
+ for (const party of document.instruments
2493
+ .slice(firstAttachedInstrument)
2494
+ .flatMap(namedParties))
2495
+ if (document.parties[party]?.kind === "business" &&
2496
+ party !== "programOperator" &&
2497
+ party !== "programTax" &&
2498
+ !Object.values(parties).some((binding) => "party" in binding && binding.party === party))
2499
+ parties[party] ??= { party };
2406
2500
  const attachedInst = document.instruments.find((i) => i.id === instId);
2407
2501
  for (const createdInst of document.instruments
2408
2502
  .slice(firstAttachedInstrument)