@hyperscale0/hsx 3.2.0 → 3.3.0
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/CHANGELOG.md +4 -0
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +43 -7
- package/dist/src/compile.js.map +1 -1
- package/dist/src/cost.d.ts +1 -1
- package/dist/src/cost.d.ts.map +1 -1
- package/dist/src/cost.js +52 -9
- package/dist/src/cost.js.map +1 -1
- package/dist/src/headers.d.ts +2 -2
- package/dist/src/headers.d.ts.map +1 -1
- package/dist/src/headers.js +2 -0
- package/dist/src/headers.js.map +1 -1
- package/dist/src/parse.js +4 -2
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.d.ts.map +1 -1
- package/dist/src/std-bundle.js +7 -5
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/docs/README.md +6 -0
- package/docs/headers.md +43 -39
- package/examples/cost-table.json +36 -4
- package/examples/library.hsx +9 -2
- package/package.json +3 -3
- package/src/compile.ts +56 -10
- package/src/cost.ts +60 -16
- package/src/headers.ts +2 -0
- package/src/parse.ts +3 -2
- package/src/std-bundle.ts +7 -5
- package/src/version.ts +1 -1
- package/std/collections.hsx +45 -4
- package/std/escrow.hsx +10 -3
- package/std/financing.hsx +240 -32
- package/std/marketplace.hsx +94 -7
- package/std/money.hsx +1 -1
- package/std/reporting.hsx +1286 -0
- package/std/vehicles.hsx +34 -0
package/docs/headers.md
CHANGED
|
@@ -3,42 +3,46 @@
|
|
|
3
3
|
Each company selects library policies through typed tunables.
|
|
4
4
|
`ref<T>[]` accepts one reference or up to 16 references as one policy. `approval` accepts a party and generates the required decision records.
|
|
5
5
|
|
|
6
|
-
| Object
|
|
7
|
-
|
|
|
8
|
-
| money.transfer
|
|
9
|
-
| money.hold
|
|
10
|
-
| money.split
|
|
11
|
-
| money.schedule
|
|
12
|
-
| money.pool
|
|
13
|
-
| money.swap
|
|
14
|
-
| money.deposit
|
|
15
|
-
| money.payout
|
|
16
|
-
| money.metered
|
|
17
|
-
| money.batch
|
|
18
|
-
| marketplace.listing
|
|
19
|
-
| marketplace.order
|
|
20
|
-
| escrow.hold
|
|
21
|
-
|
|
|
22
|
-
| wallet.
|
|
23
|
-
|
|
|
24
|
-
| financing.
|
|
25
|
-
| financing.
|
|
26
|
-
| financing.
|
|
27
|
-
| financing.
|
|
28
|
-
|
|
|
29
|
-
| lending.
|
|
30
|
-
| lending.
|
|
31
|
-
|
|
|
32
|
-
| insurance.
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
| collections.
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
-
| cards.
|
|
41
|
-
| cards.
|
|
42
|
-
| cards.
|
|
43
|
-
|
|
|
44
|
-
|
|
|
6
|
+
| Object | Tunables |
|
|
7
|
+
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
8
|
+
| money.transfer | `payer: party`, `payee: party`, `amount: money = runtime` |
|
|
9
|
+
| money.hold | `payer: party`, `payee: party`, `amount: money = runtime` |
|
|
10
|
+
| money.split | `payer: party`, `amount: money = runtime`, `shares: split = { programOperator: 100% }` |
|
|
11
|
+
| money.schedule | `payer: party`, `payee: party`, `amount: money = runtime`, `count: integer(1, 366) = 12` |
|
|
12
|
+
| money.pool | `payer: party`, `payee: party`, `target: money`, `closes: date` |
|
|
13
|
+
| money.swap | `first: party`, `second: party`, `first_amount: money`, `second_amount: money`, `expires: date` |
|
|
14
|
+
| money.deposit | `payer: party`, `payee: party`, `amount: money`, `expires: date`, `approval: approval` |
|
|
15
|
+
| money.payout | `payer: party`, `payee: party`, `amount: money = runtime`, `reconcile_within: duration = 3d` |
|
|
16
|
+
| money.metered | `payer: party`, `payee: party`, `unit_price: money`, `units: integer = 1`, `fee: fee = { seller: 0% }` |
|
|
17
|
+
| money.batch | `payer: party`, `payee: party`, `closes: date`, `approval: approval`, `reconcile_within: duration = 3d` |
|
|
18
|
+
| marketplace.listing | `seller: party`, `vehicle_facts: enum(optional, required) = optional` |
|
|
19
|
+
| marketplace.order | `listing: ref<marketplace.listing>`, `buyer: party` |
|
|
20
|
+
| marketplace.reservation | `listing: ref<marketplace.listing>`, `funds: ref<escrow.hold>`, `plans: ref<financing.installments>[]`, `converters: ref[]`, `buyer: party`, `seller: party`, `expires_after: duration = 48h`, `deposit: money = 1000 SAR`, `cancellation_refund: percent = 100%`, `expiry_refund: percent = 100%`, `retained_to: party = programOperator`, `operator: party = programOperator`, `competing_quotes: enum(allowed, blocked) = allowed` |
|
|
21
|
+
| escrow.hold | `payer: party`, `payee: party`, `for: ref<marketplace.order>`, `accept_within: duration = 48h`, `fee: fee = { seller: 0%, tax: 0% }`, `dispute: policy = { refund_after: return_verified }` |
|
|
22
|
+
| wallet.balance | `holder: party`, `spend_limit: money = 100000 SAR` |
|
|
23
|
+
| wallet.spend | `wallet: ref<wallet.balance>`, `payee: party` |
|
|
24
|
+
| financing.installments | `months: integer(1, 366)`, `charge_limit: integer(1, 3) = 3`, `profit: percent`, `disburse_to: enum(funds, borrower) = funds`, `profit_earned: enum(on_payment, by_schedule, at_disbursement) = on_payment`, `apply: enum(fines_profit_principal, principal_profit, pro_rata) = fines_profit_principal`, `payoff_rebate: percent = 100%`, `write_off_after: duration = 90d`, `max_extension: duration = 90d`, `max_amendments: integer(0, 366) = 2`, `waiver_limit: percent = 100%`, `amendment_expiry: duration = 7d`, `payoff_quote_expiry: duration = 1h`, `allow_overdue: enum(allowed, blocked) = allowed`, `assessed_fines: enum(carry, require_waiver) = carry`, `down_payment: percent = 0%`, `funds: ref`, `approval: approval`, `borrower: party = party(person)`, `capital: party = programOperator`, `profit_to: party = programOperator`, `limits: ref<financing.limits> = object(financing.limits)` |
|
|
25
|
+
| financing.late_charge | `on: ref<financing.installments>[]`, `grace: duration = 3d`, `fine: money = 50 SAR`, `cap: money = 25 SAR`, `fines_to: party = programOperator`, `costs_to: party = programOperator`, `approval: approval = programOperator`, `borrower: party = party(person)` |
|
|
26
|
+
| financing.limits | `per_borrower: money`, `portfolio: money`, `borrower: party = party(person)`, `active_plans: integer = 1` |
|
|
27
|
+
| financing.credit_line | `borrower: party`, `lender: party`, `limit: money`, `expires: date` |
|
|
28
|
+
| financing.advance | `line: ref<financing.credit_line>`, `amount: money = runtime` |
|
|
29
|
+
| lending.round | `borrower: party`, `capital: party`, `plan: ref<financing.installments> = object(financing.installments)`, `months: integer(1, 366) = 3`, `profit: percent = 3%`, `minimum_ticket: money = 100 SAR`, `investor_cap: percent = 20%`, `commitments: ref<lending.commitment> = object(lending.commitment)` |
|
|
30
|
+
| lending.commitment | `round: ref<lending.round>`, `wallet: ref<wallet.balance>`, `investor: party = party(person)` |
|
|
31
|
+
| lending.distribution | `round: ref<lending.round>`, `receipt: ref`, `commitments: ref<lending.commitment> = object(lending.commitment)`, `mode: enum(cash, loss) = cash`, `residual_to: party = programOperator`, `fee: percent = 1%`, `tax: percent = 15%` |
|
|
32
|
+
| insurance.cover | `holder: party`, `insurer: party`, `commission: percent = 0%`, `covers: ref`, `premium: money = runtime` |
|
|
33
|
+
| insurance.claim | `cover: ref<insurance.cover>`, `approved_by: approval` |
|
|
34
|
+
| approvals.decision | `for: ref`, `approved_by: party`, `action: text = "approve"` |
|
|
35
|
+
| collections.case | `on: ref<financing.installments>[]`, `agency: approval`, `overdue: duration = 3d`, `fee: percent = 20%` |
|
|
36
|
+
| collections.contact | `case: ref<collections.case>[]`, `agency: party`, `max_contacts: integer = 10`, `window: duration = 30d`, `contact_from: integer(0, 23) = 8`, `contact_until: integer(1, 23) = 20`, `timezone: text = "Asia/Riyadh"`; contact_from less_than contact_until |
|
|
37
|
+
| collections.reminder | `on: ref<financing.installments>[]`, `operator: party = programOperator`, `before_days: integer(0, 366) = 3`, `overdue_days: integer(1, 366) = 1`, `max_per_day: integer(1, 10) = 1`, `send_from: integer(0, 23) = 8`, `send_until: integer(1, 24) = 20`, `timezone: text = "Asia/Riyadh"`; send_from less_than send_until |
|
|
38
|
+
| travel.package | `price: money`, `supplier_cost: money`, `departure: date`; supplier_cost at_most price |
|
|
39
|
+
| travel.booking | `package: ref<travel.package>`, `buyer: party`, `supplier: approval`, `cover: ref<insurance.cover>`, `deposit: percent = 30%`, `balance_before: duration = 14d`, `confirm_within: duration = 24h`, `tax: percent = 15%`, `early_before: duration = 30d`, `middle_before: duration = 15d`, `middle_penalty: percent = 10%`, `late_penalty: percent = 30%`; early_before greater_than middle_before |
|
|
40
|
+
| cards.cardholder | `holder: party` |
|
|
41
|
+
| cards.card | `holder: ref<cards.cardholder>`, `spend_limit: money` |
|
|
42
|
+
| cards.authorization | `card: ref<cards.card>`, `merchant: party`, `approval: approval` |
|
|
43
|
+
| cards.transaction | `authorization: ref<cards.authorization>` |
|
|
44
|
+
| cards.dispute | `transaction: ref<cards.transaction>`, `approved_by: approval`, `within: duration = 90d` |
|
|
45
|
+
| savings.circle | `contribution: money`, `members: integer(1, 366)`, `starts: date`, `memberships: ref<savings.membership> = object(savings.membership)` |
|
|
46
|
+
| savings.membership | `circle: ref<savings.circle>`, `member: party` |
|
|
47
|
+
| reporting.portfolio | `on: ref<financing.installments>[]`, `default_days: integer(1, 3650) = 90`, `retention_years: integer(1, 100) = 10`, `aging_first_days: integer(1, 3650) = 30`, `aging_second_days: integer(1, 3650) = 60`, `aging_third_days: integer(1, 3650) = 90`, `ratio_scale: integer(1, 1000000) = 10000`, `ratio_rounding: enum(floor, halfUp) = floor`, `lock_wait_ms: integer(1, 2000) = 2000`, `capture_ms: integer(1, 10000) = 10000`, `max_rows: integer(1, 100000) = 10000`, `max_join_rows: integer(1, 1000000) = 100000`, `max_bytes: integer(1024, 16777216) = 8388608`; aging_first_days less_than aging_second_days, aging_second_days less_than aging_third_days |
|
|
48
|
+
| vehicles.vehicle | `listing: ref<marketplace.listing>`, `seller: party` |
|
package/examples/cost-table.json
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"verification.kyc.investment.count",
|
|
21
21
|
"verification.kyc.standard.count"
|
|
22
22
|
],
|
|
23
|
-
"effectiveTableDigest": "
|
|
23
|
+
"effectiveTableDigest": "92a2b90130954fe1ff685dde716b295ce0f5ad4ee9769a10aa04f3807fc64dc9",
|
|
24
24
|
"fixed": {
|
|
25
25
|
"activationBaseMinor": "500000",
|
|
26
26
|
"activationPerPointMinor": "100000",
|
|
@@ -195,6 +195,14 @@
|
|
|
195
195
|
"signature": "moves.transfer.internal",
|
|
196
196
|
"volumeMeter": "transfer.internal.volume_sar"
|
|
197
197
|
},
|
|
198
|
+
{
|
|
199
|
+
"bps": 0,
|
|
200
|
+
"meter": "instrument.event.count",
|
|
201
|
+
"payer": "product",
|
|
202
|
+
"perEventMinor": "0",
|
|
203
|
+
"settlement": "invoice",
|
|
204
|
+
"signature": "reminder.email"
|
|
205
|
+
},
|
|
198
206
|
{
|
|
199
207
|
"bps": 0,
|
|
200
208
|
"meter": "instrument.event.count",
|
|
@@ -283,7 +291,7 @@
|
|
|
283
291
|
"verification.kyc.investment.count",
|
|
284
292
|
"verification.kyc.standard.count"
|
|
285
293
|
],
|
|
286
|
-
"effectiveTableDigest": "
|
|
294
|
+
"effectiveTableDigest": "b9b790c53a6f993c11b27dc06d9373ed5e865846343ab4893fd39ea2945dc520",
|
|
287
295
|
"fixed": {
|
|
288
296
|
"activationBaseMinor": "133300",
|
|
289
297
|
"activationPerPointMinor": "26700",
|
|
@@ -458,6 +466,14 @@
|
|
|
458
466
|
"signature": "moves.transfer.internal",
|
|
459
467
|
"volumeMeter": "transfer.internal.volume_usd"
|
|
460
468
|
},
|
|
469
|
+
{
|
|
470
|
+
"bps": 0,
|
|
471
|
+
"meter": "instrument.event.count",
|
|
472
|
+
"payer": "product",
|
|
473
|
+
"perEventMinor": "0",
|
|
474
|
+
"settlement": "invoice",
|
|
475
|
+
"signature": "reminder.email"
|
|
476
|
+
},
|
|
461
477
|
{
|
|
462
478
|
"bps": 0,
|
|
463
479
|
"meter": "instrument.event.count",
|
|
@@ -546,7 +562,7 @@
|
|
|
546
562
|
"verification.kyc.investment.count",
|
|
547
563
|
"verification.kyc.standard.count"
|
|
548
564
|
],
|
|
549
|
-
"effectiveTableDigest": "
|
|
565
|
+
"effectiveTableDigest": "83ea8337ea1d8a9301156fec28df5ba941388cba1d83599b878f1f41d67d7af4",
|
|
550
566
|
"fixed": {
|
|
551
567
|
"activationBaseMinor": "125000",
|
|
552
568
|
"activationPerPointMinor": "25000",
|
|
@@ -721,6 +737,14 @@
|
|
|
721
737
|
"signature": "moves.transfer.internal",
|
|
722
738
|
"volumeMeter": "transfer.internal.volume_eur"
|
|
723
739
|
},
|
|
740
|
+
{
|
|
741
|
+
"bps": 0,
|
|
742
|
+
"meter": "instrument.event.count",
|
|
743
|
+
"payer": "product",
|
|
744
|
+
"perEventMinor": "0",
|
|
745
|
+
"settlement": "invoice",
|
|
746
|
+
"signature": "reminder.email"
|
|
747
|
+
},
|
|
724
748
|
{
|
|
725
749
|
"bps": 0,
|
|
726
750
|
"meter": "instrument.event.count",
|
|
@@ -809,7 +833,7 @@
|
|
|
809
833
|
"verification.kyc.investment.count",
|
|
810
834
|
"verification.kyc.standard.count"
|
|
811
835
|
],
|
|
812
|
-
"effectiveTableDigest": "
|
|
836
|
+
"effectiveTableDigest": "5f24fe864b81b4d801353549381e7b17a73210ece0323db2328f0f794b554152",
|
|
813
837
|
"fixed": {
|
|
814
838
|
"activationBaseMinor": "104200",
|
|
815
839
|
"activationPerPointMinor": "20800",
|
|
@@ -984,6 +1008,14 @@
|
|
|
984
1008
|
"signature": "moves.transfer.internal",
|
|
985
1009
|
"volumeMeter": "transfer.internal.volume_gbp"
|
|
986
1010
|
},
|
|
1011
|
+
{
|
|
1012
|
+
"bps": 0,
|
|
1013
|
+
"meter": "instrument.event.count",
|
|
1014
|
+
"payer": "product",
|
|
1015
|
+
"perEventMinor": "0",
|
|
1016
|
+
"settlement": "invoice",
|
|
1017
|
+
"signature": "reminder.email"
|
|
1018
|
+
},
|
|
987
1019
|
{
|
|
988
1020
|
"bps": 0,
|
|
989
1021
|
"meter": "instrument.event.count",
|
package/examples/library.hsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
program library "Standard library"
|
|
2
2
|
use money
|
|
3
3
|
use marketplace
|
|
4
|
+
use vehicles
|
|
4
5
|
use escrow
|
|
5
6
|
use wallet
|
|
6
7
|
use financing
|
|
@@ -11,6 +12,7 @@ use collections
|
|
|
11
12
|
use travel
|
|
12
13
|
use cards
|
|
13
14
|
use savings
|
|
15
|
+
use reporting
|
|
14
16
|
party buyer: person
|
|
15
17
|
party seller: business
|
|
16
18
|
party officer: staff role credit_underwriter
|
|
@@ -27,7 +29,7 @@ deposit = money.deposit { payer: buyer, payee: seller, amount: 100 SAR, expires:
|
|
|
27
29
|
payout = money.payout { payer: seller, payee: buyer, amount: 100 SAR, reconcile_within: 3d }
|
|
28
30
|
metered = money.metered { payer: buyer, payee: seller, unit_price: 10 SAR, units: 2 }
|
|
29
31
|
batch = money.batch { payer: buyer, payee: seller, closes: 2030-01-01, approval: officer }
|
|
30
|
-
listing = marketplace.listing { seller: seller }
|
|
32
|
+
listing = marketplace.listing { seller: seller, vehicle_facts: required }
|
|
31
33
|
order = marketplace.order { listing: listing, buyer: buyer }
|
|
32
34
|
sale = escrow.hold { payer: buyer, payee: seller, for: order, fee { seller: 1% cap 750 SAR, tax: 15% }, dispute { refund_after: return_verified } }
|
|
33
35
|
purse = wallet.balance { holder: buyer, spend_limit: 1000 SAR }
|
|
@@ -45,8 +47,9 @@ distribution = lending.distribution { round: round, receipt: plan.settlement, re
|
|
|
45
47
|
cover = insurance.cover { holder: buyer, insurer: carrier, covers: sale, commission: 10% }
|
|
46
48
|
claim = insurance.claim { cover: cover, approved_by: carrier }
|
|
47
49
|
decision = approvals.decision { for: transfer, approved_by: officer, action: "pay" }
|
|
48
|
-
case = collections.case { on: plan, agency: seller, fee: 20% }
|
|
50
|
+
case = collections.case { on: [plan, plan_schedule, plan_upfront], agency: seller, fee: 20% }
|
|
49
51
|
contact = collections.contact { case: case, agency: seller }
|
|
52
|
+
reminder = collections.reminder { on: plan }
|
|
50
53
|
package = travel.package { price: 1000 SAR, supplier_cost: 700 SAR, departure: 2030-01-01 }
|
|
51
54
|
booking = travel.booking { package: package, buyer: buyer, supplier: seller, cover: travel_cover }
|
|
52
55
|
cardholder = cards.cardholder { holder: buyer }
|
|
@@ -57,3 +60,7 @@ dispute = cards.dispute { transaction: transaction, approved_by: officer }
|
|
|
57
60
|
circle = savings.circle { contribution: 100 SAR, members: 3, starts: 2030-01-01 }
|
|
58
61
|
membership = savings.membership { circle: circle, member: buyer }
|
|
59
62
|
travel_cover = insurance.cover { holder: buyer, insurer: carrier, commission: 10%, covers: booking }
|
|
63
|
+
|
|
64
|
+
reports = reporting.portfolio { on: [plan, plan_schedule, plan_upfront] }
|
|
65
|
+
vehicle = vehicles.vehicle { listing: listing, seller: seller }
|
|
66
|
+
reservation = marketplace.reservation { listing: listing, funds: sale, plans: [plan, plan_schedule, plan_upfront], converters: [sale, plan, plan_schedule, plan_upfront], buyer: buyer, seller: seller }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperscale0/hsx",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "The strictly typed HSX language: compile total financial programs into canonical UDL.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hsx",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"check": "bun run generate && bun run typecheck && bun run build && tsc -p playground/tsconfig.json && bun scripts/build-playground.ts && bun test test"
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@hyperscale0/udl": "3.
|
|
79
|
+
"@hyperscale0/udl": "3.2.0"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
82
|
"bun-types": "1.4.2",
|
|
@@ -87,5 +87,5 @@
|
|
|
87
87
|
"Amir Ayub",
|
|
88
88
|
"Sara AlBakaawi"
|
|
89
89
|
],
|
|
90
|
-
"gitHead": "
|
|
90
|
+
"gitHead": "6f9f3c0315ec07edeafca09596d3cf92c53a25fd"
|
|
91
91
|
}
|
package/src/compile.ts
CHANGED
|
@@ -596,6 +596,8 @@ export function compile(
|
|
|
596
596
|
"summary",
|
|
597
597
|
"invariants",
|
|
598
598
|
"constraints",
|
|
599
|
+
"reports",
|
|
600
|
+
"revisioned",
|
|
599
601
|
].includes(key) &&
|
|
600
602
|
!key.startsWith("action ")
|
|
601
603
|
)
|
|
@@ -698,6 +700,17 @@ export function compile(
|
|
|
698
700
|
...(t.kind === "type" && t.optional ? { optional: true } : {}),
|
|
699
701
|
};
|
|
700
702
|
if (type === "enum" && t.kind === "call") f.values = t.args.map(text);
|
|
703
|
+
if (type === "text" && t.kind === "call") {
|
|
704
|
+
if (t.args.length < 2 || t.args.length > 3)
|
|
705
|
+
fail(
|
|
706
|
+
t,
|
|
707
|
+
"bounded text needs length bounds and an optional pattern",
|
|
708
|
+
"write text(1, 80)",
|
|
709
|
+
);
|
|
710
|
+
f.minLength = literal(resolve(t.args[0]!));
|
|
711
|
+
f.maxLength = literal(resolve(t.args[1]!));
|
|
712
|
+
if (t.args[2]) f.pattern = literal(resolve(t.args[2]));
|
|
713
|
+
}
|
|
701
714
|
if (["integer", "money"].includes(type) && t.kind === "call") {
|
|
702
715
|
if (t.args.length !== 2)
|
|
703
716
|
fail(
|
|
@@ -894,20 +907,53 @@ export function compile(
|
|
|
894
907
|
...block,
|
|
895
908
|
entries: block.entries.flatMap((entry) => {
|
|
896
909
|
if (!entry.key.startsWith("when ")) return [entry];
|
|
897
|
-
const [, tunable, , choice] = entry.key.split(" ");
|
|
910
|
+
const [, tunable, relation, choice] = entry.key.split(" ");
|
|
898
911
|
const binding = environment.get(tunable!);
|
|
899
912
|
if (!binding)
|
|
900
913
|
fail(
|
|
901
914
|
entry,
|
|
902
915
|
`unknown branch tunable ${tunable}`,
|
|
903
|
-
"name an enum tunable declared by this header",
|
|
916
|
+
"name an enum or reference tunable declared by this header",
|
|
904
917
|
);
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
918
|
+
let matches: boolean;
|
|
919
|
+
if (relation === "has") {
|
|
920
|
+
// Inspect declarations, not lowering order: a bound object may
|
|
921
|
+
// appear after the instrument that asks about its fields.
|
|
922
|
+
const bound = resolve(binding!);
|
|
923
|
+
const [root, ...children] = text(bound).split(".");
|
|
924
|
+
const object = objects.get(root!);
|
|
925
|
+
let target = object
|
|
926
|
+
? templates.get(object.object)?.body
|
|
927
|
+
: program.decls
|
|
928
|
+
.filter(
|
|
929
|
+
(decl): decl is InstrumentDecl =>
|
|
930
|
+
decl.kind === "instrument",
|
|
931
|
+
)
|
|
932
|
+
.find((decl) => decl.name === root)?.body;
|
|
933
|
+
for (const child of children) {
|
|
934
|
+
const record = target
|
|
935
|
+
? entries(asBlock(entries(target).get("records"))).get(child)
|
|
936
|
+
: undefined;
|
|
937
|
+
target = record ? asBlock(record) : undefined;
|
|
938
|
+
}
|
|
939
|
+
if (!target)
|
|
940
|
+
fail(
|
|
941
|
+
entry,
|
|
942
|
+
"field branch needs a declared object",
|
|
943
|
+
"bind a reference to a declared object",
|
|
944
|
+
);
|
|
945
|
+
matches = entries(asBlock(entries(target!).get("fields"))).has(
|
|
946
|
+
choice!,
|
|
910
947
|
);
|
|
948
|
+
} else {
|
|
949
|
+
if (!enums.get(tunable!)?.includes(choice!))
|
|
950
|
+
fail(
|
|
951
|
+
entry,
|
|
952
|
+
`unknown enum branch ${choice}`,
|
|
953
|
+
"use a declared enum value",
|
|
954
|
+
);
|
|
955
|
+
matches = text(binding!) === choice;
|
|
956
|
+
}
|
|
911
957
|
const body = asBlock(entry.value);
|
|
912
958
|
for (const clause of body.entries)
|
|
913
959
|
if (
|
|
@@ -921,7 +967,7 @@ export function compile(
|
|
|
921
967
|
"when permits requirements, calculations, moves and invocations",
|
|
922
968
|
"keep lifecycle and actor clauses outside the branch",
|
|
923
969
|
);
|
|
924
|
-
return
|
|
970
|
+
return matches ? selected(body).entries : [];
|
|
925
971
|
}),
|
|
926
972
|
});
|
|
927
973
|
const slots = entries(selected(asBlock(row.value)));
|
|
@@ -1278,7 +1324,7 @@ export function compile(
|
|
|
1278
1324
|
inst.actions[name] = a;
|
|
1279
1325
|
inst.actionOrder.push(name);
|
|
1280
1326
|
}
|
|
1281
|
-
for (const key of ["invariants"] as const)
|
|
1327
|
+
for (const key of ["invariants", "reports", "revisioned"] as const)
|
|
1282
1328
|
if (body.has(key))
|
|
1283
1329
|
(inst as unknown as Record<string, unknown>)[key] = data(
|
|
1284
1330
|
body.get(key)!,
|
|
@@ -1361,7 +1407,7 @@ export function compile(
|
|
|
1361
1407
|
for (let n = 0; n < inst.lifecycle.states.length; n++)
|
|
1362
1408
|
for (const edge of Object.values(inst.lifecycle.transitions))
|
|
1363
1409
|
if (edge.from.some((state) => reachable.has(state)))
|
|
1364
|
-
reachable.add(edge.to);
|
|
1410
|
+
if (edge.to !== "preserve") reachable.add(edge.to);
|
|
1365
1411
|
for (const [key, edge] of Object.entries(inst.lifecycle.transitions)) {
|
|
1366
1412
|
edge.from = edge.from.filter((state) => reachable.has(state));
|
|
1367
1413
|
if (!edge.from.length) {
|
package/src/cost.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { resolveField, type UdlDocument } from "@hyperscale0/udl";
|
|
2
2
|
export interface UdlCostManifest {
|
|
3
3
|
actions: Record<
|
|
4
4
|
string,
|
|
@@ -12,21 +12,65 @@ export interface UdlCostManifest {
|
|
|
12
12
|
/** Counts sealed instructions after lowering. Prices belong to the executor's tariff. */
|
|
13
13
|
export function buildUdlCostManifest(document: UdlDocument): UdlCostManifest {
|
|
14
14
|
const actions: UdlCostManifest["actions"] = {};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
15
|
+
const active = new Set<string>();
|
|
16
|
+
const visit = (
|
|
17
|
+
id: string,
|
|
18
|
+
name: string,
|
|
19
|
+
): UdlCostManifest["actions"][string] => {
|
|
20
|
+
const key = `${id}.${name}`;
|
|
21
|
+
if (actions[key]) return actions[key];
|
|
22
|
+
if (active.has(key)) throw new Error(`Invocation cycle at ${key}`);
|
|
23
|
+
const instrument = document.instruments.find((item) => item.id === id);
|
|
24
|
+
const action = instrument?.actions[name];
|
|
25
|
+
if (!instrument || !action) throw new Error(`Unknown invocation ${key}`);
|
|
26
|
+
active.add(key);
|
|
27
|
+
const cost = {
|
|
28
|
+
transfers: action.moves.length,
|
|
29
|
+
accounts:
|
|
30
|
+
name === "create"
|
|
31
|
+
? instrument.fields.filter(
|
|
32
|
+
(field) => field.type === "account" && field.owner === "self",
|
|
33
|
+
).length
|
|
34
|
+
: 0,
|
|
35
|
+
invocations: 0,
|
|
36
|
+
};
|
|
37
|
+
for (const call of action.invoke ?? []) {
|
|
38
|
+
const ref =
|
|
39
|
+
"reference" in call
|
|
40
|
+
? resolveField(document, instrument, call.reference, action.input)
|
|
41
|
+
: undefined;
|
|
42
|
+
const targets =
|
|
43
|
+
"instrument" in call
|
|
44
|
+
? call.instrument
|
|
45
|
+
: "selection" in call
|
|
46
|
+
? call.selection.instrument
|
|
47
|
+
: ref?.type === "ref"
|
|
48
|
+
? ref.target
|
|
49
|
+
: [];
|
|
50
|
+
const children = (typeof targets === "string" ? [targets] : targets).map(
|
|
51
|
+
(target) => visit(target, call.action),
|
|
52
|
+
);
|
|
53
|
+
const count =
|
|
54
|
+
"selection" in call
|
|
55
|
+
? call.selection.limit
|
|
56
|
+
: "instrument" in call
|
|
57
|
+
? (call.range?.maximum ?? 1)
|
|
58
|
+
: 1;
|
|
59
|
+
// A union selects one target per row. Each meter takes its worst case.
|
|
60
|
+
cost.transfers +=
|
|
61
|
+
count * Math.max(0, ...children.map((child) => child.transfers));
|
|
62
|
+
cost.accounts +=
|
|
63
|
+
count * Math.max(0, ...children.map((child) => child.accounts));
|
|
64
|
+
cost.invocations +=
|
|
65
|
+
count *
|
|
66
|
+
(1 + Math.max(0, ...children.map((child) => child.invocations)));
|
|
30
67
|
}
|
|
68
|
+
active.delete(key);
|
|
69
|
+
actions[key] = cost;
|
|
70
|
+
return cost;
|
|
71
|
+
};
|
|
72
|
+
for (const instrument of document.instruments)
|
|
73
|
+
for (const name of Object.keys(instrument.actions))
|
|
74
|
+
visit(instrument.id, name);
|
|
31
75
|
return { actions };
|
|
32
76
|
}
|
package/src/headers.ts
CHANGED
package/src/parse.ts
CHANGED
|
@@ -198,10 +198,11 @@ class Parser {
|
|
|
198
198
|
if (key === "action" && !this.at(":")) key += " " + this.identifier();
|
|
199
199
|
if (key === "when") {
|
|
200
200
|
const tunable = this.identifier();
|
|
201
|
-
this.
|
|
201
|
+
const relation = this.eat("has") ? "has" : "is";
|
|
202
|
+
if (relation === "is") this.expect("is");
|
|
202
203
|
const choice = this.identifier();
|
|
203
204
|
entries.push({
|
|
204
|
-
key: `when ${tunable}
|
|
205
|
+
key: `when ${tunable} ${relation} ${choice}`,
|
|
205
206
|
value: this.block(),
|
|
206
207
|
span: this.span(start),
|
|
207
208
|
});
|