@hyperscale0/hsx 1.0.337 → 1.0.339
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/dist/src/ast.d.ts +1 -0
- package/dist/src/ast.d.ts.map +1 -1
- package/dist/src/ast.js.map +1 -1
- package/dist/src/attachment-economics.d.ts +37 -0
- package/dist/src/attachment-economics.d.ts.map +1 -1
- package/dist/src/attachment-economics.js +183 -3
- package/dist/src/attachment-economics.js.map +1 -1
- package/dist/src/binding-contract.d.ts.map +1 -1
- package/dist/src/binding-contract.js +10 -4
- package/dist/src/binding-contract.js.map +1 -1
- package/dist/src/catalog-program.d.ts +1 -1
- package/dist/src/catalog-program.d.ts.map +1 -1
- package/dist/src/catalog-program.js +1 -1
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +133 -41
- package/dist/src/compile.js.map +1 -1
- package/dist/src/examples-bundle.js +1 -1
- package/dist/src/examples-bundle.js.map +1 -1
- package/dist/src/headers.d.ts.map +1 -1
- package/dist/src/headers.js +3 -1
- package/dist/src/headers.js.map +1 -1
- package/dist/src/language.d.ts.map +1 -1
- package/dist/src/language.js +1 -0
- package/dist/src/language.js.map +1 -1
- package/dist/src/parse.js +13 -1
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.js +6 -6
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/template-guide.d.ts.map +1 -1
- package/dist/src/template-guide.js +49 -18
- package/dist/src/template-guide.js.map +1 -1
- package/docs/README.md +5 -2
- package/examples/camera-rental.hsx +6 -1
- package/package.json +4 -4
- package/src/ast.ts +7 -1
- package/src/attachment-economics.ts +230 -2
- package/src/binding-contract.ts +13 -6
- package/src/catalog-program.ts +1 -1
- package/src/compile.ts +185 -58
- package/src/examples-bundle.ts +1 -1
- package/src/headers.ts +6 -1
- package/src/language.ts +1 -0
- package/src/parse.ts +16 -1
- package/src/std-bundle.ts +6 -6
- package/src/template-guide.ts +74 -22
- package/std/collections.hsx +4 -4
- package/std/financing.hsx +461 -266
- package/std/insurance.hsx +21 -5
- package/std/lending.hsx +13 -9
- package/std/money.hsx +148 -38
- package/std/reporting.hsx +6 -6
package/src/template-guide.ts
CHANGED
|
@@ -121,6 +121,7 @@ export function templateGuide(
|
|
|
121
121
|
const compiled = compiledAttachment(program, example.attachment, options);
|
|
122
122
|
const create = compiled.actions.create;
|
|
123
123
|
const economics = compiled.warnings.flatMap((warning) =>
|
|
124
|
+
warning.code === "economic_purpose_missing" &&
|
|
124
125
|
warning.message.startsWith(`${example.attachment}: `)
|
|
125
126
|
? [/economics [^}]+\}/.exec(warning.fix)![0]]
|
|
126
127
|
: [],
|
|
@@ -163,23 +164,54 @@ function slot(block: BlockExpr, key: string) {
|
|
|
163
164
|
return block.entries.find((entry) => entry.key === key)?.value;
|
|
164
165
|
}
|
|
165
166
|
|
|
167
|
+
/** Each `key` row of a block with the `when` branches it sits under. */
|
|
168
|
+
function branched(
|
|
169
|
+
block: BlockExpr,
|
|
170
|
+
key: string,
|
|
171
|
+
outer: string[] = [],
|
|
172
|
+
): { value: Expr; condition: string }[] {
|
|
173
|
+
return block.entries.flatMap((entry) =>
|
|
174
|
+
entry.key.startsWith("when ") && entry.value.kind === "block"
|
|
175
|
+
? branched(entry.value, key, [...outer, entry.key])
|
|
176
|
+
: entry.key === key
|
|
177
|
+
? [
|
|
178
|
+
{
|
|
179
|
+
value: entry.value,
|
|
180
|
+
condition: outer.length
|
|
181
|
+
? `when ${outer.map((when) => when.slice(5)).join(" and ")}`
|
|
182
|
+
: "",
|
|
183
|
+
},
|
|
184
|
+
]
|
|
185
|
+
: [],
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** A block's rows with every `when` branch's rows in place, so both forms read. */
|
|
190
|
+
function flat(block: BlockExpr): Entry[] {
|
|
191
|
+
return block.entries.flatMap((entry) =>
|
|
192
|
+
entry.key.startsWith("when ") && entry.value.kind === "block"
|
|
193
|
+
? flat(entry.value)
|
|
194
|
+
: [entry],
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
166
198
|
/**
|
|
167
199
|
* The parser desugars `requires` and `moves` into blocks without source
|
|
168
|
-
* spans, so each clause is spelled from the slot's own source text.
|
|
200
|
+
* spans, so each clause is spelled from the slot's own source text. A clause
|
|
201
|
+
* under a `when` branch names its condition.
|
|
169
202
|
*/
|
|
170
203
|
function clauses(
|
|
171
|
-
|
|
204
|
+
rows: { value: Expr; condition: string }[],
|
|
172
205
|
key: "requires" | "moves",
|
|
173
206
|
spell: (expression: Expr) => string,
|
|
174
207
|
) {
|
|
175
|
-
return
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
);
|
|
208
|
+
return rows.flatMap(({ value, condition }) =>
|
|
209
|
+
spell(value)
|
|
210
|
+
.split(new RegExp(`(?:^|[;,]?\\s+)${key}\\s+`))
|
|
211
|
+
.map((clause) => clause.trim().replace(/[;,]$/, ""))
|
|
212
|
+
.filter(Boolean)
|
|
213
|
+
.map((clause) => (condition ? `${clause}, ${condition}` : clause)),
|
|
214
|
+
);
|
|
183
215
|
}
|
|
184
216
|
|
|
185
217
|
function items(expression: Expr | undefined): Expr[] {
|
|
@@ -198,7 +230,7 @@ function guideActions(
|
|
|
198
230
|
for (const entry of body.entries) {
|
|
199
231
|
if (!entry.key.startsWith("action ") || entry.value.kind !== "block")
|
|
200
232
|
continue;
|
|
201
|
-
const action = entry.value;
|
|
233
|
+
const action = { ...entry.value, entries: flat(entry.value) };
|
|
202
234
|
const actor = slot(action, "actor");
|
|
203
235
|
const party = actor?.kind === "block" ? slot(actor, "party") : undefined;
|
|
204
236
|
const parent = actor?.kind === "block" ? slot(actor, "parent") : undefined;
|
|
@@ -219,10 +251,27 @@ function guideActions(
|
|
|
219
251
|
};
|
|
220
252
|
const due = at("due");
|
|
221
253
|
const deadline = at("deadline");
|
|
222
|
-
const requires = clauses(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
254
|
+
const requires = clauses(
|
|
255
|
+
branched(entry.value, "requires"),
|
|
256
|
+
"requires",
|
|
257
|
+
spell,
|
|
258
|
+
);
|
|
259
|
+
const moves = clauses(
|
|
260
|
+
action.entries
|
|
261
|
+
.filter((row) => row.key === "moves")
|
|
262
|
+
.map((row) => ({ value: row.value, condition: "" })),
|
|
263
|
+
"moves",
|
|
264
|
+
spell,
|
|
265
|
+
);
|
|
266
|
+
const invokes = branched(entry.value, "invoke").flatMap(
|
|
267
|
+
({ value, condition }) =>
|
|
268
|
+
items(value).flatMap((item) =>
|
|
269
|
+
item.kind === "block"
|
|
270
|
+
? [
|
|
271
|
+
`${invocation(item, spell, prefix)}${condition ? `, ${condition}` : ""}`,
|
|
272
|
+
]
|
|
273
|
+
: [],
|
|
274
|
+
),
|
|
226
275
|
);
|
|
227
276
|
const from = slot(action, "from");
|
|
228
277
|
const to = slot(action, "to");
|
|
@@ -262,7 +311,9 @@ function invocation(
|
|
|
262
311
|
) {
|
|
263
312
|
const instrument = slot(block, "instrument");
|
|
264
313
|
const action = slot(block, "action");
|
|
265
|
-
|
|
314
|
+
// current() is the record itself, already named by the prefix.
|
|
315
|
+
const own = instrument && spell(instrument) === "current()";
|
|
316
|
+
const target = `${instrument && !own ? `${prefix}${spell(instrument)}.` : own ? prefix : ""}${action ? spell(action) : "?"}`;
|
|
266
317
|
const range = slot(block, "range");
|
|
267
318
|
const count = range?.kind === "block" ? slot(range, "count") : undefined;
|
|
268
319
|
const literal =
|
|
@@ -351,7 +402,7 @@ function valueMeaning(
|
|
|
351
402
|
const fields = slot(decl.body, "fields");
|
|
352
403
|
const set =
|
|
353
404
|
fields?.kind === "block"
|
|
354
|
-
? fields.
|
|
405
|
+
? flat(fields).filter((entry) => bare.test(fieldValue(entry, spell)))
|
|
355
406
|
: [];
|
|
356
407
|
const read = new Set(set.map((entry) => entry.key));
|
|
357
408
|
const uses: string[] = [];
|
|
@@ -367,14 +418,14 @@ function valueMeaning(
|
|
|
367
418
|
: new RegExp(`\\bself\\.${field}\\b`).test(text),
|
|
368
419
|
);
|
|
369
420
|
if (own?.kind === "block" && prefix)
|
|
370
|
-
for (const entry of own
|
|
421
|
+
for (const entry of flat(own)) {
|
|
371
422
|
const value = fieldValue(entry, spell);
|
|
372
423
|
if (reads(value)) uses.push(`${prefix}${entry.key}: ${value}`);
|
|
373
424
|
}
|
|
374
425
|
for (const entry of body.entries) {
|
|
375
426
|
if (!entry.key.startsWith("action ") || entry.value.kind !== "block")
|
|
376
427
|
continue;
|
|
377
|
-
for (const part of entry.value
|
|
428
|
+
for (const part of flat(entry.value)) {
|
|
378
429
|
if (["actor", "from", "to", "summary"].includes(part.key)) continue;
|
|
379
430
|
const text = spell(part.value);
|
|
380
431
|
// A long slot, such as an invoke list, is named rather than spelled.
|
|
@@ -391,7 +442,7 @@ function valueMeaning(
|
|
|
391
442
|
const recordFields = slot(record.value, "fields");
|
|
392
443
|
const refs =
|
|
393
444
|
recordFields?.kind === "block"
|
|
394
|
-
? recordFields
|
|
445
|
+
? flat(recordFields)
|
|
395
446
|
.filter((entry) =>
|
|
396
447
|
/^ref<parent>/.test(fieldValue(entry, spell)),
|
|
397
448
|
)
|
|
@@ -409,7 +460,8 @@ function valueMeaning(
|
|
|
409
460
|
: []),
|
|
410
461
|
...(set.length
|
|
411
462
|
? [
|
|
412
|
-
|
|
463
|
+
// Two branches can set one field the same way.
|
|
464
|
+
`Sets ${[...new Set(set.map((entry) => `${entry.key}: ${fieldValue(entry, spell)}`))].join(", ")}.`,
|
|
413
465
|
]
|
|
414
466
|
: []),
|
|
415
467
|
...(uses.length ? [`Read by ${uses.slice(0, 6).join("; ")}.`] : []),
|
|
@@ -589,7 +641,7 @@ function listBound(
|
|
|
589
641
|
const fields = slot(decl.body, "fields");
|
|
590
642
|
const entry =
|
|
591
643
|
fields?.kind === "block"
|
|
592
|
-
? fields.
|
|
644
|
+
? flat(fields).find((item) => item.key === name)
|
|
593
645
|
: undefined;
|
|
594
646
|
const bound = entry
|
|
595
647
|
? /^list\(\s*[\w<>]+\s*,\s*(\w+)\s*\)$/.exec(fieldValue(entry, spell))?.[1]
|
package/std/collections.hsx
CHANGED
|
@@ -26,7 +26,7 @@ instrument case(on: ref<financing.installments>[], agency: party, capital: party
|
|
|
26
26
|
action create {
|
|
27
27
|
due: { at: self.eligibleAt }
|
|
28
28
|
requires self.plan == self.slice.plan
|
|
29
|
-
requires self.slice.
|
|
29
|
+
requires self.slice.openPrincipal > "0"
|
|
30
30
|
requires unique "overdue_referral" on [self.slice]
|
|
31
31
|
}
|
|
32
32
|
action assign { from: opened, to: assigned, actor: { party: capital } }
|
|
@@ -74,17 +74,17 @@ instrument reminder(on: ref<financing.installments>[], operator: party = program
|
|
|
74
74
|
action before_due {
|
|
75
75
|
from: ready, to: ready, actor: clock
|
|
76
76
|
due: { at: self.slice.dueAt, localDay: { days: before_days, direction: "before", hour: send_from, timezone: timezone } }
|
|
77
|
-
invoke: [{ selection: { instrument: current(), reference: "slice", anchor: self.slice, states: [ready], limit: 1, where: { slice.plan.status: { literal: "active" } } }, action: send, guard: { kind: compare, left: { field: self.slice.
|
|
77
|
+
invoke: [{ selection: { instrument: current(), reference: "slice", anchor: self.slice, states: [ready], limit: 1, where: { slice.plan.status: { literal: "active" } } }, action: send, guard: { kind: compare, left: { field: self.slice.openPrincipal }, operator: ">", right: { literal: "0" } }, input: {} }]
|
|
78
78
|
}
|
|
79
79
|
action overdue {
|
|
80
80
|
from: ready, to: ready, actor: clock
|
|
81
81
|
due: { at: self.slice.dueAt, localDay: { days: overdue_days, direction: "after", hour: send_from, timezone: timezone } }
|
|
82
|
-
invoke: [{ selection: { instrument: current(), reference: "slice", anchor: self.slice, states: [ready], limit: 1, where: { slice.plan.status: { literal: "active" } } }, action: send, guard: { kind: compare, left: { field: self.slice.
|
|
82
|
+
invoke: [{ selection: { instrument: current(), reference: "slice", anchor: self.slice, states: [ready], limit: 1, where: { slice.plan.status: { literal: "active" } } }, action: send, guard: { kind: compare, left: { field: self.slice.openPrincipal }, operator: ">", right: { literal: "0" } }, input: {} }]
|
|
83
83
|
}
|
|
84
84
|
action send {
|
|
85
85
|
from: ready, to: ready, actor: { parent: current() }
|
|
86
86
|
requires self.slice.plan in [active]
|
|
87
|
-
requires self.slice.
|
|
87
|
+
requires self.slice.openPrincipal > "0"
|
|
88
88
|
reminder: { installment: self.slice, recipient: self.slice.plan.borrower, dueAt: self.slice.dueAt, channel: "email", template: "payment_reminder", beforeDays: before_days, overdueDays: overdue_days, maxPerDay: max_per_day, startHour: send_from, endHour: send_until, timezone: timezone }
|
|
89
89
|
}
|
|
90
90
|
}
|