@hyperscale0/hsx 1.0.124 → 1.0.128
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 +2 -2
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +71 -19
- package/dist/src/compile.js.map +1 -1
- package/dist/src/examples-bundle.js +8 -8
- package/dist/src/examples-bundle.js.map +1 -1
- package/dist/src/headers.d.ts +1 -1
- package/dist/src/headers.d.ts.map +1 -1
- package/dist/src/headers.js +1 -0
- package/dist/src/headers.js.map +1 -1
- package/dist/src/std-bundle.d.ts.map +1 -1
- package/dist/src/std-bundle.js +7 -6
- package/dist/src/std-bundle.js.map +1 -1
- package/docs/README.md +74 -47
- package/examples/collections.hsx +1 -1
- package/examples/financing.hsx +1 -1
- package/examples/insurance.hsx +4 -4
- package/examples/lending.hsx +1 -1
- package/examples/library.hsx +1 -1
- package/examples/rental-deposit.hsx +1 -1
- package/examples/reporting.hsx +1 -1
- package/examples/serviced.hsx +1 -1
- package/package.json +4 -4
- package/src/compile.ts +98 -29
- package/src/examples-bundle.ts +8 -8
- package/src/headers.ts +1 -0
- package/src/std-bundle.ts +7 -6
- package/std/booking.hsx +75 -0
- package/std/escrow.hsx +24 -7
- package/std/financing.hsx +83 -26
- package/std/insurance.hsx +9 -5
- package/std/marketplace.hsx +3 -3
- package/std/money.hsx +3 -3
- package/std/wallet.hsx +17 -6
package/README.md
CHANGED
|
@@ -21,8 +21,8 @@ Start with the [first program and rental guide](docs/README.md). Save the
|
|
|
21
21
|
It holds 1,000 SAR, charges a one-time 50 SAR late fee, and returns the remainder.
|
|
22
22
|
The guide explains the fixed business binding and the host needed to execute it.
|
|
23
23
|
|
|
24
|
-
`npx hsx headers` prints every std instrument and its tunables from the
|
|
25
|
-
headers. The [samples](examples/) include both complete action paths and
|
|
24
|
+
`npx hsx headers --json` prints every std instrument and its tunables from the
|
|
25
|
+
bundled headers as JSON. The [samples](examples/) include both complete action paths and
|
|
26
26
|
compositions that still need child-record actions or adapter bindings. A compile
|
|
27
27
|
pass does not prove that a flow is publicly executable or funded.
|
|
28
28
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.ts"],"names":[],"mappings":"AAOA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAgBL,KAAK,WAAW,EAQjB,MAAM,kBAAkB,CAAC;AAU1B,OAAO,EAIL,KAAK,UAAU,EAKf,KAAK,IAAI,EACV,MAAM,UAAU,CAAC;AAClB,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;CACpC;AACD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,aAAa,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3E;AACD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AACD,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;CAClE;AACD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,SAAS,CAAC,EAAE;QACV,QAAQ,EAAE,WAAW,CAAC;QACtB,YAAY,EAAE,eAAe,CAAC;QAC9B,SAAS,EAAE,qBAAqB,EAAE,CAAC;KACpC,CAAC;CACH;AAqTD,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,GAC3B,aAAa,
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.ts"],"names":[],"mappings":"AAOA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAgBL,KAAK,WAAW,EAQjB,MAAM,kBAAkB,CAAC;AAU1B,OAAO,EAIL,KAAK,UAAU,EAKf,KAAK,IAAI,EACV,MAAM,UAAU,CAAC;AAClB,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;CACpC;AACD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,aAAa,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3E;AACD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AACD,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;CAClE;AACD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,SAAS,CAAC,EAAE;QACV,QAAQ,EAAE,WAAW,CAAC;QACtB,YAAY,EAAE,eAAe,CAAC;QAC9B,SAAS,EAAE,qBAAqB,EAAE,CAAC;KACpC,CAAC;CACH;AAqTD,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,GAC3B,aAAa,CAqrHf"}
|
package/dist/src/compile.js
CHANGED
|
@@ -349,10 +349,6 @@ export function compile(source, options = {}) {
|
|
|
349
349
|
for (const decl of program.decls)
|
|
350
350
|
if (decl.kind === "instrument")
|
|
351
351
|
templates.set(decl.name, decl);
|
|
352
|
-
for (const decl of program.decls) {
|
|
353
|
-
if (decl.kind === "instrument" && decl.parameters.length)
|
|
354
|
-
fail(decl, "This compiler cannot instantiate a parameterized instrument declared inside a program.", "For this version, specialize the instrument with fixed bindings and remove its parameters. Custom reusable headers require a host-supplied library.");
|
|
355
|
-
}
|
|
356
352
|
const document = {
|
|
357
353
|
udl: UDL_FORMAT_VERSION,
|
|
358
354
|
version: 1,
|
|
@@ -600,7 +596,11 @@ export function compile(source, options = {}) {
|
|
|
600
596
|
const fallback = param.value.kind === "default" ? param.value.value : undefined;
|
|
601
597
|
const typeName = type.kind === "type" || type.kind === "call" ? type.name : text(type);
|
|
602
598
|
const partyParameter = attachmentInfo && typeName === "party";
|
|
599
|
+
const optional = type.kind === "type" && type.optional;
|
|
600
|
+
// An optional party stays unset unless the attachment binds it, so a
|
|
601
|
+
// program party that shares its name does not switch it on.
|
|
603
602
|
const byName = partyParameter &&
|
|
603
|
+
!optional &&
|
|
604
604
|
(subjectPartyRoles.includes(param.key) ||
|
|
605
605
|
document.parties[param.key])
|
|
606
606
|
? { kind: "name", value: param.key, span: origin }
|
|
@@ -612,7 +612,7 @@ export function compile(source, options = {}) {
|
|
|
612
612
|
source: declarationSources.get(decl) ?? "program",
|
|
613
613
|
});
|
|
614
614
|
if (!actual) {
|
|
615
|
-
if (
|
|
615
|
+
if (optional)
|
|
616
616
|
continue;
|
|
617
617
|
parameterErrors.push({
|
|
618
618
|
span: origin,
|
|
@@ -678,7 +678,9 @@ export function compile(source, options = {}) {
|
|
|
678
678
|
const suppliedValue = supplied.get(key);
|
|
679
679
|
const requirements = decl.body.entries
|
|
680
680
|
.filter((entry) => entry.key.startsWith("action "))
|
|
681
|
-
.flatMap((entry) =>
|
|
681
|
+
.flatMap((entry) =>
|
|
682
|
+
// Read subject rows directly: entries() refuses repeated when branches.
|
|
683
|
+
asBlock(asBlock(entry.value).entries.find((row) => row.key === "subject")?.value).entries);
|
|
682
684
|
const moneyFields = [
|
|
683
685
|
...new Set(requirements
|
|
684
686
|
.filter((entry) => {
|
|
@@ -1653,10 +1655,10 @@ export function compile(source, options = {}) {
|
|
|
1653
1655
|
};
|
|
1654
1656
|
}
|
|
1655
1657
|
}
|
|
1656
|
-
const
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1658
|
+
const from = slots.get("from");
|
|
1659
|
+
// A policy member may sit in a state list such as `[funded, dispute.refund_after]`.
|
|
1660
|
+
const fromBinding = (from?.kind === "list" ? from.items : [from]).find((item) => item?.kind === "name" && item.value.includes("."));
|
|
1661
|
+
if (fromBinding?.kind === "name" && slots.has("moves")) {
|
|
1660
1662
|
const [parameter, member] = fromBinding.value.split(".");
|
|
1661
1663
|
const suppliedPolicy = supplied.get(parameter);
|
|
1662
1664
|
if (suppliedPolicy?.kind === "block") {
|
|
@@ -1693,6 +1695,7 @@ export function compile(source, options = {}) {
|
|
|
1693
1695
|
...(actionSubject ? { subject: actionSubject } : {}),
|
|
1694
1696
|
};
|
|
1695
1697
|
authored.set(a, row.value);
|
|
1698
|
+
const actionFeeCalculations = [];
|
|
1696
1699
|
for (const binding of boundaryBindings)
|
|
1697
1700
|
if (!a.subject?.adapters.find((entry) => entry.binding === binding)
|
|
1698
1701
|
?.snapshot)
|
|
@@ -1920,18 +1923,31 @@ export function compile(source, options = {}) {
|
|
|
1920
1923
|
if (rate.kind !== "percent")
|
|
1921
1924
|
fail(rate, "fee rate needs a percentage", "write a percentage such as 1%");
|
|
1922
1925
|
const prefix = `${name}_${key}`;
|
|
1923
|
-
|
|
1926
|
+
// Instrument calculations run at create, before any action
|
|
1927
|
+
// input exists, so a fee on an input amount runs with its action.
|
|
1928
|
+
const perAction = "field" in transfer.amount &&
|
|
1929
|
+
transfer.amount.field.startsWith("input.");
|
|
1930
|
+
const derived = (suffix, label, calculation) => {
|
|
1924
1931
|
const target = prefix + "_" + suffix;
|
|
1925
1932
|
if (fields.some((f) => f.name === target))
|
|
1926
1933
|
fail(move, `generated fee field ${target} conflicts with a field`, "rename the authored field or move key");
|
|
1927
|
-
fields.push({
|
|
1928
|
-
|
|
1934
|
+
fields.push({
|
|
1935
|
+
name: target,
|
|
1936
|
+
type: "money",
|
|
1937
|
+
label,
|
|
1938
|
+
...(perAction ? { optional: true } : {}),
|
|
1939
|
+
});
|
|
1940
|
+
(perAction ? actionFeeCalculations : calculations).push({
|
|
1929
1941
|
...calculation,
|
|
1930
1942
|
target,
|
|
1931
1943
|
});
|
|
1932
1944
|
return { field: `self.${target}` };
|
|
1933
1945
|
};
|
|
1934
|
-
|
|
1946
|
+
// Labels come from the source keys: seller or buyer, cap, tax.
|
|
1947
|
+
const feeLabel = `${paidBy === "seller" ? "Seller" : "Buyer"} fee`;
|
|
1948
|
+
const gross = derived("feeGross", quoted.kind === "capped"
|
|
1949
|
+
? `${feeLabel} before cap`
|
|
1950
|
+
: feeLabel, {
|
|
1935
1951
|
op: "rate",
|
|
1936
1952
|
base: transfer.amount,
|
|
1937
1953
|
bps: { literal: literal(rate) },
|
|
@@ -1941,7 +1957,7 @@ export function compile(source, options = {}) {
|
|
|
1941
1957
|
if (quoted.kind === "capped") {
|
|
1942
1958
|
if (quoted.cap.kind !== "money")
|
|
1943
1959
|
fail(quoted.cap, "fee cap needs money", "write an amount such as 750 SAR");
|
|
1944
|
-
charge = derived("fee", {
|
|
1960
|
+
charge = derived("fee", feeLabel, {
|
|
1945
1961
|
op: "minimum",
|
|
1946
1962
|
values: [gross, { literal: literal(quoted.cap) }],
|
|
1947
1963
|
});
|
|
@@ -1950,7 +1966,7 @@ export function compile(source, options = {}) {
|
|
|
1950
1966
|
if (tax && tax.kind !== "percent")
|
|
1951
1967
|
fail(tax, "tax needs a percentage of the fee", "write tax: 15%");
|
|
1952
1968
|
const vat = tax
|
|
1953
|
-
? derived("tax", {
|
|
1969
|
+
? derived("tax", "Fee tax", {
|
|
1954
1970
|
op: "rate",
|
|
1955
1971
|
base: charge,
|
|
1956
1972
|
bps: { literal: literal(tax) },
|
|
@@ -1959,7 +1975,7 @@ export function compile(source, options = {}) {
|
|
|
1959
1975
|
: undefined;
|
|
1960
1976
|
const charges = vat ? [charge, vat] : [charge];
|
|
1961
1977
|
const net = paidBy === "seller"
|
|
1962
|
-
? derived("net", {
|
|
1978
|
+
? derived("net", "Amount after fee", {
|
|
1963
1979
|
op: "subtract",
|
|
1964
1980
|
base: transfer.amount,
|
|
1965
1981
|
subtract: charges,
|
|
@@ -2022,6 +2038,8 @@ export function compile(source, options = {}) {
|
|
|
2022
2038
|
else
|
|
2023
2039
|
a[key] = data(expr);
|
|
2024
2040
|
}
|
|
2041
|
+
if (actionFeeCalculations.length)
|
|
2042
|
+
a.calculate = [...(a.calculate ?? []), ...actionFeeCalculations];
|
|
2025
2043
|
if (attachmentInfo && !attachmentInfo.child) {
|
|
2026
2044
|
if (attachmentInfo.exposed.has(name)) {
|
|
2027
2045
|
if (automatic(a.actor))
|
|
@@ -2373,7 +2391,9 @@ export function compile(source, options = {}) {
|
|
|
2373
2391
|
});
|
|
2374
2392
|
};
|
|
2375
2393
|
for (const decl of program.decls) {
|
|
2376
|
-
|
|
2394
|
+
// A parameterized program instrument is a template, like a header
|
|
2395
|
+
// instrument: each attachment instantiates it with its own bindings.
|
|
2396
|
+
if (decl.kind === "instrument" && !decl.parameters.length) {
|
|
2377
2397
|
addInstrument(decl, decl.name, emptyBlock, decl.span);
|
|
2378
2398
|
}
|
|
2379
2399
|
if (decl.kind === "assignment") {
|
|
@@ -2628,7 +2648,9 @@ export function compile(source, options = {}) {
|
|
|
2628
2648
|
const checkParty = (name, at, inst) => {
|
|
2629
2649
|
const names = parties(inst);
|
|
2630
2650
|
if (!names.includes(name))
|
|
2631
|
-
fail(at, `\`${name}\` is not a declared party.${didYouMean(name, names)}`,
|
|
2651
|
+
fail(at, `\`${name}\` is not a declared party.${didYouMean(name, names)}`, subjectPartyRoles.includes(name)
|
|
2652
|
+
? `Declare a party parameter such as \`holder: party\` on the instrument, use it here, and bind \`holder: ${name}\` where you attach it.`
|
|
2653
|
+
: `Declare it with \`party ${name}: business\`, or name a declared party (${names.join(", ")}).`);
|
|
2632
2654
|
};
|
|
2633
2655
|
const checkInput = (name, at, label, input) => {
|
|
2634
2656
|
const names = input.map((field) => field.name);
|
|
@@ -2681,9 +2703,39 @@ export function compile(source, options = {}) {
|
|
|
2681
2703
|
inst,
|
|
2682
2704
|
label: declarations.get(inst).name,
|
|
2683
2705
|
});
|
|
2706
|
+
// A family member drops an optional reference field its binding left
|
|
2707
|
+
// unset (a borrower-funded plan has no `funds`), so it cannot match and
|
|
2708
|
+
// leaves the selection. The header must still declare the field.
|
|
2709
|
+
const matchable = (selection) => {
|
|
2710
|
+
const members = [selection.instrument].flat();
|
|
2711
|
+
const header = selection.family
|
|
2712
|
+
? templates.get(`${selection.family.module}.${selection.family.exportPath}`)
|
|
2713
|
+
: undefined;
|
|
2714
|
+
if (!header ||
|
|
2715
|
+
!entries(asBlock(entries(header.body).get("fields"))).has(selection.reference))
|
|
2716
|
+
return members;
|
|
2717
|
+
return members.filter((id) => resolveField(document, byId.get(id), `self.${selection.reference}`));
|
|
2718
|
+
};
|
|
2719
|
+
// A count over no matchable member is zero.
|
|
2720
|
+
for (const inst of document.instruments)
|
|
2721
|
+
for (const action of Object.values(inst.actions))
|
|
2722
|
+
action.requires = action.requires.map((requirement) => requirement.kind === "aggregate" &&
|
|
2723
|
+
requirement.measure === "count" &&
|
|
2724
|
+
!matchable(requirement.selection).length
|
|
2725
|
+
? {
|
|
2726
|
+
kind: "compare",
|
|
2727
|
+
left: { literal: 0 },
|
|
2728
|
+
operator: requirement.operator,
|
|
2729
|
+
right: requirement.value,
|
|
2730
|
+
}
|
|
2731
|
+
: requirement);
|
|
2684
2732
|
const checkSelection = (selection, at, scope) => {
|
|
2685
2733
|
walk(selection.anchor, "anchor", site(selection, "anchor", at), scope);
|
|
2686
2734
|
walk(selection.where, "where", site(selection, "where", at), scope);
|
|
2735
|
+
const members = matchable(selection);
|
|
2736
|
+
if (members.length &&
|
|
2737
|
+
members.length < [selection.instrument].flat().length)
|
|
2738
|
+
selection.instrument = members;
|
|
2687
2739
|
for (const id of [selection.instrument].flat()) {
|
|
2688
2740
|
checkTarget(id, site(selection, "instrument", at));
|
|
2689
2741
|
const target = scopeOf(byId.get(id));
|