@hyperscale0/udl 4.4.1 → 4.5.1

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +24 -2
  3. package/dist/canonical.d.ts.map +1 -1
  4. package/dist/canonical.js +1 -29
  5. package/dist/canonical.js.map +1 -1
  6. package/dist/diagnostics.d.ts.map +1 -1
  7. package/dist/diagnostics.js +9 -6
  8. package/dist/diagnostics.js.map +1 -1
  9. package/dist/evolution.d.ts.map +1 -1
  10. package/dist/evolution.js +6 -16
  11. package/dist/evolution.js.map +1 -1
  12. package/dist/json.d.ts +2 -0
  13. package/dist/json.d.ts.map +1 -0
  14. package/dist/json.js +30 -0
  15. package/dist/json.js.map +1 -0
  16. package/dist/limits.d.ts +3 -30
  17. package/dist/limits.d.ts.map +1 -1
  18. package/dist/limits.js +4 -31
  19. package/dist/limits.js.map +1 -1
  20. package/dist/object-contract.d.ts.map +1 -1
  21. package/dist/object-contract.js +4 -1
  22. package/dist/object-contract.js.map +1 -1
  23. package/dist/parser.d.ts.map +1 -1
  24. package/dist/parser.js +23 -0
  25. package/dist/parser.js.map +1 -1
  26. package/dist/reporting-validation.d.ts.map +1 -1
  27. package/dist/reporting-validation.js +12 -5
  28. package/dist/reporting-validation.js.map +1 -1
  29. package/dist/schema.d.ts.map +1 -1
  30. package/dist/schema.js +3 -4
  31. package/dist/schema.js.map +1 -1
  32. package/dist/validation.d.ts.map +1 -1
  33. package/dist/validation.js +157 -43
  34. package/dist/validation.js.map +1 -1
  35. package/docs/README.md +69 -33
  36. package/package.json +2 -2
  37. package/spec/README.md +69 -33
  38. package/src/canonical.ts +1 -35
  39. package/src/diagnostics.ts +9 -7
  40. package/src/evolution.ts +8 -18
  41. package/src/json.ts +34 -0
  42. package/src/limits.ts +4 -31
  43. package/src/object-contract.ts +5 -1
  44. package/src/parser.ts +22 -0
  45. package/src/reporting-validation.ts +12 -10
  46. package/src/schema.ts +7 -10
  47. package/src/validation.ts +220 -47
package/src/canonical.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { assertValidUdl } from "./validation.js";
2
-
3
- const INDENT = " ";
2
+ import { writeJson } from "./json.js";
4
3
 
5
4
  export function serializeUdl(value: unknown): string {
6
5
  const document = assertValidUdl(value);
@@ -17,36 +16,3 @@ export async function canonicalDigest(value: unknown): Promise<string> {
17
16
  .map((byte) => byte.toString(16).padStart(2, "0"))
18
17
  .join("");
19
18
  }
20
-
21
- function writeJson(value: unknown, depth: number): string {
22
- if (value === null || typeof value !== "object") {
23
- const encoded = JSON.stringify(value);
24
- if (encoded === undefined) {
25
- throw new TypeError("UDL canonical JSON received a non-JSON value");
26
- }
27
- return encoded;
28
- }
29
-
30
- const indent = INDENT.repeat(depth);
31
- const childIndent = INDENT.repeat(depth + 1);
32
- if (Array.isArray(value)) {
33
- if (value.length === 0) return "[]";
34
- return `[\n${value
35
- .map((item) => `${childIndent}${writeJson(item, depth + 1)}`)
36
- .join(",\n")}\n${indent}]`;
37
- }
38
-
39
- const record = value as Record<string, unknown>;
40
- const keys = Object.keys(record).sort(compareCodeUnits);
41
- if (keys.length === 0) return "{}";
42
- return `{\n${keys
43
- .map(
44
- (key) =>
45
- `${childIndent}${JSON.stringify(key)}: ${writeJson(record[key], depth + 1)}`,
46
- )
47
- .join(",\n")}\n${indent}}`;
48
- }
49
-
50
- function compareCodeUnits(left: string, right: string): number {
51
- return left < right ? -1 : left > right ? 1 : 0;
52
- }
@@ -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: "invalid_semantics",
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 (stable(party) !== stable(next.parties[name]))
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 || stable(object) !== stable(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 || stable(instrument) !== stable(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 (stable(live) !== stable(next) && next.version <= live.version)
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
- financeAccounts: 16,
3
- financeEffects: 128,
4
- financePathVariants: 256,
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
- maxStringLength: 2_048,
33
- maxTotalStringLength: 512 * 1_024,
6
+ maxTotalStringLength: 1_024 * 1_024,
34
7
  });
@@ -511,7 +511,11 @@ export function objectActionState(
511
511
  ...action,
512
512
  requiredNow: action.requirements
513
513
  .filter((field) => {
514
- const conditions = action.requirementConditions?.[field.name];
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
- : (sealed ??
210
- (field && ["ref", "account", "enum"].includes(field.type)
211
- ? "text"
212
- : field?.type));
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
- JSON.stringify(
550
+ writeJson(
550
551
  Object.fromEntries(
551
- Object.entries(field)
552
- .filter(
553
- ([key, value]) =>
554
- !["name", "description", "optional"].includes(key) &&
555
- value !== undefined,
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);