@acid-cat/catalog 0.4.0 → 0.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/README.md +11 -9
- package/dist/canonical.d.ts +1 -8
- package/dist/canonical.js +13 -11
- package/dist/index.js +4 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,17 +11,19 @@ pnpm add -D @acid-cat/catalog
|
|
|
11
11
|
```ts
|
|
12
12
|
import { Catalog } from "@acid-cat/catalog";
|
|
13
13
|
|
|
14
|
-
const catalog = new Catalog({ title: "
|
|
15
|
-
catalog.target("
|
|
16
|
-
const
|
|
17
|
-
catalog.capability("
|
|
18
|
-
description: "
|
|
19
|
-
audience: ["
|
|
20
|
-
input: { type: "object", required: ["
|
|
21
|
-
failures: {
|
|
14
|
+
const catalog = new Catalog({ title: "Neighbourhood lending library" });
|
|
15
|
+
catalog.target("desk", { bindings: ["cli"] });
|
|
16
|
+
const notFound = catalog.type("NotFound", { type: "object" });
|
|
17
|
+
catalog.capability("library.loan.return", {
|
|
18
|
+
description: "Take a book back from a member.",
|
|
19
|
+
audience: ["desk"],
|
|
20
|
+
input: { type: "object", required: ["loan"], properties: { loan: { type: "string" } } },
|
|
21
|
+
failures: { NotFound: notFound },
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
await writeFile("acidcat.catalog.json", catalog.render());
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
`validate()` lists the rules a catalog breaks, and `render()` refuses to render one that breaks any. The full example is [`examples/
|
|
27
|
+
`validate()` lists the rules a catalog breaks, and `render()` refuses to render one that breaks any. The full example is [`examples/library.ts`](examples/library.ts), a lending library.
|
|
28
|
+
|
|
29
|
+
The tests compare the output of the library with files in this directory: the lending-library catalog [`examples/library.acidcat.json`](examples/library.acidcat.json), and a catalog of values that languages write differently, [`test/fixtures/edge.acidcat.json`](test/fixtures/edge.acidcat.json). A test also compares [`examples/aircon.ts`](examples/aircon.ts) with the air-conditioner catalog of the repository. Each committed catalog file is in [canonical form](../../../spec/catalog.md#canonical-form), and the root `moon run check` checks this. Thus all catalog libraries agree on the canonical form, and they do not share a test file. Test with `moon run catalog-typescript:test`.
|
package/dist/canonical.d.ts
CHANGED
|
@@ -2,16 +2,9 @@
|
|
|
2
2
|
* The canonical form of a catalog (spec/catalog.md#canonical-form): the one way to write a catalog as text, so that
|
|
3
3
|
* every tool and every catalog library produces the same bytes for the same catalog.
|
|
4
4
|
*/
|
|
5
|
-
type Json = null | boolean | number | string | readonly Json[] | {
|
|
6
|
-
readonly [key: string]: Json;
|
|
7
|
-
};
|
|
8
|
-
type JsonObject = {
|
|
9
|
-
readonly [key: string]: Json;
|
|
10
|
-
};
|
|
11
5
|
/** A catalog in canonical form, as text. */
|
|
12
6
|
export declare function canonicalText(catalog: unknown): string;
|
|
13
7
|
/** Put a catalog's members in canonical order. */
|
|
14
|
-
export declare function canonicalCatalog(catalog:
|
|
8
|
+
export declare function canonicalCatalog(catalog: unknown): unknown;
|
|
15
9
|
/** Order strings by Unicode code point, as Python, Go and Rust compare them. */
|
|
16
10
|
export declare function byCodePoint(left: string, right: string): number;
|
|
17
|
-
export {};
|
package/dist/canonical.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
//#region src/canonical.ts
|
|
2
|
+
const isObject = (value) => value !== null && typeof value === "object";
|
|
2
3
|
const CATALOG_ORDER = [
|
|
3
4
|
"acidcat",
|
|
4
5
|
"title",
|
|
@@ -34,33 +35,34 @@ function canonicalCatalog(catalog) {
|
|
|
34
35
|
return sortedMember(key, value);
|
|
35
36
|
});
|
|
36
37
|
}
|
|
37
|
-
/** The known members in their order, then every other member sorted by key. */
|
|
38
|
+
/** The known members in their order, then every other member sorted by key. A value that is not an object is sorted. */
|
|
38
39
|
function ordered(value, order, each) {
|
|
40
|
+
if (!isObject(value)) return sorted(value);
|
|
39
41
|
const known = order.filter((key) => key in value);
|
|
40
42
|
const others = Object.keys(value).filter((key) => !order.includes(key)).sort(byCodePoint);
|
|
41
43
|
return Object.fromEntries([...known, ...others].map((key) => [key, each(key, value[key])]));
|
|
42
44
|
}
|
|
43
|
-
/** A map keyed by name or id: its keys sorted, each value shaped by `each`. */
|
|
45
|
+
/** A map keyed by name or id: its keys sorted, each value shaped by `each`. A value that is not an object is sorted. */
|
|
44
46
|
function byKey(value, each) {
|
|
45
|
-
|
|
46
|
-
return Object.fromEntries(Object.keys(
|
|
47
|
+
if (!isObject(value)) return sorted(value);
|
|
48
|
+
return Object.fromEntries(Object.keys(value).sort(byCodePoint).map((key) => [key, each(value[key])]));
|
|
47
49
|
}
|
|
48
50
|
function sortedMember(_key, value) {
|
|
49
51
|
return sorted(value);
|
|
50
52
|
}
|
|
51
53
|
/** Every object's keys sorted, at every depth. Arrays keep their order. */
|
|
52
54
|
function sorted(value) {
|
|
53
|
-
if (Array.isArray(value)) return value.map(sorted);
|
|
54
|
-
if (value
|
|
55
|
+
if (Array.isArray(value)) return value.map((item) => sorted(item));
|
|
56
|
+
if (isObject(value)) return byKey(value, sorted);
|
|
55
57
|
return value;
|
|
56
58
|
}
|
|
57
59
|
/** Order strings by Unicode code point, as Python, Go and Rust compare them. */
|
|
58
60
|
function byCodePoint(left, right) {
|
|
59
|
-
const a =
|
|
60
|
-
const b =
|
|
61
|
+
const a = Array.from(left, (character) => character.codePointAt(0));
|
|
62
|
+
const b = Array.from(right, (character) => character.codePointAt(0));
|
|
61
63
|
for (let i = 0; i < Math.min(a.length, b.length); i += 1) {
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
64
|
+
const [first, second] = [a[i], b[i]];
|
|
65
|
+
if (first !== void 0 && second !== void 0 && first !== second) return first - second;
|
|
64
66
|
}
|
|
65
67
|
return a.length - b.length;
|
|
66
68
|
}
|
|
@@ -71,7 +73,7 @@ function write(value, indent) {
|
|
|
71
73
|
const inner = `${indent} `;
|
|
72
74
|
return `[\n${value.map((item) => inner + write(item, inner)).join(",\n")}\n${indent}]`;
|
|
73
75
|
}
|
|
74
|
-
if (value
|
|
76
|
+
if (isObject(value)) {
|
|
75
77
|
const keys = Object.keys(value);
|
|
76
78
|
if (keys.length === 0) return "{}";
|
|
77
79
|
const inner = `${indent} `;
|
package/dist/index.js
CHANGED
|
@@ -107,7 +107,7 @@ var Catalog = class {
|
|
|
107
107
|
if ("$defs" in node) problems.push("carries its own $defs; declare shared types with type()");
|
|
108
108
|
if (node.$ref === void 0) return;
|
|
109
109
|
const name = typeof node.$ref === "string" ? REFERENCE.exec(node.$ref)?.[1] : void 0;
|
|
110
|
-
if (name === void 0 || !this.#types.has(name)) problems.push(`references ${
|
|
110
|
+
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`);
|
|
111
111
|
});
|
|
112
112
|
return problems;
|
|
113
113
|
}
|
|
@@ -115,9 +115,11 @@ var Catalog = class {
|
|
|
115
115
|
function compact(value) {
|
|
116
116
|
return Object.fromEntries(Object.entries(value).filter(([, member]) => member !== void 0));
|
|
117
117
|
}
|
|
118
|
+
/** Tell if a value is an object that is not `null`. Each of its members is read as `unknown`. */
|
|
119
|
+
const isRecord = (value) => value !== null && typeof value === "object";
|
|
118
120
|
function visit(value, onObject) {
|
|
119
121
|
if (Array.isArray(value)) value.forEach((item) => visit(item, onObject));
|
|
120
|
-
else if (value
|
|
122
|
+
else if (isRecord(value)) {
|
|
121
123
|
onObject(value);
|
|
122
124
|
Object.values(value).forEach((item) => visit(item, onObject));
|
|
123
125
|
}
|