@hyperscale0/hsx 4.1.1 → 4.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 +21 -0
- package/README.md +72 -0
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +14 -4
- package/dist/src/compile.js.map +1 -1
- package/dist/src/examples-bundle.d.ts +3 -0
- package/dist/src/examples-bundle.d.ts.map +1 -0
- package/dist/src/examples-bundle.js +17 -0
- package/dist/src/examples-bundle.js.map +1 -0
- package/dist/src/examples.d.ts +10 -0
- package/dist/src/examples.d.ts.map +1 -0
- package/dist/src/examples.js +2 -0
- package/dist/src/examples.js.map +1 -0
- package/dist/src/headers.d.ts +8 -2
- package/dist/src/headers.d.ts.map +1 -1
- package/dist/src/headers.js +36 -2
- package/dist/src/headers.js.map +1 -1
- package/dist/src/keywords.d.ts +50 -0
- package/dist/src/keywords.d.ts.map +1 -0
- package/dist/src/keywords.js +125 -0
- package/dist/src/keywords.js.map +1 -0
- package/dist/src/language.d.ts +26 -0
- package/dist/src/language.d.ts.map +1 -0
- package/dist/src/language.js +440 -0
- package/dist/src/language.js.map +1 -0
- package/dist/src/lex.d.ts +6 -1
- package/dist/src/lex.d.ts.map +1 -1
- package/dist/src/lex.js +21 -1
- package/dist/src/lex.js.map +1 -1
- package/dist/src/std-bundle.js +2 -2
- 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/examples.md +20 -0
- package/examples/cards.hsx +30 -0
- package/examples/collections.hsx +28 -0
- package/examples/escrow.hsx +22 -0
- package/examples/financing.hsx +24 -0
- package/examples/insurance.hsx +26 -0
- package/examples/lending.hsx +35 -0
- package/examples/library.hsx +2 -1
- package/examples/marketplace.hsx +26 -0
- package/examples/money.hsx +17 -0
- package/examples/reporting.hsx +25 -0
- package/examples/savings.hsx +19 -0
- package/examples/serviced.hsx +1 -0
- package/examples/travel.hsx +26 -0
- package/examples/wallet.hsx +21 -0
- package/package.json +14 -5
- package/src/compile.ts +19 -4
- package/src/examples-bundle.ts +18 -0
- package/src/examples.ts +9 -0
- package/src/headers.ts +37 -1
- package/src/keywords.ts +126 -0
- package/src/language.ts +629 -0
- package/src/lex.ts +26 -3
- package/src/std-bundle.ts +2 -2
- package/src/version.ts +1 -1
- package/std/cards.hsx +5 -0
- package/std/savings.hsx +2 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Freelancers publish offers and clients place orders with funds held until work is accepted.
|
|
2
|
+
program freelance_marketplace "Freelance marketplace"
|
|
3
|
+
use marketplace
|
|
4
|
+
use escrow
|
|
5
|
+
|
|
6
|
+
object project "Project" {
|
|
7
|
+
fields { brief: text, deliverable: text, price: money }
|
|
8
|
+
columns: [brief, deliverable, price]
|
|
9
|
+
attach offer = marketplace.listing {
|
|
10
|
+
seller: owner
|
|
11
|
+
expose publish as publish_offer
|
|
12
|
+
expose withdraw as withdraw_offer
|
|
13
|
+
}
|
|
14
|
+
attach order = marketplace.order {
|
|
15
|
+
listing: offer, buyer: actor
|
|
16
|
+
expose commit as place_order
|
|
17
|
+
expose fulfill as finish_order
|
|
18
|
+
}
|
|
19
|
+
attach payment = escrow.hold {
|
|
20
|
+
payer: actor, payee: owner
|
|
21
|
+
expose fund as fund_project
|
|
22
|
+
expose deliver as submit_work
|
|
23
|
+
expose accept as accept_work
|
|
24
|
+
expose dispute as dispute_work
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// A tutoring studio collects a fixed fee for each lesson and can cancel unpaid bookings.
|
|
2
|
+
program tutoring_studio "Tutoring studio"
|
|
3
|
+
use money
|
|
4
|
+
|
|
5
|
+
object lesson "Lesson" {
|
|
6
|
+
fields {
|
|
7
|
+
student: text
|
|
8
|
+
tutor: text
|
|
9
|
+
startsAt: date
|
|
10
|
+
}
|
|
11
|
+
columns: [student, tutor, startsAt]
|
|
12
|
+
attach payment = money.transfer {
|
|
13
|
+
payer: actor, payee: operator, amount: 150 SAR
|
|
14
|
+
expose pay as pay_for_lesson
|
|
15
|
+
expose cancel as cancel_payment
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// An equipment lender keeps an ageing report over its financed purchases and recorded repayments.
|
|
2
|
+
program equipment_loan_reports "Equipment loan reports"
|
|
3
|
+
use reporting
|
|
4
|
+
use financing
|
|
5
|
+
|
|
6
|
+
object purchase "Equipment loan" {
|
|
7
|
+
fields { businessName: text, equipment: text, price: money }
|
|
8
|
+
columns: [businessName, equipment, price]
|
|
9
|
+
attach allowance = financing.limits {
|
|
10
|
+
borrower: owner, per_borrower: 200000 SAR
|
|
11
|
+
expose approve as approve_business
|
|
12
|
+
}
|
|
13
|
+
attach budget = financing.portfolio_limit {
|
|
14
|
+
limit: 5000000 SAR
|
|
15
|
+
expose approve as approve_budget
|
|
16
|
+
}
|
|
17
|
+
attach loan = financing.installments {
|
|
18
|
+
borrower: owner, capital: operator
|
|
19
|
+
months: 12, profit: 6%, disburse_to: borrower
|
|
20
|
+
limits: allowance, portfolio: budget
|
|
21
|
+
expose sign as sign_purchase_loan
|
|
22
|
+
expose disburse as fund_purchase
|
|
23
|
+
}
|
|
24
|
+
attach reports = reporting.portfolio { on: loan }
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Six members contribute to a shared pot and receive it in their agreed calendar order.
|
|
2
|
+
program neighbourhood_savings "Savings circle"
|
|
3
|
+
use savings
|
|
4
|
+
|
|
5
|
+
object group "Savings group" {
|
|
6
|
+
fields { groupName: text, purpose: text }
|
|
7
|
+
columns: [groupName, purpose]
|
|
8
|
+
attach circle = savings.circle {
|
|
9
|
+
contribution: 500 SAR, members: 6, starts: 2027-01-01
|
|
10
|
+
expose disband as disband_circle
|
|
11
|
+
expose close as close_circle
|
|
12
|
+
}
|
|
13
|
+
attach membership = savings.membership {
|
|
14
|
+
circle: circle, member: actor
|
|
15
|
+
expose activate as confirm_membership
|
|
16
|
+
expose withdraw as leave_circle
|
|
17
|
+
}
|
|
18
|
+
// Contributions and each member's payout follow the declared calendar.
|
|
19
|
+
}
|
package/examples/serviced.hsx
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// A travel agency takes deposits for an insured holiday and pays its supplier after confirmation.
|
|
2
|
+
program travel_agency "Travel agency"
|
|
3
|
+
use travel
|
|
4
|
+
use insurance
|
|
5
|
+
party hotel: business
|
|
6
|
+
|
|
7
|
+
object holiday "Holiday booking" {
|
|
8
|
+
fields { destination: text, leadTraveller: text }
|
|
9
|
+
columns: [destination, leadTraveller]
|
|
10
|
+
attach trip = travel.package {
|
|
11
|
+
price: 5000 SAR, supplier_cost: 3500 SAR, departure: 2027-07-01
|
|
12
|
+
expose publish as publish_trip
|
|
13
|
+
}
|
|
14
|
+
// Bind travel_insurer to an ADL adapter before activating cover.
|
|
15
|
+
attach protection = insurance.cover {
|
|
16
|
+
holder: actor, adapter: "travel_insurer", covers: reservation
|
|
17
|
+
expose activate as activate_cover
|
|
18
|
+
}
|
|
19
|
+
attach reservation = travel.booking {
|
|
20
|
+
package: trip, buyer: actor, supplier: hotel, cover: protection
|
|
21
|
+
expose deposit as pay_deposit
|
|
22
|
+
expose balance as pay_balance
|
|
23
|
+
expose confirm as confirm_completion
|
|
24
|
+
expose cancel_early as cancel_booking
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Members prepay for workshop visits and the studio reserves each spend before charging it.
|
|
2
|
+
program workshop_credits "Workshop credits"
|
|
3
|
+
use wallet
|
|
4
|
+
|
|
5
|
+
object membership "Membership" {
|
|
6
|
+
fields { memberName: text, membershipNumber: text }
|
|
7
|
+
columns: [memberName, membershipNumber]
|
|
8
|
+
attach balance = wallet.balance {
|
|
9
|
+
holder: owner
|
|
10
|
+
expose activate as open_balance
|
|
11
|
+
expose topup as add_credit
|
|
12
|
+
expose withdraw as withdraw_credit
|
|
13
|
+
expose close as close_balance
|
|
14
|
+
}
|
|
15
|
+
attach visit = wallet.spend {
|
|
16
|
+
wallet: balance, payee: operator
|
|
17
|
+
expose reserve as reserve_visit
|
|
18
|
+
expose pay as charge_visit
|
|
19
|
+
expose cancel as cancel_visit
|
|
20
|
+
}
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperscale0/hsx",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "The strictly typed HSX language: compile total financial programs into canonical UDL.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hsx",
|
|
@@ -40,6 +40,14 @@
|
|
|
40
40
|
"types": "./dist/src/cost.d.ts",
|
|
41
41
|
"default": "./dist/src/cost.js"
|
|
42
42
|
},
|
|
43
|
+
"./examples": {
|
|
44
|
+
"types": "./dist/src/examples.d.ts",
|
|
45
|
+
"default": "./dist/src/examples.js"
|
|
46
|
+
},
|
|
47
|
+
"./language": {
|
|
48
|
+
"types": "./dist/src/language.d.ts",
|
|
49
|
+
"default": "./dist/src/language.js"
|
|
50
|
+
},
|
|
43
51
|
"./package.json": "./package.json",
|
|
44
52
|
"./std/*": "./std/*"
|
|
45
53
|
},
|
|
@@ -71,9 +79,10 @@
|
|
|
71
79
|
"postpack": "bun scripts/pack-exports.ts restore",
|
|
72
80
|
"prepack": "bun run build && bun scripts/pack-exports.ts apply",
|
|
73
81
|
"std:bundle": "bun scripts/bundle-std.ts",
|
|
74
|
-
"generate": "bun run std:bundle && bun run docs:build",
|
|
75
|
-
"prepare": "bun run std:bundle",
|
|
76
|
-
"check": "bun run typecheck && tsc -p playground/tsconfig.json && bun test test"
|
|
82
|
+
"generate": "bun run std:bundle && bun run examples:bundle && bun run docs:build",
|
|
83
|
+
"prepare": "bun run std:bundle && bun run examples:bundle",
|
|
84
|
+
"check": "bun run typecheck && tsc -p playground/tsconfig.json && bun test test",
|
|
85
|
+
"examples:bundle": "bun scripts/bundle-examples.ts"
|
|
77
86
|
},
|
|
78
87
|
"dependencies": {
|
|
79
88
|
"@hyperscale0/adl": "1.0.0-beta.8",
|
|
@@ -89,5 +98,5 @@
|
|
|
89
98
|
"Amir Ayub",
|
|
90
99
|
"Sara AlBakaawi"
|
|
91
100
|
],
|
|
92
|
-
"gitHead": "
|
|
101
|
+
"gitHead": "c02b50cd28968db4b64bededd947e74f62fe5a4b"
|
|
93
102
|
}
|
package/src/compile.ts
CHANGED
|
@@ -848,7 +848,9 @@ export function compile(
|
|
|
848
848
|
{ span: origin },
|
|
849
849
|
partyParameter ? "subject_party_unbound" : "HSX1001",
|
|
850
850
|
`${id} needs ${param.key}`,
|
|
851
|
-
|
|
851
|
+
partyParameter
|
|
852
|
+
? `bind ${param.key} to ${[...subjectPartyRoles, ...Object.keys(document.parties)].join(", ")} or declare a party`
|
|
853
|
+
: `add ${param.key}: value inside ${id}`,
|
|
852
854
|
);
|
|
853
855
|
}
|
|
854
856
|
environment.set(param.key, actual);
|
|
@@ -932,7 +934,8 @@ export function compile(
|
|
|
932
934
|
(binding.kind === "name" && binding.value === expr.value))
|
|
933
935
|
)
|
|
934
936
|
return expr;
|
|
935
|
-
|
|
937
|
+
// A resolved party parameter takes precedence over a same-named attachment.
|
|
938
|
+
if (attachmentInfo && !resolvedParties.has(expr.value)) {
|
|
936
939
|
const [local, ...tail] = expr.value.split(".");
|
|
937
940
|
const target = `${attachmentInfo.subjectKindId}_${local}`;
|
|
938
941
|
if (attachmentSubjects.has(target))
|
|
@@ -1004,7 +1007,9 @@ export function compile(
|
|
|
1004
1007
|
actual,
|
|
1005
1008
|
attachmentInfo ? "subject_party_unbound" : "HSX1001",
|
|
1006
1009
|
`${param.key} needs a declared party`,
|
|
1007
|
-
|
|
1010
|
+
attachmentInfo
|
|
1011
|
+
? `bind ${param.key} to ${[...subjectPartyRoles, ...Object.keys(document.parties)].join(", ")} or declare a party`
|
|
1012
|
+
: "declare a party and use its name here",
|
|
1008
1013
|
);
|
|
1009
1014
|
const party = document.parties[v.value];
|
|
1010
1015
|
if (
|
|
@@ -2561,11 +2566,21 @@ export function compile(
|
|
|
2561
2566
|
);
|
|
2562
2567
|
if (!found) {
|
|
2563
2568
|
const renameEntry = renameEntries.get(oldName) ?? entry;
|
|
2569
|
+
const declared = attachedInst
|
|
2570
|
+
? Object.values(attachedInst.actions).flatMap(
|
|
2571
|
+
(action) =>
|
|
2572
|
+
action.subject?.requirements.map(
|
|
2573
|
+
(requirement) => requirement.field.name,
|
|
2574
|
+
) ?? [],
|
|
2575
|
+
)
|
|
2576
|
+
: [];
|
|
2564
2577
|
failWithCode(
|
|
2565
2578
|
renameEntry,
|
|
2566
2579
|
"subject_field_unknown",
|
|
2567
2580
|
`rename source '${oldName}' is not a declared subject requirement of ${targetTemplate}`,
|
|
2568
|
-
|
|
2581
|
+
declared.length
|
|
2582
|
+
? `rename one of: ${[...new Set(declared)].join(", ")}`
|
|
2583
|
+
: "rename a declared subject requirement",
|
|
2569
2584
|
);
|
|
2570
2585
|
}
|
|
2571
2586
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Generated by open/hsx/scripts/bundle-examples.ts. Do not edit by hand.
|
|
2
|
+
import type { HsxExample } from "./examples.ts";
|
|
3
|
+
export const examples: HsxExample[] = [
|
|
4
|
+
{"id":"cards","title":"Team expense cards","summary":"A company issues expense cards to employees and reserves each supplier payment before capture.","headers":["cards"],"source":"// A company issues expense cards to employees and reserves each supplier payment before capture.\nprogram team_expense_cards \"Team expense cards\"\nuse cards\nparty supplier: business\n\nobject employee \"Employee card\" {\n fields { employeeName: text, department: text }\n columns: [employeeName, department]\n attach holder = cards.cardholder {\n holder: owner\n expose activate as activate_employee\n expose suspend as suspend_employee\n }\n attach card = cards.card {\n holder: holder, spend_limit: 5000 SAR\n expose activate as activate_card\n expose freeze as freeze_card\n expose unfreeze as unfreeze_card\n }\n attach payment = cards.authorization {\n card: card, merchant: supplier\n expose approve as approve_payment\n expose capture as capture_payment\n expose reverse as reverse_payment\n }\n attach receipt = cards.transaction {\n authorization: payment\n expose refund as refund_payment\n }\n}\n"},
|
|
5
|
+
{"id":"collections","title":"Small business collections","summary":"A business lender refers overdue instalments to an agency with limits on collection contacts.","headers":["collections","financing"],"source":"// A business lender refers overdue instalments to an agency with limits on collection contacts.\nprogram business_collections \"Small business collections\"\nuse collections\nuse financing\nparty agency: business\n\nobject facility \"Business loan\" {\n fields { businessName: text, purpose: text, price: money }\n columns: [businessName, purpose, price]\n attach allowance = financing.limits { borrower: owner, per_borrower: 100000 SAR }\n attach budget = financing.portfolio_limit { limit: 2000000 SAR }\n attach loan = financing.installments {\n borrower: owner, capital: operator\n months: 12, profit: 6%, disburse_to: borrower\n limits: allowance, portfolio: budget\n expose sign as sign_loan\n expose disburse as release_loan\n }\n attach recovery = collections.case {\n on: loan, agency: agency\n expose assign as assign_case\n expose recover as recover_payment\n expose recall as recall_case\n }\n attach contact = collections.contact {\n case: recovery, agency: agency\n }\n}\n"},
|
|
6
|
+
{"id":"escrow","title":"Used device sales","summary":"Buyers fund a used device purchase, then accept delivery or raise a dispute before release.","headers":["escrow"],"source":"// Buyers fund a used device purchase, then accept delivery or raise a dispute before release.\nprogram used_device_sales \"Used device sales\"\nuse escrow\n\nobject device \"Device\" {\n fields {\n model: text\n condition: text\n serialNumber: text\n price: money\n }\n columns: [model, condition, price]\n attach sale = escrow.hold {\n payer: actor, payee: owner\n expose fund as pay_for_device\n expose deliver as confirm_delivery\n expose accept as accept_device\n expose dispute as report_problem\n expose verify_return as verify_return\n expose refund as refund_buyer\n }\n}\n"},
|
|
7
|
+
{"id":"financing","title":"Tuition instalments","summary":"A tuition lender funds a student's fees over three interest-free instalments with borrowing limits.","headers":["financing"],"source":"// A tuition lender funds a student's fees over three interest-free instalments with borrowing limits.\nprogram tuition_instalments \"Tuition instalments\"\nuse financing\n\nobject enrolment \"Enrolment\" {\n fields { student: text, course: text, price: money }\n columns: [student, course, price]\n attach allowance = financing.limits {\n borrower: owner, per_borrower: 12000 SAR\n expose approve as approve_student\n }\n attach budget = financing.portfolio_limit {\n limit: 500000 SAR\n expose approve as approve_budget\n }\n attach tuition = financing.installments {\n borrower: owner, capital: operator\n months: 3, profit: 0%, disburse_to: borrower\n limits: allowance, portfolio: budget\n expose sign as sign_tuition_plan\n expose disburse as fund_tuition\n expose payoff as repay_early\n }\n}\n"},
|
|
8
|
+
{"id":"insurance","title":"Device insurance","summary":"A device retailer collects the purchase price and attaches cover with a claim review process.","headers":["insurance","money"],"source":"// A device retailer collects the purchase price and attaches cover with a claim review process.\nprogram device_insurance \"Device insurance\"\nuse insurance\nuse money\nparty inspector: staff role claims\n\nobject device \"Insured device\" {\n fields { model: text, serialNumber: text, purchaseDate: date }\n columns: [model, serialNumber, purchaseDate]\n attach purchase = money.transfer {\n payer: owner, payee: operator\n expose pay as pay_for_device\n }\n // Bind device_insurer to an ADL adapter before activating cover.\n attach protection = insurance.cover {\n holder: owner, adapter: \"device_insurer\", covers: purchase\n expose activate as activate_cover\n expose cancel as cancel_cover\n }\n attach claim = insurance.claim {\n cover: protection, inspector: inspector\n expose approve as approve_claim\n expose deny as deny_claim\n expose pay as pay_claim\n }\n}\n"},
|
|
9
|
+
{"id":"lending","title":"Community business lending","summary":"Investors fund a small business loan from their wallets and share its recorded repayments.","headers":["lending","financing","wallet"],"source":"// Investors fund a small business loan from their wallets and share its recorded repayments.\nprogram community_business_lending \"Community business lending\"\nuse lending\nuse financing\nuse wallet\n\nobject business \"Business funding\" {\n fields { businessName: text, purpose: text, price: money }\n columns: [businessName, purpose, price]\n attach allowance = financing.limits { borrower: owner, per_borrower: 100000 SAR }\n attach budget = financing.portfolio_limit { limit: 2000000 SAR }\n attach loan = financing.installments {\n borrower: owner, capital: operator\n months: 12, profit: 6%, disburse_to: borrower\n limits: allowance, portfolio: budget\n expose sign as sign_loan\n expose disburse as release_loan\n }\n attach investor_balance = wallet.balance {\n holder: actor\n expose activate as open_wallet\n expose topup as add_funds\n }\n attach funding = lending.round {\n borrower: owner, plan: loan\n expose close as close_funding\n }\n attach investment = lending.commitment {\n round: funding, wallet: investor_balance, investor: actor\n expose withdraw as withdraw_investment\n }\n attach returns = lending.distribution {\n round: funding, receipt: loan.settlement\n }\n}\n"},
|
|
10
|
+
{"id":"library","title":"Financed car sales","summary":"A minimal financed car sale connects escrow to a three-instalment plan and lending limits.","headers":["escrow","financing"],"source":"// A minimal financed car sale connects escrow to a three-instalment plan and lending limits.\nprogram cars \"Financed car sales\"\nuse escrow\nuse financing\nobject car \"Cars\" {\n fields { make: text, model: text, year: integer }\n attach sale = escrow.hold { payer: actor, payee: owner, expose fund as sell }\n attach limits = financing.limits { borrower: actor, per_borrower: 60000 SAR }\n attach ceiling = financing.portfolio_limit { limit: 1500000 SAR }\n attach plan = financing.installments {\n borrower: actor, capital: operator, share: 25%\n months: 3, profit: 2.5%, funds: sale, limits: limits, portfolio: ceiling\n expose create as finance\n }\n}\n"},
|
|
11
|
+
{"id":"marketplace","title":"Freelance marketplace","summary":"Freelancers publish offers and clients place orders with funds held until work is accepted.","headers":["marketplace","escrow"],"source":"// Freelancers publish offers and clients place orders with funds held until work is accepted.\nprogram freelance_marketplace \"Freelance marketplace\"\nuse marketplace\nuse escrow\n\nobject project \"Project\" {\n fields { brief: text, deliverable: text, price: money }\n columns: [brief, deliverable, price]\n attach offer = marketplace.listing {\n seller: owner\n expose publish as publish_offer\n expose withdraw as withdraw_offer\n }\n attach order = marketplace.order {\n listing: offer, buyer: actor\n expose commit as place_order\n expose fulfill as finish_order\n }\n attach payment = escrow.hold {\n payer: actor, payee: owner\n expose fund as fund_project\n expose deliver as submit_work\n expose accept as accept_work\n expose dispute as dispute_work\n }\n}\n"},
|
|
12
|
+
{"id":"money","title":"Tutoring studio","summary":"A tutoring studio collects a fixed fee for each lesson and can cancel unpaid bookings.","headers":["money"],"source":"// A tutoring studio collects a fixed fee for each lesson and can cancel unpaid bookings.\nprogram tutoring_studio \"Tutoring studio\"\nuse money\n\nobject lesson \"Lesson\" {\n fields {\n student: text\n tutor: text\n startsAt: date\n }\n columns: [student, tutor, startsAt]\n attach payment = money.transfer {\n payer: actor, payee: operator, amount: 150 SAR\n expose pay as pay_for_lesson\n expose cancel as cancel_payment\n }\n}\n"},
|
|
13
|
+
{"id":"reporting","title":"Equipment loan reports","summary":"An equipment lender keeps an ageing report over its financed purchases and recorded repayments.","headers":["reporting","financing"],"source":"// An equipment lender keeps an ageing report over its financed purchases and recorded repayments.\nprogram equipment_loan_reports \"Equipment loan reports\"\nuse reporting\nuse financing\n\nobject purchase \"Equipment loan\" {\n fields { businessName: text, equipment: text, price: money }\n columns: [businessName, equipment, price]\n attach allowance = financing.limits {\n borrower: owner, per_borrower: 200000 SAR\n expose approve as approve_business\n }\n attach budget = financing.portfolio_limit {\n limit: 5000000 SAR\n expose approve as approve_budget\n }\n attach loan = financing.installments {\n borrower: owner, capital: operator\n months: 12, profit: 6%, disburse_to: borrower\n limits: allowance, portfolio: budget\n expose sign as sign_purchase_loan\n expose disburse as fund_purchase\n }\n attach reports = reporting.portfolio { on: loan }\n}\n"},
|
|
14
|
+
{"id":"savings","title":"Savings circle","summary":"Six members contribute to a shared pot and receive it in their agreed calendar order.","headers":["savings"],"source":"// Six members contribute to a shared pot and receive it in their agreed calendar order.\nprogram neighbourhood_savings \"Savings circle\"\nuse savings\n\nobject group \"Savings group\" {\n fields { groupName: text, purpose: text }\n columns: [groupName, purpose]\n attach circle = savings.circle {\n contribution: 500 SAR, members: 6, starts: 2027-01-01\n expose disband as disband_circle\n expose close as close_circle\n }\n attach membership = savings.membership {\n circle: circle, member: actor\n expose activate as confirm_membership\n expose withdraw as leave_circle\n }\n // Contributions and each member's payout follow the declared calendar.\n}\n"},
|
|
15
|
+
{"id":"serviced","title":"Car financing","summary":"A car finance company services loans with late charges, payment reminders and early repayment.","headers":["escrow","financing","collections"],"source":"// A car finance company services loans with late charges, payment reminders and early repayment.\nprogram car_financing \"Car financing\"\nuse escrow\nuse financing\nuse collections\nobject car \"Car\" {\n fields { make: text, model: text, year: integer, vin: text, price: money }\n columns: [make, model, year, vin, price]\n attach sale = escrow.hold { payer: actor, payee: operator, expose fund as purchase }\n attach limits = financing.limits { borrower: actor, per_borrower: 250000 SAR }\n attach ceiling = financing.portfolio_limit { limit: 10000000 SAR }\n attach plan = financing.installments {\n borrower: actor, capital: operator\n months: 36, profit: 8.5%, down_payment: 10%\n apply: fines_profit_principal, payoff_rebate: 100%, write_off_after: 90d\n funds: sale, limits: limits, portfolio: ceiling\n expose create as finance, expose sign as sign_contract, expose payoff as payoff\n }\n attach late = financing.late_charge {\n on: plan, grace: 3d, fine: 50 SAR, cap: 25 SAR\n fines_to: operator, costs_to: operator, borrower: actor\n }\n attach reminders = collections.reminder { on: plan, operator: operator, before_days: 3, overdue_days: 1 }\n}\n"},
|
|
16
|
+
{"id":"travel","title":"Travel agency","summary":"A travel agency takes deposits for an insured holiday and pays its supplier after confirmation.","headers":["travel","insurance"],"source":"// A travel agency takes deposits for an insured holiday and pays its supplier after confirmation.\nprogram travel_agency \"Travel agency\"\nuse travel\nuse insurance\nparty hotel: business\n\nobject holiday \"Holiday booking\" {\n fields { destination: text, leadTraveller: text }\n columns: [destination, leadTraveller]\n attach trip = travel.package {\n price: 5000 SAR, supplier_cost: 3500 SAR, departure: 2027-07-01\n expose publish as publish_trip\n }\n // Bind travel_insurer to an ADL adapter before activating cover.\n attach protection = insurance.cover {\n holder: actor, adapter: \"travel_insurer\", covers: reservation\n expose activate as activate_cover\n }\n attach reservation = travel.booking {\n package: trip, buyer: actor, supplier: hotel, cover: protection\n expose deposit as pay_deposit\n expose balance as pay_balance\n expose confirm as confirm_completion\n expose cancel_early as cancel_booking\n }\n}\n"},
|
|
17
|
+
{"id":"wallet","title":"Workshop credits","summary":"Members prepay for workshop visits and the studio reserves each spend before charging it.","headers":["wallet"],"source":"// Members prepay for workshop visits and the studio reserves each spend before charging it.\nprogram workshop_credits \"Workshop credits\"\nuse wallet\n\nobject membership \"Membership\" {\n fields { memberName: text, membershipNumber: text }\n columns: [memberName, membershipNumber]\n attach balance = wallet.balance {\n holder: owner\n expose activate as open_balance\n expose topup as add_credit\n expose withdraw as withdraw_credit\n expose close as close_balance\n }\n attach visit = wallet.spend {\n wallet: balance, payee: operator\n expose reserve as reserve_visit\n expose pay as charge_visit\n expose cancel as cancel_visit\n }\n}\n"},
|
|
18
|
+
];
|
package/src/examples.ts
ADDED
package/src/headers.ts
CHANGED
|
@@ -21,10 +21,11 @@ export const HEADER_NAMES = [
|
|
|
21
21
|
/** The compiler frontend owns header metadata used by docs and catalogue consumers. */
|
|
22
22
|
export function headerManifest(
|
|
23
23
|
library: StandardLibrary = bundledStandardLibrary,
|
|
24
|
+
names: readonly string[] = HEADER_NAMES,
|
|
24
25
|
) {
|
|
25
26
|
return {
|
|
26
27
|
version: 4,
|
|
27
|
-
headers:
|
|
28
|
+
headers: names.map((name) => {
|
|
28
29
|
const source = library.source(name);
|
|
29
30
|
if (!source) throw new Error(`Missing standard header ${name}`);
|
|
30
31
|
const parsed = parseProgram(source);
|
|
@@ -102,6 +103,23 @@ export function headerManifest(
|
|
|
102
103
|
return {
|
|
103
104
|
name: decl.name,
|
|
104
105
|
qualifiedName: `${name}.${decl.name}`,
|
|
106
|
+
signature: source
|
|
107
|
+
.slice(decl.span.start, decl.body.span.start)
|
|
108
|
+
.replace(/^instrument\s+/, "")
|
|
109
|
+
.trim(),
|
|
110
|
+
states: (() => {
|
|
111
|
+
const lifecycle = decl.body.entries.find(
|
|
112
|
+
(entry) => entry.key === "lifecycle",
|
|
113
|
+
)?.value;
|
|
114
|
+
const states =
|
|
115
|
+
lifecycle?.kind === "block"
|
|
116
|
+
? lifecycle.entries.find((entry) => entry.key === "states")
|
|
117
|
+
?.value
|
|
118
|
+
: undefined;
|
|
119
|
+
return states?.kind === "list"
|
|
120
|
+
? states.items.map(spelling)
|
|
121
|
+
: [];
|
|
122
|
+
})(),
|
|
105
123
|
summary:
|
|
106
124
|
summary?.kind === "text"
|
|
107
125
|
? summary.value
|
|
@@ -145,6 +163,24 @@ export function headerManifest(
|
|
|
145
163
|
parties: tunables
|
|
146
164
|
.filter((t) => t.type === "party")
|
|
147
165
|
.map((t) => t.name),
|
|
166
|
+
// Fields the attached object must carry (or rename to) before
|
|
167
|
+
// the instrument's actions can read them.
|
|
168
|
+
subject: (() => {
|
|
169
|
+
const fields = new Map<string, string>();
|
|
170
|
+
for (const entry of decl.body.entries) {
|
|
171
|
+
if (!entry.key.startsWith("action ")) continue;
|
|
172
|
+
if (entry.value.kind !== "block") continue;
|
|
173
|
+
const subject = entry.value.entries.find(
|
|
174
|
+
(slot) => slot.key === "subject",
|
|
175
|
+
)?.value;
|
|
176
|
+
if (subject?.kind !== "block") continue;
|
|
177
|
+
for (const field of subject.entries) {
|
|
178
|
+
const type = spelling(field.value);
|
|
179
|
+
if (type !== "adapter") fields.set(field.key, type);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return [...fields].map(([name, type]) => ({ name, type }));
|
|
183
|
+
})(),
|
|
148
184
|
actions: actions(decl.body),
|
|
149
185
|
};
|
|
150
186
|
}),
|
package/src/keywords.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { KEYWORDS } from "./lex.ts";
|
|
2
|
+
|
|
3
|
+
/** Business explanations shared by every HSX editor. */
|
|
4
|
+
export const KEYWORD_HELP = {
|
|
5
|
+
program: [
|
|
6
|
+
"Names the financial product and gives it a title.",
|
|
7
|
+
"Product contract",
|
|
8
|
+
],
|
|
9
|
+
header: ["Names a reusable library of instruments."],
|
|
10
|
+
use: ["Makes a header's instruments available to this program."],
|
|
11
|
+
party: [
|
|
12
|
+
"Declares a person or business that can own accounts or act in an agreement.",
|
|
13
|
+
"Parties",
|
|
14
|
+
],
|
|
15
|
+
role: ["Names the responsibility a declared party has in this product."],
|
|
16
|
+
currency: ["Sets the currency used by the program's money amounts."],
|
|
17
|
+
instrument: [
|
|
18
|
+
"Defines an agreement's fields, states and permitted actions.",
|
|
19
|
+
"Instruments",
|
|
20
|
+
],
|
|
21
|
+
fields: ["Declares the values an object or agreement retains.", "Fields"],
|
|
22
|
+
lifecycle: [
|
|
23
|
+
"Lists the agreement's possible states and its starting state.",
|
|
24
|
+
"Lifecycle",
|
|
25
|
+
],
|
|
26
|
+
action: [
|
|
27
|
+
"Defines an operation that can change an agreement or move money.",
|
|
28
|
+
"Actions",
|
|
29
|
+
],
|
|
30
|
+
expose: [
|
|
31
|
+
"Makes an attached action available under a public name.",
|
|
32
|
+
"Object action bindings",
|
|
33
|
+
],
|
|
34
|
+
hide: ["Removes an action from the program's public actions."],
|
|
35
|
+
as: ["Gives an exposed action its public name."],
|
|
36
|
+
cap: ["Limits a percentage charge to a maximum money amount.", "Capped rate"],
|
|
37
|
+
of: [
|
|
38
|
+
"Names the owner of an account, or the records included in a count or sum.",
|
|
39
|
+
],
|
|
40
|
+
when: ["Includes rules only when a parameter selects the stated option."],
|
|
41
|
+
constraints: [
|
|
42
|
+
"Requires parameter values to agree with each other before the program can be used.",
|
|
43
|
+
],
|
|
44
|
+
requires: [
|
|
45
|
+
"Rejects an action unless the stated condition holds.",
|
|
46
|
+
"Requirements",
|
|
47
|
+
],
|
|
48
|
+
invariants: [
|
|
49
|
+
"States conditions that the agreement must preserve.",
|
|
50
|
+
"Invariants",
|
|
51
|
+
],
|
|
52
|
+
moves: [
|
|
53
|
+
"Declares a transfer, reservation, posting or release of money.",
|
|
54
|
+
"Moves",
|
|
55
|
+
],
|
|
56
|
+
from: [
|
|
57
|
+
"Names the account money leaves, or the state an action can start from.",
|
|
58
|
+
],
|
|
59
|
+
to: ["Names the account money reaches, or the state an action enters."],
|
|
60
|
+
in: [
|
|
61
|
+
"Requires a referenced agreement to be in one of the listed states.",
|
|
62
|
+
"State requirement",
|
|
63
|
+
],
|
|
64
|
+
by: ["Names who performs the stated operation."],
|
|
65
|
+
for: ["Identifies the subject of the stated rule."],
|
|
66
|
+
is: ["Selects rules for one named parameter choice."],
|
|
67
|
+
unique: [
|
|
68
|
+
"Rejects a repeated combination of values in the named namespace.",
|
|
69
|
+
"Unique requirement",
|
|
70
|
+
],
|
|
71
|
+
on: ["Lists the fields whose combined values must be unique."],
|
|
72
|
+
count: [
|
|
73
|
+
"Limits how many selected records may exist.",
|
|
74
|
+
"Aggregate requirement",
|
|
75
|
+
],
|
|
76
|
+
sum: [
|
|
77
|
+
"Limits the total value of a field across selected records.",
|
|
78
|
+
"Aggregate requirement",
|
|
79
|
+
],
|
|
80
|
+
hours: [
|
|
81
|
+
"Allows an action only within the stated local hours.",
|
|
82
|
+
"Hours requirement",
|
|
83
|
+
],
|
|
84
|
+
between: ["Introduces the opening time of an allowed time window."],
|
|
85
|
+
and: ["Introduces the closing time of an allowed time window."],
|
|
86
|
+
timezone: ["Chooses the timezone used to evaluate allowed hours."],
|
|
87
|
+
evidence: [
|
|
88
|
+
"Requires evidence about the subject with the stated check and result.",
|
|
89
|
+
"Evidence requirement",
|
|
90
|
+
],
|
|
91
|
+
reserve: [
|
|
92
|
+
"Holds money for later posting or release.",
|
|
93
|
+
"internal_transfer.reserve",
|
|
94
|
+
],
|
|
95
|
+
post: ["Completes a previously reserved transfer.", "internal_transfer.post"],
|
|
96
|
+
void: [
|
|
97
|
+
"Releases a previously reserved transfer without paying its recipient.",
|
|
98
|
+
"internal_transfer.void",
|
|
99
|
+
],
|
|
100
|
+
capture: ["Stores the transfer reference for a later posting or release."],
|
|
101
|
+
fee: ["Applies the stated charges to a money movement.", "Move fees"],
|
|
102
|
+
shares: [
|
|
103
|
+
"Divides a money movement among the listed recipients.",
|
|
104
|
+
"Move shares",
|
|
105
|
+
],
|
|
106
|
+
object: [
|
|
107
|
+
"Declares a business object with fields and attached agreements.",
|
|
108
|
+
"Subjects",
|
|
109
|
+
],
|
|
110
|
+
attach: [
|
|
111
|
+
"Adds an instrument to an object and binds its parameters.",
|
|
112
|
+
"Subject attachments",
|
|
113
|
+
],
|
|
114
|
+
subject: [
|
|
115
|
+
"Declares object fields that an action requires before it can run.",
|
|
116
|
+
"Subject requirements",
|
|
117
|
+
],
|
|
118
|
+
rename: [
|
|
119
|
+
"Changes the names of fields exposed by an attachment.",
|
|
120
|
+
"Attachment field mappings",
|
|
121
|
+
],
|
|
122
|
+
columns: [
|
|
123
|
+
"Selects the fields shown when listing objects.",
|
|
124
|
+
"Presentation columns",
|
|
125
|
+
],
|
|
126
|
+
} satisfies Record<(typeof KEYWORDS)[number], readonly [string, string?]>;
|