@hyperscale0/udl 4.4.1 → 4.5.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 +20 -0
- package/README.md +1 -1
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +1 -29
- package/dist/canonical.js.map +1 -1
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +9 -6
- package/dist/diagnostics.js.map +1 -1
- package/dist/evolution.d.ts.map +1 -1
- package/dist/evolution.js +6 -16
- package/dist/evolution.js.map +1 -1
- package/dist/json.d.ts +2 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +30 -0
- package/dist/json.js.map +1 -0
- package/dist/limits.d.ts +3 -30
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +4 -31
- package/dist/limits.js.map +1 -1
- package/dist/object-contract.d.ts.map +1 -1
- package/dist/object-contract.js +4 -1
- package/dist/object-contract.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +23 -0
- package/dist/parser.js.map +1 -1
- package/dist/reporting-validation.d.ts.map +1 -1
- package/dist/reporting-validation.js +12 -5
- package/dist/reporting-validation.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +3 -4
- package/dist/schema.js.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +157 -43
- package/dist/validation.js.map +1 -1
- package/docs/README.md +48 -32
- package/package.json +2 -2
- package/spec/README.md +48 -32
- package/src/canonical.ts +1 -35
- package/src/diagnostics.ts +9 -7
- package/src/evolution.ts +8 -18
- package/src/json.ts +34 -0
- package/src/limits.ts +4 -31
- package/src/object-contract.ts +5 -1
- package/src/parser.ts +22 -0
- package/src/reporting-validation.ts +12 -10
- package/src/schema.ts +7 -10
- package/src/validation.ts +220 -47
package/src/diagnostics.ts
CHANGED
|
@@ -75,19 +75,21 @@ export function issue(
|
|
|
75
75
|
path,
|
|
76
76
|
message,
|
|
77
77
|
fix: fixes[code],
|
|
78
|
-
category:
|
|
79
|
-
code === "UDL1003"
|
|
80
|
-
? "invalid_shape"
|
|
81
|
-
: code === "UDL1004"
|
|
82
|
-
? "resource_limit"
|
|
83
|
-
: "invalid_semantics",
|
|
78
|
+
category: category(code),
|
|
84
79
|
};
|
|
85
80
|
}
|
|
81
|
+
function category(code: string): string {
|
|
82
|
+
return code === "UDL1003"
|
|
83
|
+
? "invalid_shape"
|
|
84
|
+
: code === "UDL1004"
|
|
85
|
+
? "resource_limit"
|
|
86
|
+
: "invalid_semantics";
|
|
87
|
+
}
|
|
86
88
|
export const udlDiagnostics = Object.entries(fixes).map(([code, fix]) => ({
|
|
87
89
|
code,
|
|
88
90
|
fix,
|
|
89
91
|
title: fix,
|
|
90
|
-
category:
|
|
92
|
+
category: category(code),
|
|
91
93
|
family: "document",
|
|
92
94
|
}));
|
|
93
95
|
export const udlDiagnostic = (code: string) =>
|
package/src/evolution.ts
CHANGED
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
import type { UdlDocument } from "./schema.js";
|
|
2
2
|
import { issue, type UdlIssue } from "./diagnostics.js";
|
|
3
|
-
|
|
4
|
-
function stable(value: unknown): string {
|
|
5
|
-
if (Array.isArray(value)) return "[" + value.map(stable).join(",") + "]";
|
|
6
|
-
if (value && typeof value === "object")
|
|
7
|
-
return (
|
|
8
|
-
"{" +
|
|
9
|
-
Object.entries(value)
|
|
10
|
-
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
11
|
-
.map(([key, entry]) => JSON.stringify(key) + ":" + stable(entry))
|
|
12
|
-
.join(",") +
|
|
13
|
-
"}"
|
|
14
|
-
);
|
|
15
|
-
return JSON.stringify(value) ?? "undefined";
|
|
16
|
-
}
|
|
3
|
+
import { writeJson } from "./json.js";
|
|
17
4
|
|
|
18
5
|
/** Existing instruments are frozen; a live document may add independent instruments. */
|
|
19
6
|
export function diffValidatedUdlEvolution(
|
|
@@ -26,11 +13,14 @@ export function diffValidatedUdlEvolution(
|
|
|
26
13
|
if (live.product !== next.product || live.currency !== next.currency)
|
|
27
14
|
refuse("$", "product identity and currency are immutable");
|
|
28
15
|
for (const [name, party] of Object.entries(live.parties))
|
|
29
|
-
if (
|
|
16
|
+
if (
|
|
17
|
+
!Object.hasOwn(next.parties, name) ||
|
|
18
|
+
writeJson(party) !== writeJson(next.parties[name])
|
|
19
|
+
)
|
|
30
20
|
refuse(`$.parties.${name}`, "a live party cannot change or disappear");
|
|
31
21
|
for (const object of live.objects) {
|
|
32
22
|
const updated = next.objects.find((o) => o.id === object.id);
|
|
33
|
-
if (!updated ||
|
|
23
|
+
if (!updated || writeJson(object) !== writeJson(updated))
|
|
34
24
|
refuse(
|
|
35
25
|
`$.objects.${object.id}`,
|
|
36
26
|
"a live object kind cannot change or disappear; recreate development estates",
|
|
@@ -38,13 +28,13 @@ export function diffValidatedUdlEvolution(
|
|
|
38
28
|
}
|
|
39
29
|
for (const instrument of live.instruments) {
|
|
40
30
|
const updated = next.instruments.find((i) => i.id === instrument.id);
|
|
41
|
-
if (!updated ||
|
|
31
|
+
if (!updated || writeJson(instrument) !== writeJson(updated))
|
|
42
32
|
refuse(
|
|
43
33
|
`$.instruments.${instrument.id}`,
|
|
44
34
|
"a live instrument cannot change or disappear; recreate development estates",
|
|
45
35
|
);
|
|
46
36
|
}
|
|
47
|
-
if (
|
|
37
|
+
if (writeJson(live) !== writeJson(next) && next.version <= live.version)
|
|
48
38
|
issues.push(
|
|
49
39
|
issue(
|
|
50
40
|
"UDL7002",
|
package/src/json.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const INDENT = " ";
|
|
2
|
+
|
|
3
|
+
export function writeJson(value: unknown, depth = 0): string {
|
|
4
|
+
if (value === null || typeof value !== "object") {
|
|
5
|
+
const encoded = JSON.stringify(value);
|
|
6
|
+
if (encoded === undefined) {
|
|
7
|
+
throw new TypeError("UDL canonical JSON received a non-JSON value");
|
|
8
|
+
}
|
|
9
|
+
return encoded;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const indent = INDENT.repeat(depth);
|
|
13
|
+
const childIndent = INDENT.repeat(depth + 1);
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
if (value.length === 0) return "[]";
|
|
16
|
+
return `[\n${value
|
|
17
|
+
.map((item) => `${childIndent}${writeJson(item, depth + 1)}`)
|
|
18
|
+
.join(",\n")}\n${indent}]`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const record = value as Record<string, unknown>;
|
|
22
|
+
const keys = Object.keys(record).sort(compareCodeUnits);
|
|
23
|
+
if (keys.length === 0) return "{}";
|
|
24
|
+
return `{\n${keys
|
|
25
|
+
.map(
|
|
26
|
+
(key) =>
|
|
27
|
+
`${childIndent}${JSON.stringify(key)}: ${writeJson(record[key], depth + 1)}`,
|
|
28
|
+
)
|
|
29
|
+
.join(",\n")}\n${indent}}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function compareCodeUnits(left: string, right: string): number {
|
|
33
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
34
|
+
}
|
package/src/limits.ts
CHANGED
|
@@ -1,34 +1,7 @@
|
|
|
1
1
|
export const UDL_LIMITS = Object.freeze({
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
financeStates: 32,
|
|
6
|
-
financeTransitionEdges: 128,
|
|
7
|
-
financeTransitions: 64,
|
|
8
|
-
financeActions: 64,
|
|
9
|
-
financeWork: 4_096,
|
|
10
|
-
maxActionExpansion: 256,
|
|
11
|
-
maxActionLeaves: 256,
|
|
12
|
-
maxDepth: 24,
|
|
13
|
-
maxKeyLength: 128,
|
|
14
|
-
maxNodes: 20_000,
|
|
15
|
-
maxPatternLength: 320,
|
|
16
|
-
/**
|
|
17
|
-
* Upper bound on the match attempts a document-authored `pattern` can force
|
|
18
|
-
* the backtracking engine to explore at its single anchored start position.
|
|
19
|
-
* Every admitted pattern's search space is multiplied out at admission
|
|
20
|
-
* (alternation branches x optional atoms x variable-quantifier spans), so
|
|
21
|
-
* worst-case matching cost is this budget times the pattern length, not an
|
|
22
|
-
* exponential in the document author's choice of grammar.
|
|
23
|
-
*/
|
|
24
|
-
maxPatternPaths: 4_096,
|
|
25
|
-
/**
|
|
26
|
-
* Reference-shape classifications one `ReferenceShapeBudget` may buy. Each
|
|
27
|
-
* one classifies canonical reference pattern text without compiling a validator.
|
|
28
|
-
* This safety net covers a catalogue plus hundreds of authored instruments.
|
|
29
|
-
*/
|
|
30
|
-
maxSchemaProbes: 131_072,
|
|
2
|
+
maxDepth: 32,
|
|
3
|
+
maxKeyLength: 240,
|
|
4
|
+
maxNodes: 100_000,
|
|
31
5
|
maxSourceBytes: 1_024 * 1_024,
|
|
32
|
-
|
|
33
|
-
maxTotalStringLength: 512 * 1_024,
|
|
6
|
+
maxTotalStringLength: 1_024 * 1_024,
|
|
34
7
|
});
|
package/src/object-contract.ts
CHANGED
|
@@ -511,7 +511,11 @@ export function objectActionState(
|
|
|
511
511
|
...action,
|
|
512
512
|
requiredNow: action.requirements
|
|
513
513
|
.filter((field) => {
|
|
514
|
-
const conditions =
|
|
514
|
+
const conditions =
|
|
515
|
+
action.requirementConditions &&
|
|
516
|
+
Object.hasOwn(action.requirementConditions, field.name)
|
|
517
|
+
? action.requirementConditions[field.name]
|
|
518
|
+
: undefined;
|
|
515
519
|
if (
|
|
516
520
|
conditions &&
|
|
517
521
|
!requirementPossible(
|
package/src/parser.ts
CHANGED
|
@@ -40,6 +40,28 @@ function parseUdlText(source: string): UdlDocument {
|
|
|
40
40
|
),
|
|
41
41
|
]);
|
|
42
42
|
}
|
|
43
|
+
// JSON.parse has already checked syntax. Scan member names before its
|
|
44
|
+
// last-value-wins behavior can erase an authored declaration.
|
|
45
|
+
const members: (Set<string> | null)[] = [];
|
|
46
|
+
let name = "";
|
|
47
|
+
for (const token of source.matchAll(/"(?:[^"\\]|\\.)*"|[{}[\]:]/g)) {
|
|
48
|
+
const text = token[0];
|
|
49
|
+
if (text === "{") members.push(new Set());
|
|
50
|
+
else if (text === "[") members.push(null);
|
|
51
|
+
else if (text === "}" || text === "]") members.pop();
|
|
52
|
+
else if (text === ":") {
|
|
53
|
+
const keys = members[members.length - 1];
|
|
54
|
+
if (keys?.has(name))
|
|
55
|
+
throw new UdlError([
|
|
56
|
+
issue(
|
|
57
|
+
"UDL1002",
|
|
58
|
+
"$",
|
|
59
|
+
`duplicate JSON member ${name} at offset ${token.index}`,
|
|
60
|
+
),
|
|
61
|
+
]);
|
|
62
|
+
keys?.add(name);
|
|
63
|
+
} else name = JSON.parse(text) as string;
|
|
64
|
+
}
|
|
43
65
|
return assertValidUdl(value);
|
|
44
66
|
}
|
|
45
67
|
|
|
@@ -6,6 +6,11 @@ import type {
|
|
|
6
6
|
} from "./reporting.js";
|
|
7
7
|
|
|
8
8
|
type Types = Map<string, ReportType>;
|
|
9
|
+
const sealedFields = new Map([
|
|
10
|
+
["id", "text"],
|
|
11
|
+
["status", "text"],
|
|
12
|
+
["createdAt", "date"],
|
|
13
|
+
]);
|
|
9
14
|
const integer: ReportType = { kind: "integer" };
|
|
10
15
|
const boolean: ReportType = { kind: "boolean" };
|
|
11
16
|
const date: ReportType = { kind: "date" };
|
|
@@ -195,21 +200,18 @@ export function validateReportDefinition(
|
|
|
195
200
|
const field = instrument.fields.find(
|
|
196
201
|
(entry) => entry.name === parts[0],
|
|
197
202
|
);
|
|
198
|
-
const sealed = (
|
|
199
|
-
{ id: "text", status: "text", createdAt: "date" } as Record<
|
|
200
|
-
string,
|
|
201
|
-
string
|
|
202
|
-
>
|
|
203
|
-
)[column.field];
|
|
203
|
+
const sealed = sealedFields.get(column.field);
|
|
204
204
|
const actual =
|
|
205
205
|
parts.length === 2 &&
|
|
206
206
|
parts[1] === "balance" &&
|
|
207
207
|
field?.type === "account"
|
|
208
208
|
? "money"
|
|
209
|
-
:
|
|
210
|
-
(
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
: parts.length === 1
|
|
210
|
+
? (sealed ??
|
|
211
|
+
(field && ["ref", "account", "enum"].includes(field.type)
|
|
212
|
+
? "text"
|
|
213
|
+
: field?.type))
|
|
214
|
+
: undefined;
|
|
213
215
|
requireRule(
|
|
214
216
|
actual === column.type.kind,
|
|
215
217
|
`Unknown or mistyped report field ${id}.${column.field}`,
|
package/src/schema.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
import { reportDefinitionSchema } from "./reporting.js";
|
|
3
|
+
import { writeJson } from "./json.js";
|
|
3
4
|
|
|
4
5
|
export const UDL_FORMAT_VERSION = 4 as const;
|
|
5
6
|
/** Counts the root and every nested invocation, including selected and ranged children. */
|
|
@@ -546,17 +547,13 @@ export function sameObjectField(
|
|
|
546
547
|
right: UdlObjectField,
|
|
547
548
|
): boolean {
|
|
548
549
|
const signature = (field: UdlObjectField) =>
|
|
549
|
-
|
|
550
|
+
writeJson(
|
|
550
551
|
Object.fromEntries(
|
|
551
|
-
Object.entries(field)
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
)
|
|
557
|
-
.sort(([left], [right]) =>
|
|
558
|
-
left < right ? -1 : left > right ? 1 : 0,
|
|
559
|
-
),
|
|
552
|
+
Object.entries(field).filter(
|
|
553
|
+
([key, value]) =>
|
|
554
|
+
!["name", "description", "optional"].includes(key) &&
|
|
555
|
+
value !== undefined,
|
|
556
|
+
),
|
|
560
557
|
),
|
|
561
558
|
);
|
|
562
559
|
return signature(left) === signature(right);
|