@acid-cat/catalog 0.10.0 → 0.10.2
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 +2 -0
- package/dist/canonical.d.ts +0 -2
- package/dist/canonical.js +17 -14
- package/dist/index.js +24 -12
- package/package.json +2 -2
package/README.md
CHANGED
package/dist/canonical.d.ts
CHANGED
|
@@ -4,7 +4,5 @@
|
|
|
4
4
|
*/
|
|
5
5
|
/** A catalog in canonical form, as text. */
|
|
6
6
|
export declare function canonicalText(catalog: unknown): string;
|
|
7
|
-
/** Put a catalog's members in canonical order. */
|
|
8
|
-
export declare function canonicalCatalog(catalog: unknown): unknown;
|
|
9
7
|
/** Order strings by Unicode code point, as Python, Go and Rust compare them. */
|
|
10
8
|
export declare function byCodePoint(left: string, right: string): number;
|
package/dist/canonical.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/canonical.ts
|
|
2
2
|
const isObject = (value) => value !== null && typeof value === "object";
|
|
3
3
|
const CATALOG_ORDER = [
|
|
4
4
|
"acidcat",
|
|
@@ -33,7 +33,10 @@ const EVENT_ORDER = [
|
|
|
33
33
|
function canonicalText(catalog) {
|
|
34
34
|
return `${write(canonicalCatalog(catalog), "")}\n`;
|
|
35
35
|
}
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Put a catalog's members in canonical order. Each object becomes a `Map`, not a plain object, because a plain object
|
|
38
|
+
* puts integer-like keys such as "9" and "10" first, in numeric order, whatever order they were added in.
|
|
39
|
+
*/
|
|
37
40
|
function canonicalCatalog(catalog) {
|
|
38
41
|
return ordered(catalog, CATALOG_ORDER, (key, value) => {
|
|
39
42
|
if (key === "targets") return byKey(value, (target) => ordered(target, TARGET_ORDER, sortedMember));
|
|
@@ -45,15 +48,15 @@ function canonicalCatalog(catalog) {
|
|
|
45
48
|
}
|
|
46
49
|
/** The known members in their order, then every other member sorted by key. A value that is not an object is sorted. */
|
|
47
50
|
function ordered(value, order, each) {
|
|
48
|
-
if (!isObject(value)) return sorted(value);
|
|
49
|
-
const known = order.filter((key) => key
|
|
51
|
+
if (!isObject(value) || Array.isArray(value)) return sorted(value);
|
|
52
|
+
const known = order.filter((key) => Object.hasOwn(value, key));
|
|
50
53
|
const others = Object.keys(value).filter((key) => !order.includes(key)).sort(byCodePoint);
|
|
51
|
-
return
|
|
54
|
+
return new Map([...known, ...others].map((key) => [key, each(key, value[key])]));
|
|
52
55
|
}
|
|
53
56
|
/** A map keyed by name or id: its keys sorted, each value shaped by `each`. A value that is not an object is sorted. */
|
|
54
57
|
function byKey(value, each) {
|
|
55
|
-
if (!isObject(value)) return sorted(value);
|
|
56
|
-
return
|
|
58
|
+
if (!isObject(value) || Array.isArray(value)) return sorted(value);
|
|
59
|
+
return new Map(Object.keys(value).sort(byCodePoint).map((key) => [key, each(value[key])]));
|
|
57
60
|
}
|
|
58
61
|
function sortedMember(_key, value) {
|
|
59
62
|
return sorted(value);
|
|
@@ -74,21 +77,21 @@ function byCodePoint(left, right) {
|
|
|
74
77
|
}
|
|
75
78
|
return a.length - b.length;
|
|
76
79
|
}
|
|
77
|
-
/** Write a JSON value with two-space indentation, one member or item per line. */
|
|
80
|
+
/** Write a JSON value with two-space indentation, one member or item per line. A `Map` keeps its order. */
|
|
78
81
|
function write(value, indent) {
|
|
79
82
|
if (Array.isArray(value)) {
|
|
80
83
|
if (value.length === 0) return "[]";
|
|
81
84
|
const inner = `${indent} `;
|
|
82
85
|
return `[\n${value.map((item) => inner + write(item, inner)).join(",\n")}\n${indent}]`;
|
|
83
86
|
}
|
|
84
|
-
if (
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
87
|
+
if (value instanceof Map) {
|
|
88
|
+
const entries = [...value];
|
|
89
|
+
if (entries.length === 0) return "{}";
|
|
87
90
|
const inner = `${indent} `;
|
|
88
|
-
return `{\n${
|
|
91
|
+
return `{\n${entries.map(([key, member]) => `${inner}${JSON.stringify(key)}: ${write(member, inner)}`).join(",\n")}\n${indent}}`;
|
|
89
92
|
}
|
|
93
|
+
if (isObject(value)) return write(sorted(value), indent);
|
|
90
94
|
if (typeof value === "number" && !Number.isFinite(value)) throw new TypeError("A catalog holds only finite numbers");
|
|
91
95
|
return JSON.stringify(value);
|
|
92
96
|
}
|
|
93
|
-
|
|
94
|
-
export { byCodePoint, canonicalCatalog, canonicalText };
|
|
97
|
+
export { byCodePoint, canonicalText };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { canonicalText } from "./canonical.js";
|
|
2
|
-
|
|
2
|
+
// src/index.ts
|
|
3
3
|
/**
|
|
4
4
|
* Write an AcidCat catalog in TypeScript, and render it to the JSON document the toolchain checks.
|
|
5
5
|
*
|
|
@@ -56,22 +56,25 @@ var Catalog = class {
|
|
|
56
56
|
}
|
|
57
57
|
for (const [name, schema] of this.#types) {
|
|
58
58
|
if (!TYPE_NAME.test(name)) problems.push(`the type name ${name} is malformed`);
|
|
59
|
+
if (!isJsonObject(schema)) problems.push(`the type ${name} is not a JSON object`);
|
|
59
60
|
problems.push(...this.#referenceProblems(schema).map((problem) => `the type ${name} ${problem}`));
|
|
60
61
|
}
|
|
61
62
|
for (const [id, capability] of this.#capabilities) {
|
|
62
|
-
problems.push(...this.#entryProblems(id, capability));
|
|
63
63
|
const schemas = [
|
|
64
|
-
capability.input,
|
|
65
|
-
capability.output,
|
|
66
|
-
capability.state,
|
|
67
|
-
...Object.
|
|
64
|
+
["input", capability.input],
|
|
65
|
+
["output", capability.output],
|
|
66
|
+
["state", capability.state],
|
|
67
|
+
...Object.entries(capability.failures ?? {}).map(([tag, schema]) => [`failure ${tag}`, schema])
|
|
68
68
|
];
|
|
69
|
-
|
|
69
|
+
problems.push(...schemaProblems(id, schemas));
|
|
70
|
+
problems.push(...this.#entryProblems(id, capability));
|
|
71
|
+
for (const [, schema] of schemas) if (schema !== void 0) problems.push(...this.#referenceProblems(schema).map((problem) => `${id} ${problem}`));
|
|
70
72
|
}
|
|
71
73
|
for (const [id, event] of this.#events) {
|
|
72
74
|
if (this.#capabilities.has(id)) problems.push(`${id} is both a capability and an event`);
|
|
75
|
+
problems.push(...schemaProblems(id, [["input", event.input], ["payload", event.payload]]));
|
|
73
76
|
problems.push(...this.#entryProblems(id, event));
|
|
74
|
-
if (event.payload
|
|
77
|
+
if (isJsonObject(event.payload) && this.#resolve(event.payload).type !== "object") problems.push(`${id} payload does not describe an object`);
|
|
75
78
|
for (const schema of [event.input, event.payload]) if (schema !== void 0) problems.push(...this.#referenceProblems(schema).map((problem) => `${id} ${problem}`));
|
|
76
79
|
}
|
|
77
80
|
return problems;
|
|
@@ -84,7 +87,7 @@ var Catalog = class {
|
|
|
84
87
|
const audience = entry.audience ?? [];
|
|
85
88
|
if (new Set(audience).size !== audience.length) problems.push(`${id} repeats a target in its audience`);
|
|
86
89
|
for (const target of audience) if (!this.#targets.has(target)) problems.push(`${id} names the undeclared target ${target}`);
|
|
87
|
-
if (entry.input
|
|
90
|
+
if (isJsonObject(entry.input) && this.#resolve(entry.input).type !== "object") problems.push(`${id} input does not describe an object`);
|
|
88
91
|
return problems;
|
|
89
92
|
}
|
|
90
93
|
/** The catalog as a JSON value. Throws when the catalog breaks a rule. */
|
|
@@ -124,12 +127,13 @@ var Catalog = class {
|
|
|
124
127
|
}
|
|
125
128
|
#resolve(schema) {
|
|
126
129
|
const name = typeof schema.$ref === "string" ? REFERENCE.exec(schema.$ref)?.[1] : void 0;
|
|
127
|
-
|
|
130
|
+
const resolved = name === void 0 ? schema : this.#types.get(name) ?? schema;
|
|
131
|
+
return isJsonObject(resolved) ? resolved : {};
|
|
128
132
|
}
|
|
129
133
|
#referenceProblems(schema) {
|
|
130
134
|
const problems = [];
|
|
131
135
|
visit(schema, (node) => {
|
|
132
|
-
if ("$defs"
|
|
136
|
+
if (Object.hasOwn(node, "$defs")) problems.push("carries its own $defs; declare shared types with type()");
|
|
133
137
|
if (node.$ref === void 0) return;
|
|
134
138
|
const name = typeof node.$ref === "string" ? REFERENCE.exec(node.$ref)?.[1] : void 0;
|
|
135
139
|
if (name === void 0 || !this.#types.has(name)) problems.push(`references ${typeof node.$ref === "string" ? node.$ref : JSON.stringify(node.$ref)}, which is not a type`);
|
|
@@ -137,11 +141,20 @@ var Catalog = class {
|
|
|
137
141
|
return problems;
|
|
138
142
|
}
|
|
139
143
|
};
|
|
144
|
+
/**
|
|
145
|
+
* The schemas of a capability or an event that are not JSON objects. The types of this library allow only objects,
|
|
146
|
+
* but a JavaScript caller can still give a boolean schema such as `true`.
|
|
147
|
+
*/
|
|
148
|
+
function schemaProblems(id, schemas) {
|
|
149
|
+
return schemas.filter(([, schema]) => schema !== void 0 && !isJsonObject(schema)).map(([member]) => `${id} ${member} is not a JSON object`);
|
|
150
|
+
}
|
|
140
151
|
function compact(value) {
|
|
141
152
|
return Object.fromEntries(Object.entries(value).filter(([, member]) => member !== void 0));
|
|
142
153
|
}
|
|
143
154
|
/** Tell if a value is an object that is not `null`. Each of its members is read as `unknown`. */
|
|
144
155
|
const isRecord = (value) => value !== null && typeof value === "object";
|
|
156
|
+
/** Tell if a value is a JSON object: an object that is not `null` and not an array. */
|
|
157
|
+
const isJsonObject = (value) => isRecord(value) && !Array.isArray(value);
|
|
145
158
|
function visit(value, onObject) {
|
|
146
159
|
if (Array.isArray(value)) value.forEach((item) => visit(item, onObject));
|
|
147
160
|
else if (isRecord(value)) {
|
|
@@ -149,5 +162,4 @@ function visit(value, onObject) {
|
|
|
149
162
|
Object.values(value).forEach((item) => visit(item, onObject));
|
|
150
163
|
}
|
|
151
164
|
}
|
|
152
|
-
//#endregion
|
|
153
165
|
export { Catalog, canonicalText };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acid-cat/catalog",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Write an AcidCat catalog in TypeScript and render it in canonical form.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"acidcat",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"access": "public"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@types/node": "26.6.
|
|
34
|
+
"@types/node": "26.6.6",
|
|
35
35
|
"typescript": "7.0.2"
|
|
36
36
|
}
|
|
37
37
|
}
|