@hyperscale0/hsx 5.5.0 → 5.7.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/dist/src/compile.d.ts +5 -0
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +99 -7
- package/dist/src/compile.js.map +1 -1
- package/dist/src/examples-bundle.js +3 -3
- package/dist/src/examples-bundle.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/README.md +45 -11
- package/docs/examples.md +1 -1
- package/examples/collections.hsx +8 -2
- package/examples/financing.hsx +5 -0
- package/examples/lending.hsx +7 -1
- package/package.json +3 -3
- package/src/compile.ts +127 -3
- package/src/examples-bundle.ts +3 -3
- package/src/std-bundle.ts +2 -2
- package/src/version.ts +1 -1
- package/std/financing.hsx +3 -1
- package/std/travel.hsx +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"std-bundle.js","sourceRoot":"","sources":["../../src/std-bundle.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,MAAM,CAAC,MAAM,iBAAiB,GAAgC,IAAI,GAAG,CAAiB;IACpF,CAAC,WAAW,EAAE,2oKAA2oK,CAAC;IAC1pK,CAAC,iBAAiB,EAAE,2sNAA2sN,CAAC;IAChuN,CAAC,YAAY,EAAE,+qHAA+qH,CAAC;IAC/rH,CAAC,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"std-bundle.js","sourceRoot":"","sources":["../../src/std-bundle.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,MAAM,CAAC,MAAM,iBAAiB,GAAgC,IAAI,GAAG,CAAiB;IACpF,CAAC,WAAW,EAAE,2oKAA2oK,CAAC;IAC1pK,CAAC,iBAAiB,EAAE,2sNAA2sN,CAAC;IAChuN,CAAC,YAAY,EAAE,+qHAA+qH,CAAC;IAC/rH,CAAC,eAAe,EAAE,qu9DAAqu9D,CAAC;IACxv9D,CAAC,eAAe,EAAE,01KAA01K,CAAC;IAC72K,CAAC,aAAa,EAAE,qwTAAqwT,CAAC;IACtxT,CAAC,iBAAiB,EAAE,s+TAAs+T,CAAC;IAC3/T,CAAC,WAAW,EAAE,u1VAAu1V,CAAC;IACt2V,CAAC,eAAe,EAAE,649CAA649C,CAAC;IACh69C,CAAC,aAAa,EAAE,85JAA85J,CAAC;IAC/6J,CAAC,YAAY,EAAE,o7SAAo7S,CAAC;IACp8S,CAAC,YAAY,EAAE,2jGAA2jG,CAAC;CAC5kG,CAAC,CAAC"}
|
package/dist/src/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const HSX_VERSION = "5.
|
|
1
|
+
export declare const HSX_VERSION = "5.7.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const HSX_VERSION = "5.
|
|
1
|
+
export const HSX_VERSION = "5.7.0";
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/docs/README.md
CHANGED
|
@@ -388,17 +388,51 @@ adapters are bound, participants have funds, or the flow can finish.
|
|
|
388
388
|
| `financing.late_charge` | `fine` is a fixed money amount, not a percentage of overdue principal. |
|
|
389
389
|
| `cards.card` | `spend_limit` is a per-authorization ceiling, not a monthly aggregate. |
|
|
390
390
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
391
|
+
A seller-owned marketplace object can declare `entryActions: [create_order]`
|
|
392
|
+
when `create_order` exposes an attachment's create action with its buyer bound
|
|
393
|
+
to `actor`. This admits active enrolled customers to that action. The default
|
|
394
|
+
is closed. A retained buyer can read the record and run its buyer actions;
|
|
395
|
+
the seller remains owner. Entry never grants access to an existing escrow.
|
|
396
|
+
|
|
397
|
+
`financing.limits` and `financing.portfolio_limit` declare `scope: product`.
|
|
398
|
+
Create and approve the portfolio limit once per Product and the borrower limit
|
|
399
|
+
once per borrower. Plans on later records resolve those shared references.
|
|
400
|
+
Ordinary references remain on the current record; shared references must match
|
|
401
|
+
the bound party accounts. The aggregate caps still apply across all plans.
|
|
402
|
+
|
|
403
|
+
`programTax` transfers use an operator-owned cash account keyed `programTax`.
|
|
404
|
+
The account primitive creates it automatically and shares it across the Product.
|
|
405
|
+
Zero-tax escrow releases need no additional party setup.
|
|
406
|
+
|
|
407
|
+
The financing, collections and lending samples expose payment creation, payment,
|
|
408
|
+
refund and payoff quotes through the object's existing action routes. A child
|
|
409
|
+
alias names the full attachment path, for example:
|
|
410
|
+
|
|
411
|
+
```hsx
|
|
412
|
+
expose enrolment.tuition.payment.create as create_payment
|
|
413
|
+
expose enrolment.tuition.payment.pay as pay_instalment
|
|
414
|
+
expose enrolment.tuition.payoff_quote.create as request_payoff_quote
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Read the parent object's actions and follow pagination to discover child actions.
|
|
418
|
+
Use the returned target, Build, digest and object revision when executing. Creation
|
|
419
|
+
targets the child attachment; later actions target the child's instance ID on the
|
|
420
|
+
same object. The caller must satisfy the header's actor rule. Parties bind from
|
|
421
|
+
the session and object ownership. Retry the same request with the same
|
|
422
|
+
Idempotency-Key to retrieve its result without moving money again.
|
|
423
|
+
|
|
424
|
+
Supply payment amount and identity at creation. Pass the quote instance as the
|
|
425
|
+
parent payoff's `quote` input. For a late charge on a multi-instalment plan, select
|
|
426
|
+
the overdue child with the creation input `slice`. Repayment uses the declared
|
|
427
|
+
allocation order and payoff rebate; aliases do not change those terms.
|
|
428
|
+
|
|
429
|
+
The [lending sample](../examples/lending.hsx) also exposes share creation. Callers
|
|
430
|
+
still need dates, agreement inputs, funded wallets and eligible settlements.
|
|
431
|
+
References without an explicit creation input require one matching agreement on
|
|
432
|
+
the object. In particular, lending distributions with multiple settlements or
|
|
433
|
+
commitments need authored selectors before callers can choose among them.
|
|
434
|
+
Insurance and travel need premium-slice records and actions. Savings needs dated
|
|
435
|
+
contribution records. Read each header's creation and lifecycle requirements.
|
|
402
436
|
|
|
403
437
|
## Checkout and reversals
|
|
404
438
|
|
package/docs/examples.md
CHANGED
|
@@ -9,7 +9,7 @@ Each file is an authored program. Object creation enters no agreement; an attach
|
|
|
9
9
|
| [Used device sales](../examples/escrow.hsx) | escrow | Buyers fund a used device purchase, then accept delivery or raise a dispute before release. |
|
|
10
10
|
| [Tuition instalments](../examples/financing.hsx) | financing | A tuition lender funds a student's fees over three interest-free instalments with borrowing limits. |
|
|
11
11
|
| [Device insurance](../examples/insurance.hsx) | insurance, money | A device retailer collects the purchase price and attaches cover with a claim review process. |
|
|
12
|
-
| [Community business lending](../examples/lending.hsx) | lending, financing, wallet | A lending composition with limit approvals, wallet commitments and cash distribution transitions.
|
|
12
|
+
| [Community business lending](../examples/lending.hsx) | lending, financing, wallet | A lending composition with limit approvals, wallet commitments and cash distribution transitions. |
|
|
13
13
|
| [Financed car sales](../examples/library.hsx) | escrow, financing | A minimal financed car sale connects escrow to a three-instalment plan and lending limits. |
|
|
14
14
|
| [Freelance marketplace](../examples/marketplace.hsx) | marketplace, escrow | Freelancers publish offers and clients place orders with funds held until work is accepted. |
|
|
15
15
|
| [Tutoring studio](../examples/money.hsx) | money | A tutoring studio collects a fixed fee for each lesson and can cancel unpaid bookings. |
|
package/examples/collections.hsx
CHANGED
|
@@ -7,8 +7,8 @@ party agency: business
|
|
|
7
7
|
object facility "Business loan" {
|
|
8
8
|
fields { businessName: text, purpose: text, price: money }
|
|
9
9
|
columns: [businessName, purpose, price]
|
|
10
|
-
attach allowance = financing.limits { expose create as create_allowance, borrower: owner, per_borrower: 100000 SAR }
|
|
11
|
-
attach budget = financing.portfolio_limit { expose create as create_budget, limit: 2000000 SAR }
|
|
10
|
+
attach allowance = financing.limits { expose create as create_allowance, expose approve as approve_borrower, borrower: owner, per_borrower: 100000 SAR }
|
|
11
|
+
attach budget = financing.portfolio_limit { expose create as create_budget, expose approve as approve_budget, limit: 2000000 SAR }
|
|
12
12
|
attach loan = financing.installments {
|
|
13
13
|
expose create as create_loan
|
|
14
14
|
borrower: owner, capital: operator
|
|
@@ -16,6 +16,7 @@ object facility "Business loan" {
|
|
|
16
16
|
limits: allowance, portfolio: budget
|
|
17
17
|
expose sign as sign_loan
|
|
18
18
|
expose disburse as release_loan
|
|
19
|
+
expose payoff as repay_early
|
|
19
20
|
}
|
|
20
21
|
attach recovery = collections.case {
|
|
21
22
|
expose create as create_recovery
|
|
@@ -29,3 +30,8 @@ object facility "Business loan" {
|
|
|
29
30
|
case: recovery, agency: agency
|
|
30
31
|
}
|
|
31
32
|
}
|
|
33
|
+
|
|
34
|
+
expose facility.loan.payment.create as create_payment
|
|
35
|
+
expose facility.loan.payment.pay as pay_instalment
|
|
36
|
+
expose facility.loan.payment.refund as refund_payment
|
|
37
|
+
expose facility.loan.payoff_quote.create as request_payoff_quote
|
package/examples/financing.hsx
CHANGED
|
@@ -25,3 +25,8 @@ object enrolment "Enrolment" {
|
|
|
25
25
|
expose payoff as repay_early
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
expose enrolment.tuition.payment.create as create_payment
|
|
30
|
+
expose enrolment.tuition.payment.pay as pay_instalment
|
|
31
|
+
expose enrolment.tuition.payment.refund as refund_payment
|
|
32
|
+
expose enrolment.tuition.payoff_quote.create as request_payoff_quote
|
package/examples/lending.hsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// A lending composition with limit approvals, wallet commitments and cash distribution transitions.
|
|
2
|
-
// Repayment and distribution-share child actions still need an execution path; this is not a complete public repayment flow.
|
|
3
2
|
program community_business_lending "Community business lending"
|
|
4
3
|
use lending
|
|
5
4
|
use financing
|
|
@@ -25,6 +24,7 @@ object business "Business funding" {
|
|
|
25
24
|
limits: allowance, portfolio: budget
|
|
26
25
|
expose sign as sign_loan
|
|
27
26
|
expose disburse as release_loan
|
|
27
|
+
expose payoff as repay_early
|
|
28
28
|
}
|
|
29
29
|
attach investor_balance = wallet.balance {
|
|
30
30
|
expose create as create_investor_balance
|
|
@@ -49,3 +49,9 @@ object business "Business funding" {
|
|
|
49
49
|
expose distribute_cash as distribute_returns
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
expose business.loan.payment.create as create_payment
|
|
54
|
+
expose business.loan.payment.pay as pay_instalment
|
|
55
|
+
expose business.loan.payment.refund as refund_payment
|
|
56
|
+
expose business.loan.payoff_quote.create as request_payoff_quote
|
|
57
|
+
expose business.returns.share.create as create_return_share
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperscale0/hsx",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.7.0",
|
|
4
4
|
"description": "The strictly typed HSX language: compile total financial programs into canonical UDL.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hsx",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
88
|
"@hyperscale0/adl": "1.0.0-beta.10",
|
|
89
|
-
"@hyperscale0/udl": "4.
|
|
89
|
+
"@hyperscale0/udl": "4.9.0",
|
|
90
90
|
"fast-sha256": "1.3.0"
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
|
@@ -98,5 +98,5 @@
|
|
|
98
98
|
"Amir Ayub",
|
|
99
99
|
"Sara AlBakaawi"
|
|
100
100
|
],
|
|
101
|
-
"gitHead": "
|
|
101
|
+
"gitHead": "e1e36df7b7182c7e22264414c622076a044e974a"
|
|
102
102
|
}
|
package/src/compile.ts
CHANGED
|
@@ -56,6 +56,7 @@ export interface CompileDiagnostic extends Diagnostic {
|
|
|
56
56
|
export interface CompileOriginMapEntry {
|
|
57
57
|
path: string;
|
|
58
58
|
span: Span & { line: number; column: number };
|
|
59
|
+
standardBlock?: { key: string; recordPath: string; headerDigest: string };
|
|
59
60
|
}
|
|
60
61
|
export interface AdapterBindingTarget {
|
|
61
62
|
adapter: ProviderAdapter;
|
|
@@ -428,6 +429,10 @@ export function compile(
|
|
|
428
429
|
);
|
|
429
430
|
const templates = new Map<string, InstrumentDecl>();
|
|
430
431
|
const declarationSources = new Map<InstrumentDecl, string>();
|
|
432
|
+
const standardOrigins = new Map<
|
|
433
|
+
InstrumentDecl,
|
|
434
|
+
NonNullable<CompileOriginMapEntry["standardBlock"]>
|
|
435
|
+
>();
|
|
431
436
|
const requirementOrigins = new Map<
|
|
432
437
|
UdlSubjectRequirement,
|
|
433
438
|
{ source: string; span: Span; message: string }
|
|
@@ -447,6 +452,13 @@ export function compile(
|
|
|
447
452
|
if (!content)
|
|
448
453
|
fail(use, `unknown header ${use.name}`, "choose a published header");
|
|
449
454
|
sources.set(use.name, content);
|
|
455
|
+
const bundled = bundledStandardLibrary.source(use.name);
|
|
456
|
+
const headerDigest =
|
|
457
|
+
bundled === content
|
|
458
|
+
? Array.from(sha256(new TextEncoder().encode(content)), (byte) =>
|
|
459
|
+
byte.toString(16).padStart(2, "0"),
|
|
460
|
+
).join("")
|
|
461
|
+
: undefined;
|
|
450
462
|
let header;
|
|
451
463
|
try {
|
|
452
464
|
header = parseHeader(content, use.name);
|
|
@@ -475,6 +487,14 @@ export function compile(
|
|
|
475
487
|
);
|
|
476
488
|
templates.set(prefix, parentDecl);
|
|
477
489
|
declarationSources.set(parentDecl, use.name);
|
|
490
|
+
if (headerDigest) {
|
|
491
|
+
const [, root, ...record] = prefix.split(".");
|
|
492
|
+
standardOrigins.set(parentDecl, {
|
|
493
|
+
key: `${use.name}.${root}`,
|
|
494
|
+
recordPath: record.join("."),
|
|
495
|
+
headerDigest,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
478
498
|
const recs = entries(asBlock(entries(parentDecl.body).get("records")));
|
|
479
499
|
for (const [recName, recBlock] of recs) {
|
|
480
500
|
const recDecl: InstrumentDecl = {
|
|
@@ -663,6 +683,7 @@ export function compile(
|
|
|
663
683
|
exportPath: string;
|
|
664
684
|
revision?: number | undefined;
|
|
665
685
|
},
|
|
686
|
+
standardOrigin?: NonNullable<CompileOriginMapEntry["standardBlock"]>,
|
|
666
687
|
) => {
|
|
667
688
|
const resolveFamilyInstruments = (
|
|
668
689
|
family: UdlFamily,
|
|
@@ -1467,6 +1488,7 @@ export function compile(
|
|
|
1467
1488
|
"parameterDiagnostics",
|
|
1468
1489
|
"reports",
|
|
1469
1490
|
"revisioned",
|
|
1491
|
+
"scope",
|
|
1470
1492
|
].includes(key) &&
|
|
1471
1493
|
!key.startsWith("action ")
|
|
1472
1494
|
)
|
|
@@ -2257,7 +2279,7 @@ export function compile(
|
|
|
2257
2279
|
const a: UdlAction = {
|
|
2258
2280
|
summary: slots.has("summary")
|
|
2259
2281
|
? String(data(slots.get("summary")!))
|
|
2260
|
-
:
|
|
2282
|
+
: title(name),
|
|
2261
2283
|
publicAction: camel(`${name}_${id}`),
|
|
2262
2284
|
event: `${id}.${name}`,
|
|
2263
2285
|
actor: "caller",
|
|
@@ -2818,11 +2840,43 @@ export function compile(
|
|
|
2818
2840
|
inst.actions[name] = a;
|
|
2819
2841
|
inst.actionOrder.push(name);
|
|
2820
2842
|
}
|
|
2821
|
-
for (const key of [
|
|
2843
|
+
for (const key of [
|
|
2844
|
+
"invariants",
|
|
2845
|
+
"reports",
|
|
2846
|
+
"revisioned",
|
|
2847
|
+
"scope",
|
|
2848
|
+
] as const)
|
|
2822
2849
|
if (body.has(key))
|
|
2823
2850
|
(inst as unknown as Record<string, unknown>)[key] = data(
|
|
2824
2851
|
body.get(key)!,
|
|
2825
2852
|
);
|
|
2853
|
+
let usesTax = false;
|
|
2854
|
+
for (const action of Object.values(inst.actions)) {
|
|
2855
|
+
for (const move of action.moves) {
|
|
2856
|
+
if (!("from" in move)) continue;
|
|
2857
|
+
for (const endpoint of ["from", "to"] as const) {
|
|
2858
|
+
if (move[endpoint] === "party.programTax") {
|
|
2859
|
+
move[endpoint] = "self.programTaxPayable";
|
|
2860
|
+
usesTax = true;
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
if (usesTax) {
|
|
2866
|
+
if (inst.fields.some((field) => field.name === "programTaxPayable"))
|
|
2867
|
+
fail(
|
|
2868
|
+
decl,
|
|
2869
|
+
"programTaxPayable is reserved for the tax account",
|
|
2870
|
+
"rename the authored field",
|
|
2871
|
+
);
|
|
2872
|
+
inst.fields.push({
|
|
2873
|
+
name: "programTaxPayable",
|
|
2874
|
+
type: "account",
|
|
2875
|
+
owner: "programOperator",
|
|
2876
|
+
book: "cash",
|
|
2877
|
+
key: "programTax",
|
|
2878
|
+
});
|
|
2879
|
+
}
|
|
2826
2880
|
const shape = udlInstrumentSchema.safeParse(inst);
|
|
2827
2881
|
if (!shape.success) {
|
|
2828
2882
|
const issue = shape.error.issues[0]!;
|
|
@@ -2847,6 +2901,7 @@ export function compile(
|
|
|
2847
2901
|
origins.push({
|
|
2848
2902
|
path: `$.instruments[${document.instruments.length}]`,
|
|
2849
2903
|
span: { ...origin, ...lineColAt(source, origin.start) },
|
|
2904
|
+
...(standardOrigin ? { standardBlock: standardOrigin } : {}),
|
|
2850
2905
|
});
|
|
2851
2906
|
if (familyDeclaration && familyDeclaration.revision !== undefined) {
|
|
2852
2907
|
inst.family = {
|
|
@@ -2904,6 +2959,14 @@ export function compile(
|
|
|
2904
2959
|
enums,
|
|
2905
2960
|
childAttachment,
|
|
2906
2961
|
childFamily,
|
|
2962
|
+
standardOrigin
|
|
2963
|
+
? {
|
|
2964
|
+
...standardOrigin,
|
|
2965
|
+
recordPath: [standardOrigin.recordPath, key]
|
|
2966
|
+
.filter(Boolean)
|
|
2967
|
+
.join("."),
|
|
2968
|
+
}
|
|
2969
|
+
: undefined,
|
|
2907
2970
|
);
|
|
2908
2971
|
}
|
|
2909
2972
|
};
|
|
@@ -2913,12 +2976,13 @@ export function compile(
|
|
|
2913
2976
|
if (
|
|
2914
2977
|
key !== "fields" &&
|
|
2915
2978
|
key !== "columns" &&
|
|
2979
|
+
key !== "entryActions" &&
|
|
2916
2980
|
!key.startsWith("attach ")
|
|
2917
2981
|
) {
|
|
2918
2982
|
fail(
|
|
2919
2983
|
decl,
|
|
2920
2984
|
`unknown object clause ${key}`,
|
|
2921
|
-
"use fields, columns, or attach",
|
|
2985
|
+
"use fields, columns, entryActions, or attach",
|
|
2922
2986
|
);
|
|
2923
2987
|
}
|
|
2924
2988
|
}
|
|
@@ -3035,6 +3099,7 @@ export function compile(
|
|
|
3035
3099
|
attachments,
|
|
3036
3100
|
},
|
|
3037
3101
|
templateFamily ? { ...templateFamily } : undefined,
|
|
3102
|
+
standardOrigins.get(template),
|
|
3038
3103
|
);
|
|
3039
3104
|
} catch (error) {
|
|
3040
3105
|
if (!(error instanceof CompileFailure)) throw error;
|
|
@@ -3149,9 +3214,21 @@ export function compile(
|
|
|
3149
3214
|
}
|
|
3150
3215
|
}
|
|
3151
3216
|
|
|
3217
|
+
const entryExpr = body.get("entryActions");
|
|
3218
|
+
let entryActions: string[] | undefined;
|
|
3219
|
+
if (entryExpr) {
|
|
3220
|
+
if (entryExpr.kind !== "list" || !entryExpr.items.length)
|
|
3221
|
+
fail(
|
|
3222
|
+
entryExpr,
|
|
3223
|
+
"entryActions needs a nonempty list",
|
|
3224
|
+
"name exposed create actions bound to actor",
|
|
3225
|
+
);
|
|
3226
|
+
entryActions = entryExpr.items.map(text);
|
|
3227
|
+
}
|
|
3152
3228
|
document.objects.push({
|
|
3153
3229
|
id: decl.name,
|
|
3154
3230
|
title: decl.title,
|
|
3231
|
+
...(entryActions ? { entryActions } : {}),
|
|
3155
3232
|
authoredFields: authoredNames,
|
|
3156
3233
|
attachments,
|
|
3157
3234
|
fields: authoredFields,
|
|
@@ -3180,6 +3257,7 @@ export function compile(
|
|
|
3180
3257
|
new Map(),
|
|
3181
3258
|
undefined,
|
|
3182
3259
|
templateFamily ? { ...templateFamily } : undefined,
|
|
3260
|
+
standardOrigins.get(template),
|
|
3183
3261
|
);
|
|
3184
3262
|
}
|
|
3185
3263
|
if (decl.kind === "object") {
|
|
@@ -3193,6 +3271,31 @@ export function compile(
|
|
|
3193
3271
|
.sort((a, b) => a.span.start - b.span.start)
|
|
3194
3272
|
.map((d) => diagnostic(d, "check")),
|
|
3195
3273
|
};
|
|
3274
|
+
// Resolve selectors after every attachment and child has its family.
|
|
3275
|
+
const retainSelectionFamilies = (value: unknown): void => {
|
|
3276
|
+
if (!value || typeof value !== "object") return;
|
|
3277
|
+
const record = value as Record<string, unknown>;
|
|
3278
|
+
const selection = record.selection as
|
|
3279
|
+
| { instrument: string | string[]; family?: UdlFamily }
|
|
3280
|
+
| undefined;
|
|
3281
|
+
if (selection && !selection.family) {
|
|
3282
|
+
const families = [selection.instrument]
|
|
3283
|
+
.flat()
|
|
3284
|
+
.map(
|
|
3285
|
+
(id) => document.instruments.find((item) => item.id === id)?.family,
|
|
3286
|
+
);
|
|
3287
|
+
const family = families[0];
|
|
3288
|
+
if (
|
|
3289
|
+
family &&
|
|
3290
|
+
families.every(
|
|
3291
|
+
(item) => canonicalJson(item) === canonicalJson(family),
|
|
3292
|
+
)
|
|
3293
|
+
)
|
|
3294
|
+
selection.family = family;
|
|
3295
|
+
}
|
|
3296
|
+
for (const child of Object.values(record)) retainSelectionFamilies(child);
|
|
3297
|
+
};
|
|
3298
|
+
retainSelectionFamilies(document);
|
|
3196
3299
|
// Propagate invoked requirements with the conditions on each invocation path.
|
|
3197
3300
|
let changedInvocations = true;
|
|
3198
3301
|
let invocationIterations = 0;
|
|
@@ -3545,6 +3648,27 @@ export function compile(
|
|
|
3545
3648
|
"give each exposed action a distinct public name",
|
|
3546
3649
|
);
|
|
3547
3650
|
}
|
|
3651
|
+
for (const kind of document.objects) {
|
|
3652
|
+
for (const name of kind.entryActions ?? []) {
|
|
3653
|
+
const eligible = kind.attachments.some((attachment) => {
|
|
3654
|
+
const create = document.instruments.find(
|
|
3655
|
+
(instrument) => instrument.id === attachment.instrument,
|
|
3656
|
+
)?.actions.create;
|
|
3657
|
+
return (
|
|
3658
|
+
create?.publicAction === name &&
|
|
3659
|
+
typeof create.actor === "object" &&
|
|
3660
|
+
"party" in create.actor &&
|
|
3661
|
+
create.actor.party === "actor"
|
|
3662
|
+
);
|
|
3663
|
+
});
|
|
3664
|
+
if (!eligible)
|
|
3665
|
+
fail(
|
|
3666
|
+
objects.get(kind.id)!,
|
|
3667
|
+
`entry action ${name} must create an attachment as actor`,
|
|
3668
|
+
"expose its create action and bind its acting party to actor",
|
|
3669
|
+
);
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3548
3672
|
const usedParties = new Set<string>();
|
|
3549
3673
|
const collectParties = (value: unknown): void => {
|
|
3550
3674
|
if (typeof value === "string") {
|
package/src/examples-bundle.ts
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import type { HsxExample } from "./examples.ts";
|
|
3
3
|
export const examples: HsxExample[] = [
|
|
4
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 expose create as create_holder\n holder: owner\n expose activate as activate_employee\n expose suspend as suspend_employee\n }\n attach card = cards.card {\n expose create as create_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 expose create as create_payment\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 expose create as create_receipt\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 { expose create as create_allowance, borrower: owner, per_borrower: 100000 SAR }\n attach budget = financing.portfolio_limit { expose create as create_budget, limit: 2000000 SAR }\n attach loan = financing.installments {\n expose create as create_loan\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 expose create as create_recovery\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 expose create as create_contact\n case: recovery, agency: agency\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 { expose create as create_allowance, expose approve as approve_borrower, borrower: owner, per_borrower: 100000 SAR }\n attach budget = financing.portfolio_limit { expose create as create_budget, expose approve as approve_budget, limit: 2000000 SAR }\n attach loan = financing.installments {\n expose create as create_loan\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 expose payoff as repay_early\n }\n attach recovery = collections.case {\n expose create as create_recovery\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 expose create as create_contact\n case: recovery, agency: agency\n }\n}\n\nexpose facility.loan.payment.create as create_payment\nexpose facility.loan.payment.pay as pay_instalment\nexpose facility.loan.payment.refund as refund_payment\nexpose facility.loan.payoff_quote.create as request_payoff_quote\n"},
|
|
6
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 expose create as create_sale\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 expose cancel as cancel_sale\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 expose create as create_allowance\n borrower: owner, per_borrower: 12000 SAR\n expose approve as approve_student\n }\n attach budget = financing.portfolio_limit {\n expose create as create_budget\n limit: 500000 SAR\n expose approve as approve_budget\n }\n attach tuition = financing.installments {\n expose create as create_tuition\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"},
|
|
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 expose create as create_allowance\n borrower: owner, per_borrower: 12000 SAR\n expose approve as approve_student\n }\n attach budget = financing.portfolio_limit {\n expose create as create_budget\n limit: 500000 SAR\n expose approve as approve_budget\n }\n attach tuition = financing.installments {\n expose create as create_tuition\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\nexpose enrolment.tuition.payment.create as create_payment\nexpose enrolment.tuition.payment.pay as pay_instalment\nexpose enrolment.tuition.payment.refund as refund_payment\nexpose enrolment.tuition.payoff_quote.create as request_payoff_quote\n"},
|
|
8
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 expose create as create_purchase\n payer: owner, payee: operator\n expose pay as pay_for_device\n }\n // Bind device_insurer to an ADL adapter before creating cover. Premiums net of commission go to its account.\n attach protection = insurance.cover {\n expose create as create_protection\n holder: owner, adapter: \"device_insurer\", commission: 10%, covers: purchase\n expose activate as activate_cover\n expose cancel as cancel_cover\n }\n attach claim = insurance.claim {\n expose create as create_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":"A lending composition with limit approvals, wallet commitments and cash distribution transitions.
|
|
9
|
+
{"id":"lending","title":"Community business lending","summary":"A lending composition with limit approvals, wallet commitments and cash distribution transitions.","headers":["lending","financing","wallet"],"source":"// A lending composition with limit approvals, wallet commitments and cash distribution transitions.\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 {\n expose create as create_allowance\n borrower: owner, per_borrower: 100000 SAR\n expose approve as approve_borrower\n }\n attach budget = financing.portfolio_limit {\n expose create as create_budget\n limit: 2000000 SAR\n expose approve as approve_budget\n }\n attach loan = financing.installments {\n expose create as create_loan\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 expose payoff as repay_early\n }\n attach investor_balance = wallet.balance {\n expose create as create_investor_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 create as open_funding\n expose close as close_funding\n }\n attach investment = lending.commitment {\n round: funding, wallet: investor_balance, investor: actor\n expose create as invest\n expose withdraw as withdraw_investment\n }\n attach returns = lending.distribution {\n expose create as create_returns\n round: funding, receipt: loan.settlement\n expose prepare_cash as prepare_returns\n expose distribute_cash as distribute_returns\n }\n}\n\nexpose business.loan.payment.create as create_payment\nexpose business.loan.payment.pay as pay_instalment\nexpose business.loan.payment.refund as refund_payment\nexpose business.loan.payoff_quote.create as request_payoff_quote\nexpose business.returns.share.create as create_return_share\n"},
|
|
10
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 { expose create as create_sale, payer: actor, payee: owner, expose fund as sell }\n attach limits = financing.limits { expose create as create_limits, borrower: actor, per_borrower: 60000 SAR }\n attach ceiling = financing.portfolio_limit { expose create as create_ceiling, 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
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 expose create as create_offer\n seller: owner\n expose publish as publish_offer\n expose withdraw as withdraw_offer\n }\n attach order = marketplace.order {\n expose create as create_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 expose create as create_payment\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 expose verify_return as verify_return\n expose refund as refund_client\n expose cancel as cancel_payment\n }\n}\n"},
|
|
12
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 expose create as create_payment\n payer: actor, payee: operator, amount: 150 SAR\n expose pay as pay_for_lesson\n expose cancel as cancel_payment\n }\n}\n"},
|