@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 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: "Living-room air conditioner" });
15
- catalog.target("cli", { bindings: ["cli"] });
16
- const offline = catalog.type("Offline", { type: "object" });
17
- catalog.capability("aircon.power.set", {
18
- description: "Turn the unit on or off.",
19
- audience: ["cli"],
20
- input: { type: "object", required: ["on"], properties: { on: { type: "boolean" } } },
21
- failures: { Offline: offline },
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/aircon.ts`](examples/aircon.ts). Test with `moon run catalog-typescript:test`.
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`.
@@ -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: JsonObject): JsonObject;
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
- const object = value;
46
- return Object.fromEntries(Object.keys(object).sort(byCodePoint).map((key) => [key, each(object[key])]));
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 !== null && typeof value === "object") return byKey(value, sorted);
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 = [...left];
60
- const b = [...right];
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 difference = a[i].codePointAt(0) - b[i].codePointAt(0);
63
- if (difference !== 0) return difference;
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 !== null && typeof value === "object") {
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 ${String(node.$ref)}, which is not a type`);
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 !== null && typeof value === "object") {
122
+ else if (isRecord(value)) {
121
123
  onObject(value);
122
124
  Object.values(value).forEach((item) => visit(item, onObject));
123
125
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acid-cat/catalog",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Write an AcidCat catalog in TypeScript and render it in canonical form.",
5
5
  "keywords": [
6
6
  "acidcat",