@hyperscale0/udl 1.0.342 → 1.0.347
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/README.md +3 -2
- package/dist/canonical.d.ts +15 -3
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +77 -12
- package/dist/canonical.js.map +1 -1
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +2 -0
- package/dist/diagnostics.js.map +1 -1
- package/dist/formats.d.ts +27 -0
- package/dist/formats.d.ts.map +1 -0
- package/dist/formats.js +31 -0
- package/dist/formats.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/object-contract.d.ts +4 -0
- package/dist/object-contract.d.ts.map +1 -1
- package/dist/object-contract.js.map +1 -1
- package/dist/object-semantics-schema.d.ts +276 -92
- package/dist/object-semantics-schema.d.ts.map +1 -1
- package/dist/schema.d.ts +6481 -115
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +58 -10
- package/dist/schema.js.map +1 -1
- package/dist/serialize.d.ts +5 -0
- package/dist/serialize.d.ts.map +1 -0
- package/dist/serialize.js +14 -0
- package/dist/serialize.js.map +1 -0
- package/dist/validation.d.ts +19 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +49 -7
- package/dist/validation.js.map +1 -1
- package/package.json +3 -2
- package/spec/cars.udl.json +1 -1
- package/spec/udl.schema.json +529 -489
- package/src/canonical.ts +84 -14
- package/src/diagnostics.ts +6 -0
- package/src/formats.ts +56 -0
- package/src/index.ts +9 -1
- package/src/object-contract.ts +4 -1
- package/src/schema.ts +71 -11
- package/src/serialize.ts +14 -0
- package/src/validation.ts +65 -10
- package/dist/json.d.ts +0 -2
- package/dist/json.d.ts.map +0 -1
- package/dist/json.js +0 -30
- package/dist/json.js.map +0 -1
- package/src/json.ts +0 -34
package/src/canonical.ts
CHANGED
|
@@ -1,18 +1,88 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { writeJson } from "./json.js";
|
|
1
|
+
import { hash } from "fast-sha256";
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Canonical JSON, the one encoder behind every hashed value in Arc. Keys sort
|
|
5
|
+
* by UTF-16 code unit and scalars encode as `JSON.stringify` writes them. Only
|
|
6
|
+
* plain data encodes: non-finite numbers, `bigint`, functions, symbols,
|
|
7
|
+
* `undefined`, missing array items, non-plain objects and cycles throw.
|
|
8
|
+
*/
|
|
9
|
+
export function canonicalJson(value: unknown): string {
|
|
10
|
+
return write(value, "$", new WeakSet<object>());
|
|
7
11
|
}
|
|
8
12
|
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
/**
|
|
14
|
+
* The UDL document layout: canonical JSON with a two-space indent. Every
|
|
15
|
+
* stored `documentDigest` hashes these bytes.
|
|
16
|
+
*/
|
|
17
|
+
export function writeJson(value: unknown): string {
|
|
18
|
+
return write(value, "$", new WeakSet<object>(), "");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function write(
|
|
22
|
+
value: unknown,
|
|
23
|
+
path: string,
|
|
24
|
+
ancestors: WeakSet<object>,
|
|
25
|
+
indent?: string,
|
|
26
|
+
): string {
|
|
27
|
+
if (value === null) return "null";
|
|
28
|
+
switch (typeof value) {
|
|
29
|
+
case "boolean":
|
|
30
|
+
case "string":
|
|
31
|
+
return JSON.stringify(value);
|
|
32
|
+
case "number":
|
|
33
|
+
if (!Number.isFinite(value)) refuse("non finite numbers", path);
|
|
34
|
+
return JSON.stringify(value);
|
|
35
|
+
case "object":
|
|
36
|
+
break;
|
|
37
|
+
default:
|
|
38
|
+
refuse(typeof value, path);
|
|
39
|
+
}
|
|
40
|
+
const array = Array.isArray(value);
|
|
41
|
+
if (!array) {
|
|
42
|
+
const prototype = Object.getPrototypeOf(value);
|
|
43
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
44
|
+
refuse("objects other than plain ones", path);
|
|
45
|
+
if (Object.getOwnPropertySymbols(value).length > 0)
|
|
46
|
+
refuse("symbol keys", path);
|
|
47
|
+
}
|
|
48
|
+
if (ancestors.has(value)) refuse("cycles", path);
|
|
49
|
+
ancestors.add(value);
|
|
50
|
+
try {
|
|
51
|
+
const inner = indent === undefined ? undefined : `${indent} `;
|
|
52
|
+
const items: string[] = [];
|
|
53
|
+
if (array) {
|
|
54
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
55
|
+
if (!Object.hasOwn(value, index))
|
|
56
|
+
refuse("missing array items", `${path}[${index}]`);
|
|
57
|
+
items.push(write(value[index], `${path}[${index}]`, ancestors, inner));
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
const record = value as Record<string, unknown>;
|
|
61
|
+
for (const key of Object.keys(record).sort())
|
|
62
|
+
items.push(
|
|
63
|
+
`${JSON.stringify(key)}:${inner === undefined ? "" : " "}${write(record[key], `${path}.${key}`, ancestors, inner)}`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const [open, close] = array ? ["[", "]"] : ["{", "}"];
|
|
67
|
+
if (inner === undefined || items.length === 0)
|
|
68
|
+
return `${open}${items.join(",")}${close}`;
|
|
69
|
+
return `${open}\n${inner}${items.join(`,\n${inner}`)}\n${indent}${close}`;
|
|
70
|
+
} finally {
|
|
71
|
+
ancestors.delete(value);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function refuse(what: string, path: string): never {
|
|
76
|
+
throw new TypeError(`canonical JSON does not support ${what} at ${path}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** SHA-256 over canonical JSON, encoded as lowercase hexadecimal. */
|
|
80
|
+
export function digest(value: unknown): string {
|
|
81
|
+
return sha256(canonicalJson(value));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function sha256(text: string): string {
|
|
85
|
+
return Array.from(hash(new TextEncoder().encode(text)), (byte) =>
|
|
86
|
+
byte.toString(16).padStart(2, "0"),
|
|
87
|
+
).join("");
|
|
18
88
|
}
|
package/src/diagnostics.ts
CHANGED
|
@@ -11,6 +11,8 @@ export type UdlIssueCode =
|
|
|
11
11
|
| "party_kind_mismatch"
|
|
12
12
|
| "staff_role_unknown"
|
|
13
13
|
| "product_party_invalid"
|
|
14
|
+
| "udl_format_unknown"
|
|
15
|
+
| "udl_format_not_current"
|
|
14
16
|
| "UDL1003"
|
|
15
17
|
| "UDL1004"
|
|
16
18
|
| "UDL2001"
|
|
@@ -53,6 +55,10 @@ const fixes: Record<UdlIssueCode, string> = {
|
|
|
53
55
|
staff_role_unknown: "Use a registered staff permission role.",
|
|
54
56
|
product_party_invalid:
|
|
55
57
|
"Bind a business participant in the same tenant and environment.",
|
|
58
|
+
udl_format_unknown:
|
|
59
|
+
"Run the document on an engine that ships a reader for its format.",
|
|
60
|
+
udl_format_not_current:
|
|
61
|
+
"Recompile the HSX program, which emits the current UDL format.",
|
|
56
62
|
|
|
57
63
|
UDL1003: "Use the UDL 1 typed grammar.",
|
|
58
64
|
UDL1004: "Reduce the declared structure or expansion.",
|
package/src/formats.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type * as z from "zod";
|
|
2
|
+
import {
|
|
3
|
+
UDL_FORMAT_VERSION,
|
|
4
|
+
udlDocumentSchema,
|
|
5
|
+
udlFormat1DocumentSchema,
|
|
6
|
+
type UdlAnyFamily,
|
|
7
|
+
} from "./schema.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* How this engine reads one UDL format. A Build is bytecode and keeps the
|
|
11
|
+
* format it compiled to. The current format parses straight into the engine's
|
|
12
|
+
* document; an older one parses with its own schema, then `upgrade`, a pure
|
|
13
|
+
* and deterministic step, turns it into the next format.
|
|
14
|
+
*/
|
|
15
|
+
export interface UdlFormatReader {
|
|
16
|
+
readonly schema: z.ZodType;
|
|
17
|
+
readonly upgrade?: (document: unknown) => unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* One reader per format a stored Build uses, keyed by `document.udl`. A format
|
|
22
|
+
* bump adds its schema here and gives the previous format an upgrade step. A
|
|
23
|
+
* new Build admits only `UDL_FORMAT_VERSION` (`validateNewBuildUdl`).
|
|
24
|
+
*/
|
|
25
|
+
export const udlFormatReaders = new Map<number, UdlFormatReader>([
|
|
26
|
+
[1, { schema: udlFormat1DocumentSchema }],
|
|
27
|
+
[UDL_FORMAT_VERSION, { schema: udlDocumentSchema }],
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Whether two families name the same std declaration. Two format 2 families
|
|
32
|
+
* compare digests. When either is format 1, module and export path decide,
|
|
33
|
+
* which is what a stored revision decided: every stored revision is 1.
|
|
34
|
+
*/
|
|
35
|
+
export function sameFamily(
|
|
36
|
+
a: UdlAnyFamily | undefined,
|
|
37
|
+
b: UdlAnyFamily,
|
|
38
|
+
): boolean {
|
|
39
|
+
return (
|
|
40
|
+
a !== undefined &&
|
|
41
|
+
a.module === b.module &&
|
|
42
|
+
a.exportPath === b.exportPath &&
|
|
43
|
+
(!("digest" in a) || !("digest" in b) || a.digest === b.digest)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The format a stored document declares, when this engine can read it. */
|
|
48
|
+
export function readableUdlFormat(document: unknown): number | undefined {
|
|
49
|
+
const format =
|
|
50
|
+
document !== null && typeof document === "object"
|
|
51
|
+
? (document as { udl?: unknown }).udl
|
|
52
|
+
: undefined;
|
|
53
|
+
return typeof format === "number" && udlFormatReaders.has(format)
|
|
54
|
+
? format
|
|
55
|
+
: undefined;
|
|
56
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
export * from "./schema.js";
|
|
2
2
|
export { sarCurrency, sarMinorUnitExponent, currencySchema } from "./money.js";
|
|
3
|
-
export { serializeUdl,
|
|
3
|
+
export { serializeUdl, documentDigest } from "./serialize.js";
|
|
4
|
+
export { canonicalJson, digest } from "./canonical.js";
|
|
5
|
+
export {
|
|
6
|
+
udlFormatReaders,
|
|
7
|
+
readableUdlFormat,
|
|
8
|
+
sameFamily,
|
|
9
|
+
type UdlFormatReader,
|
|
10
|
+
} from "./formats.js";
|
|
4
11
|
export {
|
|
5
12
|
validateUdl,
|
|
13
|
+
validateNewBuildUdl,
|
|
6
14
|
RESERVED_OBJECT_NAMES,
|
|
7
15
|
assertValidUdl,
|
|
8
16
|
resolveField,
|
package/src/object-contract.ts
CHANGED
|
@@ -29,7 +29,10 @@ export interface ObjectKindDiscovery {
|
|
|
29
29
|
title: string;
|
|
30
30
|
instrument: string;
|
|
31
31
|
parentAttachment: string | null;
|
|
32
|
-
block:
|
|
32
|
+
block:
|
|
33
|
+
| { key: string; recordPath: string; digest: string }
|
|
34
|
+
| { key: string; recordPath: string; headerDigest: string }
|
|
35
|
+
| null;
|
|
33
36
|
}[];
|
|
34
37
|
productBuildId: string;
|
|
35
38
|
digest: string;
|
package/src/schema.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
import { currencySchema } from "./money.js";
|
|
3
3
|
import { reportDefinitionSchema } from "./reporting.js";
|
|
4
|
-
import {
|
|
4
|
+
import { canonicalJson } from "./canonical.js";
|
|
5
5
|
|
|
6
|
-
export const UDL_FORMAT_VERSION =
|
|
6
|
+
export const UDL_FORMAT_VERSION = 2 as const;
|
|
7
7
|
/** Counts the root and every nested invocation, including selected and ranged children. */
|
|
8
8
|
export const MAX_ACTION_EXPANSION = 8192;
|
|
9
9
|
const name = z
|
|
@@ -33,12 +33,23 @@ export const udlPartySchema = z.strictObject({
|
|
|
33
33
|
role: name.optional(),
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
+
/** Format 2 names a std declaration by the digest of its normal form. */
|
|
36
37
|
export const udlFamilySchema = z.strictObject({
|
|
38
|
+
module: name,
|
|
39
|
+
exportPath: path,
|
|
40
|
+
digest: z.string().regex(/^[a-f0-9]{64}$/),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/** Format 1 named it by a hand-written revision. Stored Builds keep it. */
|
|
44
|
+
export const udlFormat1FamilySchema = z.strictObject({
|
|
37
45
|
module: name,
|
|
38
46
|
exportPath: path,
|
|
39
47
|
revision: integer.positive(),
|
|
40
48
|
});
|
|
41
49
|
|
|
50
|
+
// One tree serves both formats; each document schema checks its families.
|
|
51
|
+
const family = z.union([udlFamilySchema, udlFormat1FamilySchema]);
|
|
52
|
+
|
|
42
53
|
const fieldBase = {
|
|
43
54
|
name,
|
|
44
55
|
label: text.optional(),
|
|
@@ -51,7 +62,7 @@ const referenceField = z.strictObject({
|
|
|
51
62
|
...fieldBase,
|
|
52
63
|
type: z.literal("ref"),
|
|
53
64
|
targetKind: z.enum(["instrument", "object"]),
|
|
54
|
-
targetFamily:
|
|
65
|
+
targetFamily: family.optional(),
|
|
55
66
|
target: z.union([name, z.array(name).min(1).max(16)]),
|
|
56
67
|
});
|
|
57
68
|
|
|
@@ -60,7 +71,7 @@ const listField = z.strictObject({
|
|
|
60
71
|
type: z.literal("list"),
|
|
61
72
|
item: z.enum(["money", "date", "text", "integer", "ref"]),
|
|
62
73
|
targetKind: z.enum(["instrument", "object"]).optional(),
|
|
63
|
-
targetFamily:
|
|
74
|
+
targetFamily: family.optional(),
|
|
64
75
|
target: name.optional(),
|
|
65
76
|
maxItems: integer.min(1).max(366),
|
|
66
77
|
});
|
|
@@ -254,7 +265,7 @@ export const udlObjectKindSchema = z.strictObject({
|
|
|
254
265
|
const values = z.array(value).min(1).max(256);
|
|
255
266
|
|
|
256
267
|
const selection = z.strictObject({
|
|
257
|
-
family:
|
|
268
|
+
family: family.optional(),
|
|
258
269
|
instrument: z.union([instrumentId, z.array(instrumentId).min(1).max(16)]),
|
|
259
270
|
reference: name,
|
|
260
271
|
anchor: path,
|
|
@@ -551,7 +562,7 @@ export const udlInstrumentSchema = z.strictObject({
|
|
|
551
562
|
scope: z.literal("product").optional(),
|
|
552
563
|
reports: z.array(reportDefinitionSchema).max(16).optional(),
|
|
553
564
|
revisioned: z.literal(true).optional(),
|
|
554
|
-
family:
|
|
565
|
+
family: family.optional(),
|
|
555
566
|
id: instrumentId,
|
|
556
567
|
subject: objectKindId.optional(),
|
|
557
568
|
// The authored instrument an attachment instantiates, when its scoped id
|
|
@@ -570,8 +581,7 @@ export const udlInstrumentSchema = z.strictObject({
|
|
|
570
581
|
.max(16)
|
|
571
582
|
.optional(),
|
|
572
583
|
});
|
|
573
|
-
|
|
574
|
-
udl: z.literal(UDL_FORMAT_VERSION),
|
|
584
|
+
const documentShape = {
|
|
575
585
|
version: integer.positive(),
|
|
576
586
|
product: name,
|
|
577
587
|
title: text,
|
|
@@ -579,10 +589,60 @@ export const udlDocumentSchema = z.strictObject({
|
|
|
579
589
|
parties: z.record(name, udlPartySchema),
|
|
580
590
|
objects: z.array(udlObjectKindSchema).max(256),
|
|
581
591
|
instruments: z.array(udlInstrumentSchema).max(256),
|
|
582
|
-
}
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
/** Every family a document names: instruments, reference fields, selections. */
|
|
595
|
+
function families(instruments: unknown[]): [PropertyKey[], object][] {
|
|
596
|
+
const found: [PropertyKey[], object][] = [];
|
|
597
|
+
const visit = (value: unknown, path: PropertyKey[]): void => {
|
|
598
|
+
if (!value || typeof value !== "object") return;
|
|
599
|
+
for (const [key, child] of Object.entries(value)) {
|
|
600
|
+
// An evidence `family` is a name, not a std family.
|
|
601
|
+
if (
|
|
602
|
+
(key === "family" || key === "targetFamily") &&
|
|
603
|
+
child &&
|
|
604
|
+
typeof child === "object"
|
|
605
|
+
)
|
|
606
|
+
found.push([[...path, key], child]);
|
|
607
|
+
else if (key !== "examples")
|
|
608
|
+
visit(child, [...path, Array.isArray(value) ? Number(key) : key]);
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
visit(instruments, ["instruments"]);
|
|
612
|
+
return found;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const familiesIn =
|
|
616
|
+
(key: "digest" | "revision") =>
|
|
617
|
+
(document: { instruments: unknown[] }, context: z.RefinementCtx) => {
|
|
618
|
+
for (const [path, item] of families(document.instruments))
|
|
619
|
+
if (!(key in item))
|
|
620
|
+
context.addIssue({
|
|
621
|
+
code: "custom",
|
|
622
|
+
path,
|
|
623
|
+
message: `a format ${key === "digest" ? 2 : 1} family names its ${key}`,
|
|
624
|
+
});
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
export const udlDocumentSchema = z
|
|
628
|
+
.strictObject({ udl: z.literal(UDL_FORMAT_VERSION), ...documentShape })
|
|
629
|
+
.superRefine(familiesIn("digest"));
|
|
630
|
+
|
|
631
|
+
/** The format 1 reader: families carry the revision they were stored with. */
|
|
632
|
+
export const udlFormat1DocumentSchema = z
|
|
633
|
+
.strictObject({ udl: z.literal(1), ...documentShape })
|
|
634
|
+
.superRefine(familiesIn("revision"));
|
|
635
|
+
|
|
636
|
+
/** Any document a stored Build may hold, in the format it compiled to. */
|
|
637
|
+
export const udlStoredDocumentSchema = z.discriminatedUnion("udl", [
|
|
638
|
+
udlDocumentSchema,
|
|
639
|
+
udlFormat1DocumentSchema,
|
|
640
|
+
]);
|
|
583
641
|
|
|
584
642
|
export type UdlFamily = z.infer<typeof udlFamilySchema>;
|
|
585
|
-
export type
|
|
643
|
+
export type UdlFormat1Family = z.infer<typeof udlFormat1FamilySchema>;
|
|
644
|
+
export type UdlAnyFamily = UdlFamily | UdlFormat1Family;
|
|
645
|
+
export type UdlDocument = z.infer<typeof udlStoredDocumentSchema>;
|
|
586
646
|
export type UdlInstrument = z.infer<typeof udlInstrumentSchema>;
|
|
587
647
|
export type UdlField = z.infer<typeof udlFieldSchema>;
|
|
588
648
|
export type UdlValue = z.infer<typeof udlValueSchema>;
|
|
@@ -606,7 +666,7 @@ export function sameObjectField(
|
|
|
606
666
|
right: UdlObjectField,
|
|
607
667
|
): boolean {
|
|
608
668
|
const signature = (field: UdlObjectField) =>
|
|
609
|
-
|
|
669
|
+
canonicalJson(
|
|
610
670
|
Object.fromEntries(
|
|
611
671
|
Object.entries(field).filter(
|
|
612
672
|
([key, value]) =>
|
package/src/serialize.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readUdl, UdlError } from "./validation.js";
|
|
2
|
+
import { sha256, writeJson } from "./canonical.js";
|
|
3
|
+
|
|
4
|
+
/** The stored bytes of a UDL document: its own format's layout, with a trailing newline. */
|
|
5
|
+
export function serializeUdl(value: unknown): string {
|
|
6
|
+
const read = readUdl(value);
|
|
7
|
+
if (!read.ok) throw new UdlError(read.issues);
|
|
8
|
+
return `${writeJson(read.stored)}\n`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** SHA-256 over the stored document bytes, the `documentDigest` every Build carries. */
|
|
12
|
+
export function documentDigest(value: unknown): string {
|
|
13
|
+
return sha256(serializeUdl(value));
|
|
14
|
+
}
|
package/src/validation.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
UDL_FORMAT_VERSION,
|
|
3
3
|
subjectPartyRoles,
|
|
4
4
|
sameObjectField,
|
|
5
5
|
type UdlDocument,
|
|
6
|
-
type UdlFamily,
|
|
7
6
|
type UdlField,
|
|
8
7
|
type UdlInstrument,
|
|
9
8
|
type UdlValue,
|
|
@@ -16,6 +15,7 @@ import {
|
|
|
16
15
|
type UdlObjectKind,
|
|
17
16
|
} from "./schema.js";
|
|
18
17
|
import { issue, type UdlIssue } from "./diagnostics.js";
|
|
18
|
+
import { readableUdlFormat, sameFamily, udlFormatReaders } from "./formats.js";
|
|
19
19
|
import { validateReportDefinition } from "./reporting-validation.js";
|
|
20
20
|
import { analyzeInstrumentFinance } from "./finance.js";
|
|
21
21
|
import { UDL_LIMITS } from "./limits.js";
|
|
@@ -140,11 +140,6 @@ function bounded(value: unknown): boolean {
|
|
|
140
140
|
|
|
141
141
|
const overlap = (a: string | string[], b: string | string[]) =>
|
|
142
142
|
targetIds(a).some((id) => targetIds(b).includes(id));
|
|
143
|
-
const sameFamily = (actual: UdlFamily | undefined, expected: UdlFamily) =>
|
|
144
|
-
actual !== undefined &&
|
|
145
|
-
actual.module === expected.module &&
|
|
146
|
-
actual.exportPath === expected.exportPath &&
|
|
147
|
-
actual.revision === expected.revision;
|
|
148
143
|
|
|
149
144
|
/** Union paths retain only fields with compatible types on every member. */
|
|
150
145
|
function commonField(
|
|
@@ -449,6 +444,44 @@ function resolvePath(
|
|
|
449
444
|
}
|
|
450
445
|
|
|
451
446
|
export function validateUdl(value: unknown): UdlValidationResult {
|
|
447
|
+
const read = readUdl(value);
|
|
448
|
+
return read.ok ? { ok: true, value: read.document } : read;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Admit a document for a new Build. A new Build is the current format; the
|
|
453
|
+
* older formats in `udlFormatReaders` are readable only as stored Builds,
|
|
454
|
+
* through `validateUdl`.
|
|
455
|
+
*/
|
|
456
|
+
export function validateNewBuildUdl(value: unknown): UdlValidationResult {
|
|
457
|
+
const format =
|
|
458
|
+
value !== null && typeof value === "object"
|
|
459
|
+
? (value as { udl?: unknown }).udl
|
|
460
|
+
: undefined;
|
|
461
|
+
if (format !== UDL_FORMAT_VERSION)
|
|
462
|
+
return {
|
|
463
|
+
ok: false,
|
|
464
|
+
issues: [
|
|
465
|
+
issue(
|
|
466
|
+
"udl_format_not_current",
|
|
467
|
+
"$.udl",
|
|
468
|
+
`a new Build is UDL format ${UDL_FORMAT_VERSION}; this document is format ${String(format)}`,
|
|
469
|
+
),
|
|
470
|
+
],
|
|
471
|
+
};
|
|
472
|
+
return validateUdl(value);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Read a document in the format it declares, then run the upgrade chain to
|
|
477
|
+
* the current format. `stored` is the parsed document in its own format, the
|
|
478
|
+
* one its digests cover; `document` is what the engine runs.
|
|
479
|
+
*/
|
|
480
|
+
export function readUdl(
|
|
481
|
+
value: unknown,
|
|
482
|
+
):
|
|
483
|
+
| { ok: true; stored: unknown; document: UdlDocument }
|
|
484
|
+
| { ok: false; issues: UdlIssue[] } {
|
|
452
485
|
if (!bounded(value))
|
|
453
486
|
return {
|
|
454
487
|
ok: false,
|
|
@@ -460,7 +493,29 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
460
493
|
),
|
|
461
494
|
],
|
|
462
495
|
};
|
|
463
|
-
|
|
496
|
+
let format = readableUdlFormat(value);
|
|
497
|
+
if (format === undefined)
|
|
498
|
+
return {
|
|
499
|
+
ok: false,
|
|
500
|
+
issues: [
|
|
501
|
+
issue(
|
|
502
|
+
"udl_format_unknown",
|
|
503
|
+
"$.udl",
|
|
504
|
+
`UDL format ${String((value as { udl?: unknown }).udl)} has no reader in this engine`,
|
|
505
|
+
),
|
|
506
|
+
],
|
|
507
|
+
};
|
|
508
|
+
let reader = udlFormatReaders.get(format)!;
|
|
509
|
+
let parsed = reader.schema.safeParse(value);
|
|
510
|
+
const stored = parsed.data;
|
|
511
|
+
while (parsed.success && reader.upgrade) {
|
|
512
|
+
const next = reader.upgrade(parsed.data);
|
|
513
|
+
format = readableUdlFormat(next);
|
|
514
|
+
if (format === undefined)
|
|
515
|
+
throw new Error("a UDL upgrade step produced a format with no reader");
|
|
516
|
+
reader = udlFormatReaders.get(format)!;
|
|
517
|
+
parsed = reader.schema.safeParse(next);
|
|
518
|
+
}
|
|
464
519
|
if (!parsed.success)
|
|
465
520
|
return {
|
|
466
521
|
ok: false,
|
|
@@ -475,7 +530,7 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
475
530
|
),
|
|
476
531
|
),
|
|
477
532
|
};
|
|
478
|
-
const document = parsed.data;
|
|
533
|
+
const document = parsed.data as UdlDocument;
|
|
479
534
|
const issues: UdlIssue[] = [];
|
|
480
535
|
const add = (
|
|
481
536
|
path: string,
|
|
@@ -1765,7 +1820,7 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
1765
1820
|
visited.add(key);
|
|
1766
1821
|
};
|
|
1767
1822
|
for (const key of calls.keys()) visit(key);
|
|
1768
|
-
return issues.length ? { ok: false, issues } : { ok: true,
|
|
1823
|
+
return issues.length ? { ok: false, issues } : { ok: true, stored, document };
|
|
1769
1824
|
}
|
|
1770
1825
|
export function assertValidUdl(value: unknown): UdlDocument {
|
|
1771
1826
|
const result = validateUdl(value);
|
package/dist/json.d.ts
DELETED
package/dist/json.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AAEA,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,MAAM,CA2B3D"}
|
package/dist/json.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
const INDENT = " ";
|
|
2
|
-
export function writeJson(value, depth = 0) {
|
|
3
|
-
if (value === null || typeof value !== "object") {
|
|
4
|
-
const encoded = JSON.stringify(value);
|
|
5
|
-
if (encoded === undefined) {
|
|
6
|
-
throw new TypeError("UDL canonical JSON received a non-JSON value");
|
|
7
|
-
}
|
|
8
|
-
return encoded;
|
|
9
|
-
}
|
|
10
|
-
const indent = INDENT.repeat(depth);
|
|
11
|
-
const childIndent = INDENT.repeat(depth + 1);
|
|
12
|
-
if (Array.isArray(value)) {
|
|
13
|
-
if (value.length === 0)
|
|
14
|
-
return "[]";
|
|
15
|
-
return `[\n${value
|
|
16
|
-
.map((item) => `${childIndent}${writeJson(item, depth + 1)}`)
|
|
17
|
-
.join(",\n")}\n${indent}]`;
|
|
18
|
-
}
|
|
19
|
-
const record = value;
|
|
20
|
-
const keys = Object.keys(record).sort(compareCodeUnits);
|
|
21
|
-
if (keys.length === 0)
|
|
22
|
-
return "{}";
|
|
23
|
-
return `{\n${keys
|
|
24
|
-
.map((key) => `${childIndent}${JSON.stringify(key)}: ${writeJson(record[key], depth + 1)}`)
|
|
25
|
-
.join(",\n")}\n${indent}}`;
|
|
26
|
-
}
|
|
27
|
-
function compareCodeUnits(left, right) {
|
|
28
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
29
|
-
}
|
|
30
|
-
//# sourceMappingURL=json.js.map
|
package/dist/json.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"json.js","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,GAAG,IAAI,CAAC;AAEpB,MAAM,UAAU,SAAS,CAAC,KAAc,EAAE,KAAK,GAAG,CAAC;IACjD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,IAAI,SAAS,CAAC,8CAA8C,CAAC,CAAC;QACtE,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IAC7C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACpC,OAAO,MAAM,KAAK;aACf,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,WAAW,GAAG,SAAS,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;aAC5D,IAAI,CAAC,KAAK,CAAC,KAAK,MAAM,GAAG,CAAC;IAC/B,CAAC;IAED,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACxD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,MAAM,IAAI;SACd,GAAG,CACF,CAAC,GAAG,EAAE,EAAE,CACN,GAAG,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,EAAE,CAC/E;SACA,IAAI,CAAC,KAAK,CAAC,KAAK,MAAM,GAAG,CAAC;AAC/B,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAY,EAAE,KAAa;IACnD,OAAO,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClD,CAAC"}
|
package/src/json.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
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
|
-
}
|