@hyperscale0/hsx 2.1.1 → 2.2.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 +28 -7
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +39 -5
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts +0 -1
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +15 -1
- package/dist/src/compile.js.map +1 -1
- package/dist/src/cost.d.ts +3 -3
- package/dist/src/cost.d.ts.map +1 -1
- package/dist/src/cost.js +22 -12
- package/dist/src/cost.js.map +1 -1
- package/dist/src/parse.d.ts.map +1 -1
- package/dist/src/parse.js +10 -14
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.d.ts.map +1 -1
- package/dist/src/std-bundle.js +20 -84
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/typecheck.d.ts.map +1 -1
- package/dist/src/typecheck.js +171 -39
- package/dist/src/typecheck.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/docs/README.md +4 -2
- package/docs/guide/01-first-program.md +1 -1
- package/docs/guide/06-schedules.md +5 -1
- package/docs/guide/08-writing-a-module.md +48 -0
- package/docs/llms-full.txt +1928 -421
- package/docs/llms.txt +2 -3
- package/docs/reference/cli.md +6 -5
- package/docs/reference/diagnostics.md +9 -9
- package/docs/reference/grammar.md +2 -3
- package/docs/reference/std/advance.md +94 -17
- package/docs/reference/std/cancellable_booking.md +138 -17
- package/docs/reference/std/captured_payment.md +96 -24
- package/docs/reference/std/conditional_disbursement.md +82 -14
- package/docs/reference/std/credit_facility.md +89 -16
- package/docs/reference/std/held_payment.md +155 -55
- package/docs/reference/std/instant_transfer.md +79 -12
- package/docs/reference/std/metered.md +71 -9
- package/docs/reference/std/pooled_split.md +76 -7
- package/docs/reference/std/premium_forward.md +100 -20
- package/docs/reference/std/reconciled_payout.md +84 -14
- package/docs/reference/std/rotating_pool.md +112 -24
- package/docs/reference/std/scheduled.md +117 -32
- package/docs/reference/std/security_deposit.md +119 -27
- package/docs/reference/std/settlement_batch.md +105 -24
- package/docs/reference/std/swap.md +113 -26
- package/docs/reference/std/threshold_pool.md +120 -29
- package/docs/reference/std/weighted_distribution.md +117 -21
- package/docs/reference/types.md +15 -15
- package/docs/reference/udl-output.md +6 -6
- package/examples/01-first-program/README.md +1 -1
- package/examples/{02-imports-and-archetypes → 02-imports-and-modules}/README.md +1 -1
- package/examples/{02-imports-and-archetypes → 02-imports-and-modules}/photo-booth.hsx +1 -1
- package/examples/04-complete-product/README.md +1 -1
- package/examples/05-authored-instrument/README.md +5 -0
- package/examples/05-authored-instrument/payment.hsx +37 -0
- package/examples/05-watch-club/watch-club.hsx +0 -1
- package/examples/README.md +1 -2
- package/examples/advance/advance.udl +29 -6
- package/examples/cancellable_booking/cancellable_booking.udl +19 -0
- package/examples/captured_payment/captured_payment.hsx +0 -4
- package/examples/captured_payment/captured_payment.udl +6 -0
- package/examples/conditional_disbursement/conditional_disbursement.hsx +0 -2
- package/examples/conditional_disbursement/conditional_disbursement.udl +4 -0
- package/examples/cost-table.json +136 -8
- package/examples/credit_facility/credit_facility.hsx +0 -3
- package/examples/credit_facility/credit_facility.udl +17 -1
- package/examples/held_payment/held_payment.udl +16 -0
- package/examples/instant_transfer/instant_transfer.udl +6 -0
- package/examples/metered/metered.udl +5 -1
- package/examples/pooled_split/pooled_split.udl +4 -0
- package/examples/premium_forward/premium_forward.udl +4 -0
- package/examples/reconciled_payout/reconciled_payout.udl +7 -0
- package/examples/rotating_pool/rotating_pool.udl +8 -0
- package/examples/scheduled/scheduled.udl +18 -3
- package/examples/security_deposit/security_deposit.udl +13 -0
- package/examples/settlement_batch/settlement_batch.udl +4 -0
- package/examples/swap/swap.udl +10 -0
- package/examples/threshold_pool/threshold_pool.udl +11 -0
- package/examples/weighted_distribution/weighted_distribution.udl +9 -0
- package/package.json +10 -10
- package/skills/hsx/SKILL.md +2 -36
- package/src/cli.ts +41 -5
- package/src/compile.ts +14 -6
- package/src/cost.ts +13 -16
- package/src/parse.ts +15 -10
- package/src/std-bundle.ts +21 -88
- package/src/typecheck.ts +207 -34
- package/src/version.ts +1 -1
- package/std/SEMANTICS.md +33 -128
- package/std/money_flows/advance.hsx +39 -26
- package/std/money_flows/cancellable_booking.hsx +275 -8
- package/std/money_flows/captured_payment.hsx +1 -9
- package/std/money_flows/conditional_disbursement.hsx +0 -6
- package/std/money_flows/credit_facility.hsx +0 -9
- package/std/money_flows/held_payment.hsx +39 -1
- package/std/money_flows/index.hsx +2 -1
- package/std/money_flows/metered.hsx +1 -5
- package/std/money_flows/scheduled.hsx +29 -119
- package/std/money_flows/threshold_pool.hsx +40 -5
- package/std/money_flows/weighted_distribution.hsx +47 -5
- package/docs/reference/std/recurring_collection.md +0 -25
- package/examples/recurring_collection/README.md +0 -3
- package/examples/recurring_collection/recurring_collection.hsx +0 -21
- package/examples/recurring_collection/recurring_collection.udl +0 -1135
- package/std/money_flows/recurring_collection.hsx +0 -72
|
@@ -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,
|
|
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,cAAc,EAAE,26SAA26S,CAAC;IAC77S,CAAC,yBAAyB,EAAE,usWAAusW,CAAC;IACpuW,CAAC,qCAAqC,EAAE,6++BAA6++B,CAAC;IACth/B,CAAC,kCAAkC,EAAE,4pdAA4pd,CAAC;IAClsd,CAAC,0CAA0C,EAAE,shSAAshS,CAAC;IACpkS,CAAC,iCAAiC,EAAE,s8QAAs8Q,CAAC;IAC3+Q,CAAC,8BAA8B,EAAE,g73EAAg73E,CAAC;IACl93E,CAAC,uBAAuB,EAAE,8JAA8J,CAAC;IACzL,CAAC,kCAAkC,EAAE,k7fAAk7f,CAAC;IACx9f,CAAC,yBAAyB,EAAE,siIAAsiI,CAAC;IACnkI,CAAC,8BAA8B,EAAE,slOAAslO,CAAC;IACxnO,CAAC,iCAAiC,EAAE,wucAAwuc,CAAC;IAC7wc,CAAC,mCAAmC,EAAE,4hSAA4hS,CAAC;IACnkS,CAAC,+BAA+B,EAAE,ohwBAAohwB,CAAC;IACvjwB,CAAC,2BAA2B,EAAE,gu2BAAgu2B,CAAC;IAC/v2B,CAAC,kCAAkC,EAAE,+0eAA+0e,CAAC;IACr3e,CAAC,kCAAkC,EAAE,s2dAAs2d,CAAC;IAC54d,CAAC,sBAAsB,EAAE,kxuCAAkxuC,CAAC;IAC5yuC,CAAC,gCAAgC,EAAE,ktpBAAktpB,CAAC;IACtvpB,CAAC,uCAAuC,EAAE,mtgBAAmtgB,CAAC;CAC/vgB,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typecheck.d.ts","sourceRoot":"","sources":["../../src/typecheck.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"typecheck.d.ts","sourceRoot":"","sources":["../../src/typecheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,WAAW,EAEjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAaV,OAAO,EAKR,MAAM,UAAU,CAAC;AASlB,OAAO,KAAK,EACV,kBAAkB,EASnB,MAAM,SAAS,CAAC;AAWjB,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,WAAW,CAAC;CACzC;AAkCD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CA4cpB"}
|
package/dist/src/typecheck.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { deriveUdlActionEffects, resolveUdlActionPlans, udlDocumentSchema, udlClauseVocabulary, } from "@hyperscale0/udl";
|
|
1
|
+
import { fixedIsoDurationMs, referencedUdlInstrumentIds, deriveUdlActionEffects, resolveUdlActionPlans, udlDocumentSchema, udlClauseVocabulary, } from "@hyperscale0/udl";
|
|
2
2
|
import { requiredFields, generalInstrumentValue, hasActionPlanClauses, lowerGeneralProgram, } from "./emit.js";
|
|
3
3
|
import { hsxClauseDiagnosticCode } from "./diagnostics.js";
|
|
4
4
|
import { HSX_LIMITS } from "./limits.js";
|
|
@@ -250,6 +250,24 @@ export function checkGeneralProgram(program, options = {}) {
|
|
|
250
250
|
if (options.publishedCatalog) {
|
|
251
251
|
checkPortCaptureTypes(allocated, options.publishedCatalog, aliases, diagnostics);
|
|
252
252
|
}
|
|
253
|
+
const prefixByInstrument = new Map();
|
|
254
|
+
if (options.publishedCatalog) {
|
|
255
|
+
for (const inst of options.publishedCatalog.instruments) {
|
|
256
|
+
if (inst.idPrefix) {
|
|
257
|
+
prefixByInstrument.set(inst.id, inst.idPrefix);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
for (const [name, decl] of templates) {
|
|
262
|
+
const prefix = stringSlot(decl.body, "idPrefix", "id_prefix") ?? prefixFor(name);
|
|
263
|
+
prefixByInstrument.set(name, prefix);
|
|
264
|
+
}
|
|
265
|
+
for (const candidate of allocated) {
|
|
266
|
+
const prefix = stringSlot(candidate.body, "idPrefix", "id_prefix") ??
|
|
267
|
+
prefixFor(candidate.name.name);
|
|
268
|
+
prefixByInstrument.set(candidate.name.name, prefix);
|
|
269
|
+
}
|
|
270
|
+
const resolvePrefix = (target) => prefixByInstrument.get(target);
|
|
253
271
|
const instruments = [];
|
|
254
272
|
const ids = new Set();
|
|
255
273
|
for (const candidate of allocated) {
|
|
@@ -263,14 +281,14 @@ export function checkGeneralProgram(program, options = {}) {
|
|
|
263
281
|
continue;
|
|
264
282
|
}
|
|
265
283
|
const expandedBody = expandComprehensions(candidate.body, diagnostics);
|
|
266
|
-
const checked = checkInstrument(candidate.name, expandedBody, candidate.parties, candidate.ports, candidate.aliases, diagnostics);
|
|
284
|
+
const checked = checkInstrument(candidate.name, expandedBody, candidate.parties, candidate.ports, candidate.aliases, diagnostics, resolvePrefix);
|
|
267
285
|
if (checked)
|
|
268
286
|
instruments.push(checked);
|
|
269
287
|
}
|
|
270
288
|
if (options.publishedCatalog) {
|
|
271
289
|
return checkPublishedProgram(program, options.publishedCatalog, header, subjects, instruments, diagnostics, report);
|
|
272
290
|
}
|
|
273
|
-
crossInstrumentReferenceDiagnostics(instruments, diagnostics);
|
|
291
|
+
crossInstrumentReferenceDiagnostics(instruments, diagnostics, new Set(templates.keys()));
|
|
274
292
|
if (header &&
|
|
275
293
|
instruments.length === 0 &&
|
|
276
294
|
!diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
@@ -343,6 +361,10 @@ function checkPublishedProgram(program, catalog, header, authoredSubjects, autho
|
|
|
343
361
|
report("HSX1021", exposure.span, `${target} is not an action on an explicitly used published instrument or authored instrument`, "add the matching use declaration and choose one of that instrument's actions");
|
|
344
362
|
continue;
|
|
345
363
|
}
|
|
364
|
+
if (action?.engineOwned || authoredAction?.slots.engineOwned === true) {
|
|
365
|
+
report("HSX1021", exposure.span, `${target} is engine-owned`, "expose a caller action instead");
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
346
368
|
if (!CAMEL_CASE.test(exposure.publicName.name)) {
|
|
347
369
|
report("HSX1020", exposure.publicName.span, `public action name ${exposure.publicName.name} is not camelCase`, "write a lowercase camelCase public action name");
|
|
348
370
|
continue;
|
|
@@ -466,23 +488,7 @@ function publishedInstrumentClosure(uses, catalogById) {
|
|
|
466
488
|
return selected;
|
|
467
489
|
}
|
|
468
490
|
function publishedInstrumentDependencies(instrument, idByPrefix) {
|
|
469
|
-
const found = new Set();
|
|
470
|
-
const visit = (value) => {
|
|
471
|
-
if (Array.isArray(value)) {
|
|
472
|
-
value.forEach(visit);
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
if (!value || typeof value !== "object")
|
|
476
|
-
return;
|
|
477
|
-
for (const [key, child] of Object.entries(value)) {
|
|
478
|
-
if ((key === "instrumentId" || key === "childInstrumentId") &&
|
|
479
|
-
typeof child === "string") {
|
|
480
|
-
found.add(child);
|
|
481
|
-
}
|
|
482
|
-
visit(child);
|
|
483
|
-
}
|
|
484
|
-
};
|
|
485
|
-
visit(instrument);
|
|
491
|
+
const found = new Set(referencedUdlInstrumentIds(instrument));
|
|
486
492
|
for (const field of Object.values(instrument.fields)) {
|
|
487
493
|
const prefix = typeof field.pattern === "string"
|
|
488
494
|
? /^\^([a-z]{2,8})_\(sandbox\|live\)_/.exec(field.pattern)?.[1]
|
|
@@ -673,7 +679,6 @@ const applicationMetadataKeys = new Set([
|
|
|
673
679
|
"action",
|
|
674
680
|
"agent_description",
|
|
675
681
|
"description",
|
|
676
|
-
"journeys",
|
|
677
682
|
"nav",
|
|
678
683
|
"navigation",
|
|
679
684
|
"summary",
|
|
@@ -934,7 +939,11 @@ function allocateGeneratedPrefixes(candidates) {
|
|
|
934
939
|
}
|
|
935
940
|
function inferSettlementTypeArgument(template, rows, typeParameter) {
|
|
936
941
|
for (const parameter of template.parameters) {
|
|
937
|
-
const
|
|
942
|
+
const declaredType = parameter.type;
|
|
943
|
+
const parameterType = declaredType.kind === "type_apply" &&
|
|
944
|
+
declaredType.callee.name === "optional"
|
|
945
|
+
? (declaredType.args[0] ?? declaredType)
|
|
946
|
+
: declaredType;
|
|
938
947
|
if (parameterType.kind !== "type_apply" ||
|
|
939
948
|
parameterType.callee.name !== "money" ||
|
|
940
949
|
parameterType.args[0]?.kind !== "ident" ||
|
|
@@ -1177,9 +1186,53 @@ function instantiate(template, application, diagnostics, instrumentName, ports,
|
|
|
1177
1186
|
if (diagnostics.length > dependencyDiagnosticStart)
|
|
1178
1187
|
return undefined;
|
|
1179
1188
|
const body = expandComprehensions(selected, diagnostics, values, portDependencies);
|
|
1189
|
+
const binding = {
|
|
1190
|
+
key: { kind: "ident", name: "template_binding", span: application.span },
|
|
1191
|
+
qualifiers: [],
|
|
1192
|
+
span: application.span,
|
|
1193
|
+
value: {
|
|
1194
|
+
kind: "block",
|
|
1195
|
+
span: application.span,
|
|
1196
|
+
entries: [
|
|
1197
|
+
{
|
|
1198
|
+
key: { kind: "ident", name: "id", span: application.span },
|
|
1199
|
+
qualifiers: [],
|
|
1200
|
+
span: application.span,
|
|
1201
|
+
value: {
|
|
1202
|
+
kind: "string",
|
|
1203
|
+
value: template.name.name,
|
|
1204
|
+
span: application.span,
|
|
1205
|
+
},
|
|
1206
|
+
},
|
|
1207
|
+
{
|
|
1208
|
+
key: { kind: "ident", name: "parameters", span: application.span },
|
|
1209
|
+
qualifiers: [],
|
|
1210
|
+
span: application.span,
|
|
1211
|
+
value: {
|
|
1212
|
+
kind: "block",
|
|
1213
|
+
span: application.span,
|
|
1214
|
+
entries: resolvedArgs.flatMap(({ parameter }) => {
|
|
1215
|
+
const value = values.get(parameter.name.name);
|
|
1216
|
+
return value &&
|
|
1217
|
+
["string", "number", "boolean"].includes(value.kind)
|
|
1218
|
+
? [
|
|
1219
|
+
{
|
|
1220
|
+
key: parameter.name,
|
|
1221
|
+
qualifiers: [],
|
|
1222
|
+
span: parameter.span,
|
|
1223
|
+
value,
|
|
1224
|
+
},
|
|
1225
|
+
]
|
|
1226
|
+
: [];
|
|
1227
|
+
}),
|
|
1228
|
+
},
|
|
1229
|
+
},
|
|
1230
|
+
],
|
|
1231
|
+
},
|
|
1232
|
+
};
|
|
1180
1233
|
return {
|
|
1181
1234
|
...body,
|
|
1182
|
-
entries: body.entries.map((row) => {
|
|
1235
|
+
entries: [...body.entries, binding].map((row) => {
|
|
1183
1236
|
if (row.key.name !== "fields" || row.value.kind !== "block")
|
|
1184
1237
|
return row;
|
|
1185
1238
|
return {
|
|
@@ -1663,9 +1716,14 @@ function substituteExpr(expr, values) {
|
|
|
1663
1716
|
}
|
|
1664
1717
|
: {}),
|
|
1665
1718
|
key: substituteName(entry.key, nestedValues),
|
|
1666
|
-
qualifiers: entry.qualifiers.
|
|
1719
|
+
qualifiers: entry.qualifiers.flatMap((qualifier, index) => {
|
|
1667
1720
|
const value = nestedValues.get(qualifier.name);
|
|
1668
1721
|
const fixed = fixedQualifier(entry.key.name, qualifier.name, index);
|
|
1722
|
+
if (entry.key.name === "on" &&
|
|
1723
|
+
!fixed &&
|
|
1724
|
+
index > 0 &&
|
|
1725
|
+
value?.kind === "list")
|
|
1726
|
+
return value.items.map((item) => nameFromValue(qualifier, item));
|
|
1669
1727
|
return !fixed && value
|
|
1670
1728
|
? nameFromValue(qualifier, value)
|
|
1671
1729
|
: substituteName(qualifier, nestedValues);
|
|
@@ -2094,6 +2152,17 @@ function evaluateCompileTimeCall(expr, resolveMissing = true) {
|
|
|
2094
2152
|
: undefined;
|
|
2095
2153
|
case "basis_points":
|
|
2096
2154
|
return first?.kind === "percent" ? numeric(first.bps) : undefined;
|
|
2155
|
+
case "shift_date": {
|
|
2156
|
+
const instant = first ? Date.parse(nameText(first)) : NaN;
|
|
2157
|
+
const offset = second ? fixedIsoDurationMs(nameText(second)) : null;
|
|
2158
|
+
if (!Number.isFinite(instant) || offset === null)
|
|
2159
|
+
return text(NONE_SENTINEL);
|
|
2160
|
+
const direction = third && nameText(third) === "before" ? -1 : 1;
|
|
2161
|
+
const shifted = new Date(instant + direction * offset);
|
|
2162
|
+
return Number.isFinite(shifted.getTime())
|
|
2163
|
+
? text(shifted.toISOString())
|
|
2164
|
+
: text(NONE_SENTINEL);
|
|
2165
|
+
}
|
|
2097
2166
|
case "names": {
|
|
2098
2167
|
const count = number(second);
|
|
2099
2168
|
if (!first || count === undefined || count < 0 || !third)
|
|
@@ -2112,6 +2181,13 @@ function evaluateCompileTimeCall(expr, resolveMissing = true) {
|
|
|
2112
2181
|
span: expr.span,
|
|
2113
2182
|
}
|
|
2114
2183
|
: undefined;
|
|
2184
|
+
case "without":
|
|
2185
|
+
return first?.kind === "list" && second
|
|
2186
|
+
? {
|
|
2187
|
+
...first,
|
|
2188
|
+
items: first.items.filter((item) => nameText(item) !== nameText(second)),
|
|
2189
|
+
}
|
|
2190
|
+
: undefined;
|
|
2115
2191
|
case "concat_lists":
|
|
2116
2192
|
return expr.args.every((arg) => arg.kind === "list")
|
|
2117
2193
|
? {
|
|
@@ -2254,9 +2330,14 @@ function expandComprehensions(block, diagnostics, values = new Map(), dependenci
|
|
|
2254
2330
|
const substituted = {
|
|
2255
2331
|
...row,
|
|
2256
2332
|
key: substituteName(row.key, locals),
|
|
2257
|
-
qualifiers: row.qualifiers.
|
|
2333
|
+
qualifiers: row.qualifiers.flatMap((qualifier, index) => {
|
|
2258
2334
|
const value = locals.get(qualifier.name);
|
|
2259
2335
|
const fixed = fixedQualifier(row.key.name, qualifier.name, index);
|
|
2336
|
+
if (row.key.name === "on" &&
|
|
2337
|
+
!fixed &&
|
|
2338
|
+
index > 0 &&
|
|
2339
|
+
value?.kind === "list")
|
|
2340
|
+
return value.items.map((item) => nameFromValue(qualifier, item));
|
|
2260
2341
|
return !fixed && value
|
|
2261
2342
|
? nameFromValue(qualifier, value)
|
|
2262
2343
|
: substituteName(qualifier, locals);
|
|
@@ -2327,7 +2408,7 @@ function finiteIterationValues(bound) {
|
|
|
2327
2408
|
span: bound.span,
|
|
2328
2409
|
}));
|
|
2329
2410
|
}
|
|
2330
|
-
function checkInstrument(name, body, parties, ports, aliases, diagnostics) {
|
|
2411
|
+
function checkInstrument(name, body, parties, ports, aliases, diagnostics, resolvePrefix) {
|
|
2331
2412
|
if (!SNAKE_CASE.test(name.name)) {
|
|
2332
2413
|
diagnostics.push({
|
|
2333
2414
|
code: "HSX1003",
|
|
@@ -2339,7 +2420,7 @@ function checkInstrument(name, body, parties, ports, aliases, diagnostics) {
|
|
|
2339
2420
|
}
|
|
2340
2421
|
const fieldsEntry = entry(body, "fields");
|
|
2341
2422
|
const fields = fieldsEntry?.value.kind === "block"
|
|
2342
|
-
? checkFields(fieldsEntry.value, aliases, diagnostics)
|
|
2423
|
+
? checkFields(fieldsEntry.value, aliases, diagnostics, resolvePrefix)
|
|
2343
2424
|
: [];
|
|
2344
2425
|
if (!fieldsEntry) {
|
|
2345
2426
|
diagnostics.push({
|
|
@@ -2749,7 +2830,7 @@ function checkRequiredFields(value, fields, span, diagnostics) {
|
|
|
2749
2830
|
}
|
|
2750
2831
|
return valid ? value : undefined;
|
|
2751
2832
|
}
|
|
2752
|
-
function checkFields(block, aliases, diagnostics) {
|
|
2833
|
+
function checkFields(block, aliases, diagnostics, resolvePrefix) {
|
|
2753
2834
|
const result = [];
|
|
2754
2835
|
const names = new Set();
|
|
2755
2836
|
for (const row of block.entries) {
|
|
@@ -2774,11 +2855,11 @@ function checkFields(block, aliases, diagnostics) {
|
|
|
2774
2855
|
continue;
|
|
2775
2856
|
}
|
|
2776
2857
|
names.add(name);
|
|
2777
|
-
result.push(lowerField(row, aliases, diagnostics));
|
|
2858
|
+
result.push(lowerField(row, aliases, diagnostics, resolvePrefix));
|
|
2778
2859
|
}
|
|
2779
2860
|
return result;
|
|
2780
2861
|
}
|
|
2781
|
-
function lowerField(row, aliases, diagnostics) {
|
|
2862
|
+
function lowerField(row, aliases, diagnostics, resolvePrefix) {
|
|
2782
2863
|
const name = camel(row.key.name);
|
|
2783
2864
|
let typeExpr = row.value;
|
|
2784
2865
|
let description;
|
|
@@ -2830,6 +2911,23 @@ function lowerField(row, aliases, diagnostics) {
|
|
|
2830
2911
|
span: declaredSpan,
|
|
2831
2912
|
});
|
|
2832
2913
|
}
|
|
2914
|
+
else if (type.kind === "ref" && type.target) {
|
|
2915
|
+
const prefix = resolvePrefix?.(type.target);
|
|
2916
|
+
if (!extra.pattern) {
|
|
2917
|
+
if (prefix) {
|
|
2918
|
+
extra.pattern = `^${prefix}_(sandbox|live)_[a-z0-9]{8,64}$`;
|
|
2919
|
+
}
|
|
2920
|
+
else if (resolvePrefix !== undefined) {
|
|
2921
|
+
diagnostics.push({
|
|
2922
|
+
code: "HSX1007",
|
|
2923
|
+
fix: "reference an instrument declared in this program, imported from std, or published in the catalogue",
|
|
2924
|
+
message: `field ${name} references unknown instrument ${type.target}`,
|
|
2925
|
+
severity: "error",
|
|
2926
|
+
span: declaredSpan,
|
|
2927
|
+
});
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2833
2931
|
const structuralType = Object.hasOwn(extra, "items")
|
|
2834
2932
|
? "array"
|
|
2835
2933
|
: Object.hasOwn(extra, "properties") ||
|
|
@@ -3212,8 +3310,32 @@ function pinFixedCurrencyBindings(action, fields, diagnostics) {
|
|
|
3212
3310
|
function bindFieldReferences(slots, fields, action, diagnostics) {
|
|
3213
3311
|
const externalFieldSlot = (path) => {
|
|
3214
3312
|
const key = path.at(-1);
|
|
3215
|
-
|
|
3216
|
-
|
|
3313
|
+
const input = slots.input !== undefined && isJsonObject(slots.input)
|
|
3314
|
+
? slots.input
|
|
3315
|
+
: undefined;
|
|
3316
|
+
const allocation = slots.allocate !== undefined && isJsonObject(slots.allocate)
|
|
3317
|
+
? slots.allocate
|
|
3318
|
+
: undefined;
|
|
3319
|
+
const allocationOperand = path.includes("allocate") &&
|
|
3320
|
+
["amountField", "sourceAccountField", "paymentIdentityField"].includes(key ?? "");
|
|
3321
|
+
return ((allocationOperand &&
|
|
3322
|
+
Array.isArray(input?.required) &&
|
|
3323
|
+
allocation !== undefined &&
|
|
3324
|
+
input.required.includes(allocation[key])) ||
|
|
3325
|
+
(path.includes("funding") &&
|
|
3326
|
+
[
|
|
3327
|
+
"ticketRefField",
|
|
3328
|
+
"ticketAmountField",
|
|
3329
|
+
"ticketInvestorField",
|
|
3330
|
+
"ticketAccountField",
|
|
3331
|
+
].includes(key ?? "")) ||
|
|
3332
|
+
(path.includes("requiresExposure") && key === "minimumField") ||
|
|
3333
|
+
(path.includes("requiresAggregate") && key === "refField") ||
|
|
3334
|
+
(path.includes("requiresAggregate") &&
|
|
3335
|
+
["amountField", "dateField", "positionField"].includes(key ?? "")) ||
|
|
3336
|
+
(path.includes("requiresAggregate") &&
|
|
3337
|
+
path.includes("dueBefore") &&
|
|
3338
|
+
key === "field") ||
|
|
3217
3339
|
(path.includes("signedSum") && key === "refField") ||
|
|
3218
3340
|
(path.includes("signedSum") && key === "amountField") ||
|
|
3219
3341
|
(path.includes("remainder") && key === "refField") ||
|
|
@@ -3248,7 +3370,7 @@ function bindFieldReferences(slots, fields, action, diagnostics) {
|
|
|
3248
3370
|
};
|
|
3249
3371
|
visit(slots);
|
|
3250
3372
|
}
|
|
3251
|
-
function crossInstrumentReferenceDiagnostics(instruments, diagnostics) {
|
|
3373
|
+
function crossInstrumentReferenceDiagnostics(instruments, diagnostics, knownTargets) {
|
|
3252
3374
|
const byId = new Map(instruments.map((instrument) => [instrument.id, instrument]));
|
|
3253
3375
|
const report = (owner, span, message, fix) => {
|
|
3254
3376
|
diagnostics.push({
|
|
@@ -3270,7 +3392,8 @@ function crossInstrumentReferenceDiagnostics(instruments, diagnostics) {
|
|
|
3270
3392
|
for (const field of owner.fields) {
|
|
3271
3393
|
if (field.type.kind !== "ref" || !field.type.target)
|
|
3272
3394
|
continue;
|
|
3273
|
-
if (!byId.has(field.type.target)
|
|
3395
|
+
if (!byId.has(field.type.target) &&
|
|
3396
|
+
!knownTargets?.has(field.type.target)) {
|
|
3274
3397
|
report(owner, field.origin, `field ${field.name} has ref<${field.type.target}>, but that instrument does not exist`, `declare instrument ${field.type.target} or correct the ref target`);
|
|
3275
3398
|
}
|
|
3276
3399
|
}
|
|
@@ -3333,14 +3456,22 @@ function crossInstrumentReferenceDiagnostics(instruments, diagnostics) {
|
|
|
3333
3456
|
return;
|
|
3334
3457
|
}
|
|
3335
3458
|
const refField = relation.refField;
|
|
3459
|
+
const anchorName = relation.anchorField ??
|
|
3460
|
+
(relation.over === "siblings" ? refField : undefined);
|
|
3461
|
+
const anchor = typeof anchorName === "string" ? fieldIn(owner, anchorName) : undefined;
|
|
3462
|
+
const expectedTarget = anchor?.type.kind === "ref" ? anchor.type.target : owner.id;
|
|
3463
|
+
if (typeof anchorName === "string" &&
|
|
3464
|
+
(!anchor || anchor.type.kind !== "ref"))
|
|
3465
|
+
report(owner, action.origin, `aggregate anchor ${anchorName} is not a reference`, "declare the anchor as a typed parent reference");
|
|
3336
3466
|
if (typeof refField === "string") {
|
|
3337
3467
|
const field = fieldIn(target, refField);
|
|
3338
3468
|
if (!field) {
|
|
3339
|
-
report(owner, action.origin, `${targetId}.${refField} does not exist`, `declare ${refField} on ${targetId} as ref<${
|
|
3469
|
+
report(owner, action.origin, `${targetId}.${refField} does not exist`, `declare ${refField} on ${targetId} as ref<${expectedTarget}> or correct the clause`);
|
|
3340
3470
|
}
|
|
3341
3471
|
else if (field.type.kind !== "ref" ||
|
|
3342
|
-
(field.type.target !== undefined &&
|
|
3343
|
-
|
|
3472
|
+
(field.type.target !== undefined &&
|
|
3473
|
+
field.type.target !== expectedTarget)) {
|
|
3474
|
+
report(owner, action.origin, `${targetId}.${refField} has ${typeWords(field.type)}; this relation needs ref<${expectedTarget}>`, `change ${targetId}.${refField} to ref<${expectedTarget}>`);
|
|
3344
3475
|
}
|
|
3345
3476
|
}
|
|
3346
3477
|
const check = jsonObject(relation.check);
|
|
@@ -3355,8 +3486,9 @@ function crossInstrumentReferenceDiagnostics(instruments, diagnostics) {
|
|
|
3355
3486
|
}
|
|
3356
3487
|
const targetField = check?.targetField;
|
|
3357
3488
|
if (typeof targetField === "string" &&
|
|
3358
|
-
fieldIn(owner, targetField)?.type.kind !==
|
|
3359
|
-
|
|
3489
|
+
fieldIn(owner, targetField)?.type.kind !==
|
|
3490
|
+
(check?.kind === "count_at_least" ? "integer" : "money")) {
|
|
3491
|
+
report(owner, action.origin, `${owner.id}.${targetField} is not declared ${check?.kind === "count_at_least" ? "integer" : "money"}`, `declare ${owner.id}.${targetField} as money<C> or correct the aggregate target`);
|
|
3360
3492
|
}
|
|
3361
3493
|
const statuses = relation.statuses;
|
|
3362
3494
|
if (Array.isArray(statuses)) {
|