@hyperscale0/hsx 1.0.334 → 1.0.335
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/attachment-economics.d.ts +16 -2
- package/dist/src/attachment-economics.d.ts.map +1 -1
- package/dist/src/attachment-economics.js +58 -2
- package/dist/src/attachment-economics.js.map +1 -1
- package/dist/src/cli.js +2 -2
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts +5 -0
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +47 -5
- package/dist/src/compile.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/parse.js +13 -0
- package/dist/src/parse.js.map +1 -1
- package/dist/src/template-guide.d.ts.map +1 -1
- package/dist/src/template-guide.js +18 -4
- package/dist/src/template-guide.js.map +1 -1
- package/docs/README.md +11 -1
- package/package.json +4 -4
- package/src/attachment-economics.ts +77 -2
- package/src/cli.ts +2 -2
- package/src/compile.ts +73 -8
- package/src/index.ts +1 -0
- package/src/parse.ts +15 -0
- package/src/template-guide.ts +21 -4
package/src/compile.ts
CHANGED
|
@@ -40,7 +40,10 @@ import {
|
|
|
40
40
|
} from "./binding-contract.ts";
|
|
41
41
|
import { tunableBounds } from "./tunables.ts";
|
|
42
42
|
import { parseProgram } from "./parse.ts";
|
|
43
|
-
import {
|
|
43
|
+
import {
|
|
44
|
+
applyAttachmentEconomics,
|
|
45
|
+
purposeWarnings,
|
|
46
|
+
} from "./attachment-economics.ts";
|
|
44
47
|
import { parseHeader } from "./header-source.ts";
|
|
45
48
|
import {
|
|
46
49
|
lineColAt,
|
|
@@ -79,9 +82,14 @@ export interface CompileOptions {
|
|
|
79
82
|
standardLibrary?: StandardLibrary;
|
|
80
83
|
adapterRegistry?: Readonly<Record<string, AdapterBindingTarget>>;
|
|
81
84
|
}
|
|
85
|
+
export interface CompileWarning extends Omit<CompileDiagnostic, "severity"> {
|
|
86
|
+
severity: "warning";
|
|
87
|
+
}
|
|
82
88
|
export interface CompileResult {
|
|
83
89
|
verdict: "valid" | "invalid";
|
|
84
90
|
diagnostics: CompileDiagnostic[];
|
|
91
|
+
/** Advice that does not block a freeze, such as a money move with no purpose. */
|
|
92
|
+
warnings: CompileWarning[];
|
|
85
93
|
artifacts?: {
|
|
86
94
|
document: UdlDocument;
|
|
87
95
|
costManifest: UdlCostManifest;
|
|
@@ -392,8 +400,10 @@ function literal(expr: Expr, expectedType?: string): string | number | boolean {
|
|
|
392
400
|
if (!Number.isSafeInteger(n) || n <= 0)
|
|
393
401
|
fail(
|
|
394
402
|
expr,
|
|
395
|
-
"
|
|
396
|
-
|
|
403
|
+
expr.value.includes(" ")
|
|
404
|
+
? `${expr.value} is not a fixed duration`
|
|
405
|
+
: "invalid duration",
|
|
406
|
+
"write a positive fixed duration such as 48h, 3d or 30d",
|
|
397
407
|
);
|
|
398
408
|
return n;
|
|
399
409
|
}
|
|
@@ -463,6 +473,7 @@ export function compile(
|
|
|
463
473
|
return {
|
|
464
474
|
verdict: "invalid",
|
|
465
475
|
diagnostics: parsed.diagnostics.map((d) => diagnostic(d, "parse")),
|
|
476
|
+
warnings: [],
|
|
466
477
|
};
|
|
467
478
|
const program = parsed.program;
|
|
468
479
|
const bindingDiagnostics: Diagnostic[] = [];
|
|
@@ -489,6 +500,15 @@ export function compile(
|
|
|
489
500
|
"write currency SAR or omit currency",
|
|
490
501
|
);
|
|
491
502
|
const templates = new Map<string, InstrumentDecl>();
|
|
503
|
+
// Attached instruments, for the warning on a money move with no purpose.
|
|
504
|
+
const attachedSelectors = new Map<
|
|
505
|
+
string,
|
|
506
|
+
{ prefix: string; root: string }
|
|
507
|
+
>();
|
|
508
|
+
const attachEntries = new Map<
|
|
509
|
+
string,
|
|
510
|
+
{ entry: Entry; name: string; template: InstrumentDecl }
|
|
511
|
+
>();
|
|
492
512
|
const declarationSources = new Map<InstrumentDecl, string>();
|
|
493
513
|
const standardOrigins = new Map<
|
|
494
514
|
InstrumentDecl,
|
|
@@ -746,6 +766,11 @@ export function compile(
|
|
|
746
766
|
attachments: UdlObjectAttachment[];
|
|
747
767
|
economics: Entry[];
|
|
748
768
|
child?: boolean;
|
|
769
|
+
/** How an economics selector names this instrument: "" or "occurrence.". */
|
|
770
|
+
selectorPrefix?: string;
|
|
771
|
+
rootInstrument?: string;
|
|
772
|
+
/** The template as the founder wrote it, such as money.schedule. */
|
|
773
|
+
template?: string;
|
|
749
774
|
},
|
|
750
775
|
familyDeclaration?: {
|
|
751
776
|
module: string;
|
|
@@ -933,6 +958,7 @@ export function compile(
|
|
|
933
958
|
enums.set(parameter.key, type.args.map(text));
|
|
934
959
|
}
|
|
935
960
|
const supplied = new Map<string, Expr>();
|
|
961
|
+
const suppliedEntries = new Map<string, Entry>();
|
|
936
962
|
for (const entry of arguments_.entries) {
|
|
937
963
|
if (supplied.has(entry.key))
|
|
938
964
|
fail(
|
|
@@ -941,6 +967,7 @@ export function compile(
|
|
|
941
967
|
"supply each parameter once",
|
|
942
968
|
);
|
|
943
969
|
supplied.set(entry.key, entry.value);
|
|
970
|
+
suppliedEntries.set(entry.key, entry);
|
|
944
971
|
}
|
|
945
972
|
const environment = new Map<string, Expr>(inherited);
|
|
946
973
|
const parameterErrors: Diagnostic[] = [];
|
|
@@ -1082,10 +1109,11 @@ export function compile(
|
|
|
1082
1109
|
`Remove \`${key}\`. To use your object's money field, add \`rename { ${field}: ${target} }\`.`,
|
|
1083
1110
|
);
|
|
1084
1111
|
}
|
|
1112
|
+
const tunables = decl.parameters.map((p) => p.key);
|
|
1085
1113
|
fail(
|
|
1086
|
-
suppliedValue,
|
|
1087
|
-
|
|
1088
|
-
`
|
|
1114
|
+
suppliedEntries.get(key) ?? suppliedValue,
|
|
1115
|
+
`${assignments.get(id)?.target ?? attachmentInfo?.template ?? decl.name} has no tunable "${key}".${didYouMean(key, tunables)} Tunables: ${tunables.join(", ")}`,
|
|
1116
|
+
`remove ${key}, or set one of ${tunables.join(", ")}`,
|
|
1089
1117
|
);
|
|
1090
1118
|
}
|
|
1091
1119
|
const isParty = (name: string) =>
|
|
@@ -3205,14 +3233,39 @@ export function compile(
|
|
|
3205
3233
|
);
|
|
3206
3234
|
if (receipt) move.transfer = `self.${receipt}`;
|
|
3207
3235
|
}
|
|
3208
|
-
|
|
3236
|
+
// `economics occurrence.pay` classifies a record's move; an action of
|
|
3237
|
+
// the attached instrument wins over a record of the same name.
|
|
3238
|
+
const recordEconomics = new Map<string, Entry[]>();
|
|
3239
|
+
if (attachmentInfo) {
|
|
3240
|
+
const own: Entry[] = [];
|
|
3241
|
+
for (const declaration of attachmentInfo.economics) {
|
|
3242
|
+
const [first = "", ...rest] = declaration.key
|
|
3243
|
+
.slice("economics ".length)
|
|
3244
|
+
.split(".");
|
|
3245
|
+
if (
|
|
3246
|
+
rest.length &&
|
|
3247
|
+
!Object.hasOwn(inst.actions, first) &&
|
|
3248
|
+
records.has(first)
|
|
3249
|
+
)
|
|
3250
|
+
recordEconomics.set(first, [
|
|
3251
|
+
...(recordEconomics.get(first) ?? []),
|
|
3252
|
+
{ ...declaration, key: `economics ${rest.join(".")}` },
|
|
3253
|
+
]);
|
|
3254
|
+
else own.push(declaration);
|
|
3255
|
+
}
|
|
3209
3256
|
applyAttachmentEconomics(
|
|
3210
3257
|
document,
|
|
3211
3258
|
inst,
|
|
3212
3259
|
attachmentInfo.parties,
|
|
3213
|
-
|
|
3260
|
+
own,
|
|
3214
3261
|
data,
|
|
3262
|
+
[...records.keys()],
|
|
3215
3263
|
);
|
|
3264
|
+
attachedSelectors.set(inst.id, {
|
|
3265
|
+
prefix: attachmentInfo.selectorPrefix ?? "",
|
|
3266
|
+
root: attachmentInfo.rootInstrument ?? inst.id,
|
|
3267
|
+
});
|
|
3268
|
+
}
|
|
3216
3269
|
for (const key of [
|
|
3217
3270
|
"invariants",
|
|
3218
3271
|
"reports",
|
|
@@ -3292,6 +3345,9 @@ export function compile(
|
|
|
3292
3345
|
attachmentName: `${attachmentInfo.attachmentName}_${key}`,
|
|
3293
3346
|
child: true,
|
|
3294
3347
|
exposed: new Map<string, string>(),
|
|
3348
|
+
economics: recordEconomics.get(key) ?? [],
|
|
3349
|
+
selectorPrefix: `${attachmentInfo.selectorPrefix ?? ""}${key}.`,
|
|
3350
|
+
rootInstrument: attachmentInfo.rootInstrument ?? id,
|
|
3295
3351
|
}
|
|
3296
3352
|
: undefined;
|
|
3297
3353
|
if (childAttachment)
|
|
@@ -3450,6 +3506,7 @@ export function compile(
|
|
|
3450
3506
|
|
|
3451
3507
|
const parties: Record<string, AttachmentPartyBinding> = {};
|
|
3452
3508
|
attachments.push({ name: attachmentName, instrument: instId, parties });
|
|
3509
|
+
attachEntries.set(instId, { entry, name: attachmentName, template });
|
|
3453
3510
|
|
|
3454
3511
|
const tunableBlock: BlockExpr = {
|
|
3455
3512
|
kind: "block",
|
|
@@ -3475,6 +3532,7 @@ export function compile(
|
|
|
3475
3532
|
parties,
|
|
3476
3533
|
attachments,
|
|
3477
3534
|
economics,
|
|
3535
|
+
template: targetTemplate,
|
|
3478
3536
|
},
|
|
3479
3537
|
templateFamily ? { ...templateFamily } : undefined,
|
|
3480
3538
|
standardOrigins.get(template),
|
|
@@ -3689,6 +3747,7 @@ export function compile(
|
|
|
3689
3747
|
if (bindingDiagnostics.length)
|
|
3690
3748
|
return {
|
|
3691
3749
|
verdict: "invalid",
|
|
3750
|
+
warnings: [],
|
|
3692
3751
|
diagnostics: distinct(bindingDiagnostics)
|
|
3693
3752
|
.sort((a, b) => a.span.start - b.span.start)
|
|
3694
3753
|
.map((d) => diagnostic(d, "check")),
|
|
@@ -4500,6 +4559,7 @@ export function compile(
|
|
|
4500
4559
|
if (collisions.length)
|
|
4501
4560
|
return {
|
|
4502
4561
|
verdict: "invalid",
|
|
4562
|
+
warnings: [],
|
|
4503
4563
|
diagnostics: collisions
|
|
4504
4564
|
.sort((a, b) => a.span.start - b.span.start)
|
|
4505
4565
|
.map((d) => diagnostic(d, "check")),
|
|
@@ -4577,6 +4637,7 @@ export function compile(
|
|
|
4577
4637
|
if (!validated.ok)
|
|
4578
4638
|
return {
|
|
4579
4639
|
verdict: "invalid",
|
|
4640
|
+
warnings: [],
|
|
4580
4641
|
diagnostics: issues.map((i) => {
|
|
4581
4642
|
const origin = [...origins]
|
|
4582
4643
|
.reverse()
|
|
@@ -4652,6 +4713,9 @@ export function compile(
|
|
|
4652
4713
|
return {
|
|
4653
4714
|
verdict: "valid",
|
|
4654
4715
|
diagnostics: [],
|
|
4716
|
+
warnings: purposeWarnings(document, attachedSelectors, attachEntries).map(
|
|
4717
|
+
(d) => ({ ...diagnostic(d, "check"), severity: "warning" }),
|
|
4718
|
+
),
|
|
4655
4719
|
artifacts: {
|
|
4656
4720
|
document: validated.value,
|
|
4657
4721
|
costManifest: buildUdlCostManifest(validated.value),
|
|
@@ -4662,6 +4726,7 @@ export function compile(
|
|
|
4662
4726
|
if (error instanceof CompileFailure)
|
|
4663
4727
|
return {
|
|
4664
4728
|
verdict: "invalid",
|
|
4729
|
+
warnings: [],
|
|
4665
4730
|
diagnostics: distinct([...bindingDiagnostics, error.diagnostic]).map(
|
|
4666
4731
|
(d) => diagnostic(d, "check"),
|
|
4667
4732
|
),
|
package/src/index.ts
CHANGED
package/src/parse.ts
CHANGED
|
@@ -514,6 +514,21 @@ class Parser {
|
|
|
514
514
|
};
|
|
515
515
|
if (/[a-z]$/.test(token.text))
|
|
516
516
|
return { kind: "duration", value: token.text, span: token.span };
|
|
517
|
+
// `1 month` reads as one value, so the checker names the tunable it
|
|
518
|
+
// was given to instead of failing on the next line. A unit followed by
|
|
519
|
+
// `:` is the next entry's key, as in `count: 2` then `months: 2`.
|
|
520
|
+
if (
|
|
521
|
+
/^(?:minute|hour|day|week|month|year)s?$/.test(this.peek().text) &&
|
|
522
|
+
this.peek().kind === "name" &&
|
|
523
|
+
this.tokens[this.index + 1]?.text !== ":"
|
|
524
|
+
) {
|
|
525
|
+
const unit = this.take().text;
|
|
526
|
+
return {
|
|
527
|
+
kind: "duration",
|
|
528
|
+
value: `${token.text} ${unit}`,
|
|
529
|
+
span: this.span(start),
|
|
530
|
+
};
|
|
531
|
+
}
|
|
517
532
|
if (/^[A-Z]{3}$/.test(this.peek().text)) {
|
|
518
533
|
const currency = this.take().text;
|
|
519
534
|
return {
|
package/src/template-guide.ts
CHANGED
|
@@ -49,6 +49,14 @@ export interface TemplateGuide {
|
|
|
49
49
|
const RUN_BY_NOTE =
|
|
50
50
|
"runBy names who runs each action: a party is whoever the attach binds to it (owner, actor, operator or a declared business), clock is the platform when the action falls due, parent runs only inside its parent record's action, and caller is anyone the exposed action admits.";
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* A stock money move declares no purpose, because the purpose follows how
|
|
54
|
+
* the attach binds its parties; the guide shows the line for this example.
|
|
55
|
+
*/
|
|
56
|
+
function economicsNote(lines: string[]) {
|
|
57
|
+
return `economics declares what a payment is for the books. Add ${lines.map((line) => `\`${line}\``).join(" and ")} inside the attach. Use purpose earning when operator receives the money as revenue, participant_payout when operator pays a participant, and pass_through when money passes between participants. Without it hsx check warns and the books report leaves the payment out.`;
|
|
58
|
+
}
|
|
59
|
+
|
|
52
60
|
const SUBJECT_NOTE =
|
|
53
61
|
"subject names fields the attached object must carry, or rename to with rename { field: yourField }, before the actions that read them can run.";
|
|
54
62
|
|
|
@@ -110,8 +118,13 @@ export function templateGuide(
|
|
|
110
118
|
)
|
|
111
119
|
.map((action) => `expose ${action.name}`);
|
|
112
120
|
const program = example.render(exposes);
|
|
113
|
-
const
|
|
114
|
-
|
|
121
|
+
const compiled = compiledAttachment(program, example.attachment, options);
|
|
122
|
+
const create = compiled.actions.create;
|
|
123
|
+
const economics = compiled.warnings.flatMap((warning) =>
|
|
124
|
+
warning.message.startsWith(`${example.attachment}: `)
|
|
125
|
+
? [/economics [^}]+\}/.exec(warning.fix)![0]]
|
|
126
|
+
: [],
|
|
127
|
+
);
|
|
115
128
|
return {
|
|
116
129
|
qualifiedName,
|
|
117
130
|
summary: manifest.summary,
|
|
@@ -135,7 +148,11 @@ export function templateGuide(
|
|
|
135
148
|
subject: manifest.subject,
|
|
136
149
|
actions,
|
|
137
150
|
example: program,
|
|
138
|
-
notes: [
|
|
151
|
+
notes: [
|
|
152
|
+
RUN_BY_NOTE,
|
|
153
|
+
...(manifest.subject.length ? [SUBJECT_NOTE] : []),
|
|
154
|
+
...(economics.length ? [economicsNote(economics)] : []),
|
|
155
|
+
],
|
|
139
156
|
};
|
|
140
157
|
}
|
|
141
158
|
|
|
@@ -553,7 +570,7 @@ function compiledAttachment(
|
|
|
553
570
|
(candidate) => candidate.id === instrumentId,
|
|
554
571
|
);
|
|
555
572
|
if (!instrument) throw new Error(`${attachment} example has no instrument`);
|
|
556
|
-
return instrument;
|
|
573
|
+
return Object.assign(instrument, { warnings: result.warnings });
|
|
557
574
|
}
|
|
558
575
|
|
|
559
576
|
function inputType(field: Record<string, unknown>) {
|