@procedures/json 0.0.0-stage → 0.2.3

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 (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +104 -2
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/diff.d.ts +18 -0
  5. package/dist/diff.d.ts.map +1 -0
  6. package/dist/diff.js +66 -0
  7. package/dist/diff.js.map +1 -0
  8. package/dist/index.d.ts +13 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +15 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/merge.d.ts +14 -0
  13. package/dist/merge.d.ts.map +1 -0
  14. package/dist/merge.js +34 -0
  15. package/dist/merge.js.map +1 -0
  16. package/dist/module.d.ts +21 -0
  17. package/dist/module.d.ts.map +1 -0
  18. package/dist/module.js +192 -0
  19. package/dist/module.js.map +1 -0
  20. package/dist/patch.d.ts +23 -0
  21. package/dist/patch.d.ts.map +1 -0
  22. package/dist/patch.js +137 -0
  23. package/dist/patch.js.map +1 -0
  24. package/dist/path.d.ts +38 -0
  25. package/dist/path.d.ts.map +1 -0
  26. package/dist/path.js +157 -0
  27. package/dist/path.js.map +1 -0
  28. package/dist/pointer.d.ts +22 -0
  29. package/dist/pointer.d.ts.map +1 -0
  30. package/dist/pointer.js +70 -0
  31. package/dist/pointer.js.map +1 -0
  32. package/dist/procedures/json/diff.d.ts +12 -0
  33. package/dist/procedures/json/diff.d.ts.map +1 -0
  34. package/dist/procedures/json/diff.js +17 -0
  35. package/dist/procedures/json/diff.js.map +1 -0
  36. package/dist/procedures/json/index.d.ts +6 -0
  37. package/dist/procedures/json/index.d.ts.map +1 -0
  38. package/dist/procedures/json/index.js +6 -0
  39. package/dist/procedures/json/index.js.map +1 -0
  40. package/dist/procedures/json/merge.d.ts +12 -0
  41. package/dist/procedures/json/merge.d.ts.map +1 -0
  42. package/dist/procedures/json/merge.js +17 -0
  43. package/dist/procedures/json/merge.js.map +1 -0
  44. package/dist/procedures/json/patch.d.ts +12 -0
  45. package/dist/procedures/json/patch.d.ts.map +1 -0
  46. package/dist/procedures/json/patch.js +20 -0
  47. package/dist/procedures/json/patch.js.map +1 -0
  48. package/dist/procedures/json/query.d.ts +12 -0
  49. package/dist/procedures/json/query.d.ts.map +1 -0
  50. package/dist/procedures/json/query.js +28 -0
  51. package/dist/procedures/json/query.js.map +1 -0
  52. package/dist/procedures/json/validate.d.ts +12 -0
  53. package/dist/procedures/json/validate.d.ts.map +1 -0
  54. package/dist/procedures/json/validate.js +18 -0
  55. package/dist/procedures/json/validate.js.map +1 -0
  56. package/dist/procedures/yaml/index.d.ts +3 -0
  57. package/dist/procedures/yaml/index.d.ts.map +1 -0
  58. package/dist/procedures/yaml/index.js +3 -0
  59. package/dist/procedures/yaml/index.js.map +1 -0
  60. package/dist/procedures/yaml/parse.d.ts +12 -0
  61. package/dist/procedures/yaml/parse.d.ts.map +1 -0
  62. package/dist/procedures/yaml/parse.js +14 -0
  63. package/dist/procedures/yaml/parse.js.map +1 -0
  64. package/dist/procedures/yaml/stringify.d.ts +11 -0
  65. package/dist/procedures/yaml/stringify.d.ts.map +1 -0
  66. package/dist/procedures/yaml/stringify.js +15 -0
  67. package/dist/procedures/yaml/stringify.js.map +1 -0
  68. package/dist/register.d.ts +6 -0
  69. package/dist/register.d.ts.map +1 -0
  70. package/dist/register.js +8 -0
  71. package/dist/register.js.map +1 -0
  72. package/dist/schema.d.ts +38 -0
  73. package/dist/schema.d.ts.map +1 -0
  74. package/dist/schema.js +92 -0
  75. package/dist/schema.js.map +1 -0
  76. package/dist/types.d.ts +90 -0
  77. package/dist/types.d.ts.map +1 -0
  78. package/dist/types.js +8 -0
  79. package/dist/types.js.map +1 -0
  80. package/dist/value.d.ts +61 -0
  81. package/dist/value.d.ts.map +1 -0
  82. package/dist/value.js +124 -0
  83. package/dist/value.js.map +1 -0
  84. package/dist/yaml.d.ts +35 -0
  85. package/dist/yaml.d.ts.map +1 -0
  86. package/dist/yaml.js +111 -0
  87. package/dist/yaml.js.map +1 -0
  88. package/package.json +81 -3
  89. package/src/diff.ts +58 -0
  90. package/src/index.ts +14 -0
  91. package/src/merge.ts +34 -0
  92. package/src/module.ts +236 -0
  93. package/src/patch.ts +132 -0
  94. package/src/path.ts +155 -0
  95. package/src/pointer.ts +70 -0
  96. package/src/procedures/json/diff.ts +19 -0
  97. package/src/procedures/json/index.ts +5 -0
  98. package/src/procedures/json/merge.ts +19 -0
  99. package/src/procedures/json/patch.ts +21 -0
  100. package/src/procedures/json/query.ts +29 -0
  101. package/src/procedures/json/validate.ts +20 -0
  102. package/src/procedures/yaml/index.ts +2 -0
  103. package/src/procedures/yaml/parse.ts +16 -0
  104. package/src/procedures/yaml/stringify.ts +17 -0
  105. package/src/register.ts +10 -0
  106. package/src/schema.ts +104 -0
  107. package/src/types.ts +127 -0
  108. package/src/value.ts +143 -0
  109. package/src/yaml.ts +128 -0
package/dist/yaml.js ADDED
@@ -0,0 +1,111 @@
1
+ /**
2
+ * YAML to JSON and back, with the `yaml` package (YAML 1.2, the core schema).
3
+ *
4
+ * - The parse gives only JSON values. A value that JSON does not have (`.inf`, `.nan`) is an
5
+ * error. The explicit YAML 1.1 tags (`!!binary`, `!!timestamp`, `!!set`) give their text.
6
+ * - A key of a map that is a number, a boolean or null becomes its text. A key that is a map or a
7
+ * list is an error.
8
+ * - The parse refuses more than 100 aliases (a resource attack) and a duplicate key.
9
+ * - Each object of the result has own data properties only, so a key `__proto__` stays data.
10
+ * - The text of a value is YAML 1.2 that a YAML 1.1 reader reads as the same value.
11
+ */
12
+ import { Document, parseAllDocuments, parseDocument, Scalar, Schema, visit } from "yaml";
13
+ import { formatPointer, JsonError, put } from "./value.js";
14
+ // The log level "error" keeps the error of a second document, and prints nothing
15
+ const PARSE_OPTIONS = { version: "1.2", schema: "core", resolveKnownTags: false, uniqueKeys: true, logLevel: "error", prettyErrors: true };
16
+ /** The byte order mark. */
17
+ const BOM = String.fromCharCode(0xfeff);
18
+ /** The JSON value of a value of the parse. */
19
+ function toJson(value, tokens) {
20
+ if (value === null || typeof value === "string" || typeof value === "boolean")
21
+ return value;
22
+ if (typeof value === "number") {
23
+ if (!Number.isFinite(value)) {
24
+ throw new JsonError("INVALID_YAML", `yaml.parse: the value at "${formatPointer(tokens)}" is ${String(value)}, which JSON does not have`);
25
+ }
26
+ return value;
27
+ }
28
+ if (Array.isArray(value))
29
+ return value.map((item, index) => toJson(item, [...tokens, index]));
30
+ if (value instanceof Map) {
31
+ const out = {};
32
+ for (const [key, item] of value) {
33
+ if (key !== null && typeof key === "object") {
34
+ throw new JsonError("INVALID_YAML", `yaml.parse: a key at "${formatPointer(tokens)}" is a map or a list, which JSON does not have`);
35
+ }
36
+ const name = key === null ? "null" : String(key);
37
+ put(out, name, toJson(item, [...tokens, name]));
38
+ }
39
+ return out;
40
+ }
41
+ throw new JsonError("INVALID_YAML", `yaml.parse: the value at "${formatPointer(tokens)}" is not a JSON value`);
42
+ }
43
+ /** The JSON value of one document, or the error of its first problem. */
44
+ function documentValue(document, index) {
45
+ const label = index === undefined ? "yaml.parse" : `yaml.parse, document ${index}`;
46
+ const problem = document.errors[0];
47
+ if (problem)
48
+ throw new JsonError("INVALID_YAML", `${label}: ${problem.message}`);
49
+ let value;
50
+ try {
51
+ value = document.toJS({ mapAsMap: true, maxAliasCount: 100 });
52
+ }
53
+ catch (error) {
54
+ throw new JsonError("INVALID_YAML", `${label}: ${error instanceof Error ? error.message : String(error)}`);
55
+ }
56
+ return toJson(value === undefined ? null : value, []);
57
+ }
58
+ /** This function parses YAML text into a JSON value. Bad YAML throws `INVALID_YAML`. */
59
+ export function parseYaml(text, options = {}) {
60
+ if (options.all)
61
+ return parseAllDocuments(text, PARSE_OPTIONS).map((document, index) => {
62
+ if (!("errors" in document) || !("toJS" in document))
63
+ throw new JsonError("INVALID_YAML", "yaml.parse: the text is not YAML");
64
+ return documentValue(document, index);
65
+ });
66
+ return documentValue(parseDocument(text, PARSE_OPTIONS), undefined);
67
+ }
68
+ /**
69
+ * The plain scalars that a YAML 1.1 reader takes as a boolean, a null, a number, a date, a merge
70
+ * key or a value key. YAML 1.2 reads them as strings, so a 1.2 writer does not quote them.
71
+ */
72
+ const YAML_1_1_TYPES = [
73
+ /^(?:y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF)$/,
74
+ /^(?:~|null|Null|NULL)$/,
75
+ /^[-+]?(?:0b[01_]+|0[0-7_]+|0|[1-9][0-9_]*|0x[0-9a-fA-F_]+|[1-9][0-9_]*(?::[0-5]?[0-9])+)$/,
76
+ /^[-+]?(?:(?:[0-9][0-9_]*)?\.[0-9._]*(?:[eE][-+]?[0-9]+)?|[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*|\.(?:inf|Inf|INF))$/,
77
+ /^\.(?:nan|NaN|NAN)$/,
78
+ /^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}/,
79
+ /^(?:<<|=)$/,
80
+ // The YAML 1.1 schema of the yaml package reads more forms: "e0" is a float there
81
+ ...new Schema({ schema: "yaml-1.1" }).tags.flatMap((tag) => tag.default === true && tag.tag !== "tag:yaml.org,2002:str" && "test" in tag && tag.test instanceof RegExp ? [tag.test] : []),
82
+ ];
83
+ /**
84
+ * This function writes a JSON value as YAML text. The text is YAML 1.2, and a YAML 1.1 reader
85
+ * reads the same value: a string that YAML 1.1 takes as another type (`yes`, `no`, `on`, a
86
+ * date, `0777`, `1_000`) is in double quotes.
87
+ */
88
+ export function stringifyYaml(value, options = {}) {
89
+ const document = new Document(value, {
90
+ version: "1.2",
91
+ schema: "core",
92
+ sortMapEntries: options.sortKeys ?? false,
93
+ aliasDuplicateObjects: false,
94
+ });
95
+ visit(document, {
96
+ Scalar(_key, node) {
97
+ const text = node.value;
98
+ if (typeof text !== "string")
99
+ return;
100
+ // A byte order mark at the start of a line is removed by a parser: it must be an escape.
101
+ // yaml 2.9.1 writes a block scalar whose first line has only spaces wrongly: " \n" came
102
+ // back as "\n", and " \nb" was not valid YAML. Double quotes keep such a text.
103
+ const blockRisk = /[\n\r]/.test(text) && /(^|[\n\r])[ \t]/.test(text);
104
+ if (blockRisk || text.includes(BOM) || YAML_1_1_TYPES.some((type) => type.test(text)))
105
+ node.type = Scalar.QUOTE_DOUBLE;
106
+ },
107
+ });
108
+ // Each byte order mark is in a string in double quotes, where the escape has the same value
109
+ return document.toString({ indent: options.indent ?? 2, lineWidth: options.lineWidth ?? 80 }).replaceAll(BOM, "\\uFEFF");
110
+ }
111
+ //# sourceMappingURL=yaml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"yaml.js","sourceRoot":"","sources":["../src/yaml.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,84 @@
1
1
  {
2
+ "$schema": "https://json.schemastore.org/package",
2
3
  "name": "@procedures/json",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
4
+ "version": "0.2.3",
5
+ "description": "Pure JSON and YAML procedures: a query with JSON Pointer or a path, JSON Patch, a diff, a merge patch, a JSON Schema check, and YAML to JSON and back.",
6
+ "keywords": [
7
+ "procedures",
8
+ "json",
9
+ "yaml",
10
+ "json-pointer",
11
+ "json-patch",
12
+ "merge-patch",
13
+ "json-schema",
14
+ "diff"
15
+ ],
16
+ "homepage": "https://mark1russell7.github.io/client/",
17
+ "bugs": {
18
+ "url": "https://github.com/mark1russell7/client/issues"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/mark1russell7/client.git",
23
+ "directory": "packages/json"
24
+ },
25
+ "license": "MIT",
26
+ "author": "Mark Russell (https://github.com/mark1russell7)",
27
+ "type": "module",
28
+ "main": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js"
34
+ },
35
+ "./module": {
36
+ "types": "./dist/module.d.ts",
37
+ "import": "./dist/module.js"
38
+ },
39
+ "./register": {
40
+ "types": "./dist/register.d.ts",
41
+ "import": "./dist/register.js"
42
+ }
43
+ },
44
+ "files": [
45
+ "dist",
46
+ "src",
47
+ "!src/**/*.test.ts",
48
+ "!src/**/*.test.tsx",
49
+ "!src/**/__tests__",
50
+ "CHANGELOG.md"
51
+ ],
52
+ "sideEffects": [
53
+ "./dist/register.js"
54
+ ],
55
+ "devDependencies": {
56
+ "@mark1russell7/cue": "^1.0.0",
57
+ "fast-check": "^4.10.2",
58
+ "typescript": "^7.0.2",
59
+ "vitest": "^5.0.3"
60
+ },
61
+ "engines": {
62
+ "node": ">=22"
63
+ },
64
+ "publishConfig": {
65
+ "access": "public",
66
+ "provenance": true
67
+ },
68
+ "client": {
69
+ "procedures": "./dist/register.js"
70
+ },
71
+ "dependencies": {
72
+ "@procedures/client": "0.2.3",
73
+ "@procedures/compiler": "0.2.3",
74
+ "yaml": "^2.9.1",
75
+ "zod": "^4.6.5"
76
+ },
77
+ "scripts": {
78
+ "build": "tsc -b",
79
+ "typecheck": "tsc --noEmit",
80
+ "clean": "rm -rf dist .tsbuildinfo",
81
+ "test": "vitest run",
82
+ "test:watch": "vitest"
83
+ }
6
84
  }
package/src/diff.ts ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The diff of two JSON values: a JSON Patch (RFC 6902) that changes the first value into the
3
+ * second value. `applyPatch(a, diffJson(a, b))` gives a value equal to `b`.
4
+ *
5
+ * - Two objects: a `remove` for each key that only the first has, a diff of each shared key, and
6
+ * an `add` for each key that only the second has.
7
+ * - Two arrays: the items of the shared start and end stay. The items between them get a diff
8
+ * for each shared position, then a `remove` or an `add` for each other item.
9
+ * - Other values: one `replace`.
10
+ *
11
+ * The patch never names `__proto__`, `constructor` or `prototype`, because `applyPatch` refuses
12
+ * them. When a difference is under such a key, the patch replaces the object that holds the key.
13
+ */
14
+
15
+ import type { PatchOperation } from "./patch.js";
16
+ import { cloneJson, formatPointer, hasOwn, jsonEqual, PROTOTYPE_KEYS, type JsonValue } from "./value.js";
17
+
18
+ function diffAt(a: JsonValue, b: JsonValue, tokens: (string | number)[], out: PatchOperation[]): void {
19
+ if (jsonEqual(a, b)) return;
20
+ const path = formatPointer(tokens);
21
+ if (Array.isArray(a) && Array.isArray(b)) {
22
+ diffArrays(a, b, tokens, out);
23
+ return;
24
+ }
25
+ if (a !== null && b !== null && typeof a === "object" && typeof b === "object" && !Array.isArray(a) && !Array.isArray(b)) {
26
+ const keysA = Object.keys(a);
27
+ const keysB = Object.keys(b);
28
+ const named = [...keysA, ...keysB].some((key) => PROTOTYPE_KEYS.has(key) && !(hasOwn(a, key) && hasOwn(b, key) && jsonEqual(a[key]!, b[key]!)));
29
+ if (!named) {
30
+ for (const key of keysA) if (!hasOwn(b, key)) out.push({ op: "remove", path: formatPointer([...tokens, key]) });
31
+ for (const key of keysA) if (hasOwn(b, key)) diffAt(a[key]!, b[key]!, [...tokens, key], out);
32
+ for (const key of keysB) if (!hasOwn(a, key)) out.push({ op: "add", path: formatPointer([...tokens, key]), value: cloneJson(b[key]!) });
33
+ return;
34
+ }
35
+ }
36
+ out.push({ op: "replace", path, value: cloneJson(b) });
37
+ }
38
+
39
+ function diffArrays(a: JsonValue[], b: JsonValue[], tokens: (string | number)[], out: PatchOperation[]): void {
40
+ let start = 0;
41
+ while (start < a.length && start < b.length && jsonEqual(a[start]!, b[start]!)) start++;
42
+ let end = 0;
43
+ while (end < a.length - start && end < b.length - start && jsonEqual(a[a.length - 1 - end]!, b[b.length - 1 - end]!)) end++;
44
+ const middleA = a.length - start - end;
45
+ const middleB = b.length - start - end;
46
+ const shared = Math.min(middleA, middleB);
47
+ for (let i = 0; i < shared; i++) diffAt(a[start + i]!, b[start + i]!, [...tokens, start + i], out);
48
+ // The items after the shared positions: remove from the last one, so each index stays valid
49
+ for (let i = middleA - 1; i >= shared; i--) out.push({ op: "remove", path: formatPointer([...tokens, start + i]) });
50
+ for (let i = shared; i < middleB; i++) out.push({ op: "add", path: formatPointer([...tokens, start + i]), value: cloneJson(b[start + i]!) });
51
+ }
52
+
53
+ /** This function gives a JSON Patch that changes `a` into `b`. Equal values give an empty patch. */
54
+ export function diffJson(a: JsonValue, b: JsonValue): PatchOperation[] {
55
+ const out: PatchOperation[] = [];
56
+ diffAt(a, b, [], out);
57
+ return out;
58
+ }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ // The root registers nothing: import "<package>/register", or give the module to createRegistry()
2
+ export * from "./module.js";
3
+ export * from "./types.js";
4
+ export { jsonQuery, jsonPatch, jsonDiff, jsonMerge, jsonValidate } from "./procedures/json/index.js";
5
+ export { yamlParse, yamlStringify } from "./procedures/yaml/index.js";
6
+ // The functions behind the procedures, for a program in the same process
7
+ export { assertJson, cloneJson, jsonEqual, JsonError, PROTOTYPE_KEYS, formatPointer, escapeToken, type JsonErrorCode } from "./value.js";
8
+ export { parsePointer, lookup, arrayIndex, type Lookup } from "./pointer.js";
9
+ export { parsePath, queryPath, type PathMatch, type PathStep } from "./path.js";
10
+ export { applyPatch } from "./patch.js";
11
+ export { diffJson } from "./diff.js";
12
+ export { mergePatch } from "./merge.js";
13
+ export { validateJson, NOT_EXAMINED, type ValidationIssue, type ValidationResult } from "./schema.js";
14
+ export { parseYaml, stringifyYaml, type YamlParseOptions, type YamlStringifyOptions } from "./yaml.js";
package/src/merge.ts ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * JSON Merge Patch (RFC 7386): a patch is a JSON value of the same form as the target.
3
+ *
4
+ * - A patch that is an object changes the target member by member. A member with the value null
5
+ * removes the key. Another member merges into the key.
6
+ * - A patch that is not an object replaces the target.
7
+ *
8
+ * The merge applies to a copy of the target. A key `__proto__`, `constructor` or `prototype` in
9
+ * an object of the patch is refused (roadmap R6).
10
+ */
11
+
12
+ import { cloneJson, formatPointer, hasOwn, isPlainObject, JsonError, PROTOTYPE_KEYS, put, type JsonValue } from "./value.js";
13
+
14
+ function mergeAt(target: JsonValue | undefined, patch: JsonValue, tokens: string[]): JsonValue {
15
+ if (!isPlainObject(patch)) return cloneJson(patch);
16
+ const result: Record<string, JsonValue> = isPlainObject(target) ? cloneJson(target as Record<string, JsonValue>) : {};
17
+ for (const key of Object.keys(patch)) {
18
+ if (PROTOTYPE_KEYS.has(key)) {
19
+ throw new JsonError("PROTOTYPE_KEY", `The merge patch has the key "${key}" at "${formatPointer([...tokens, key])}", which reaches a prototype`);
20
+ }
21
+ const value = patch[key] as JsonValue;
22
+ if (value === null) {
23
+ delete result[key];
24
+ } else {
25
+ put(result, key, mergeAt(hasOwn(result, key) ? result[key] : undefined, value, [...tokens, key]));
26
+ }
27
+ }
28
+ return result;
29
+ }
30
+
31
+ /** This function applies a JSON merge patch to a copy of the target and gives the result. */
32
+ export function mergePatch(target: JsonValue, patch: JsonValue): JsonValue {
33
+ return mergeAt(target, patch, []);
34
+ }
package/src/module.ts ADDED
@@ -0,0 +1,236 @@
1
+ /**
2
+ * The procedures of @procedures/json as data. Importing this file registers nothing.
3
+ *
4
+ * Scaffold a procedure with: mark procedure new <name> --path packages/json
5
+ *
6
+ * Each procedure is pure (`PURE_EFFECTS`): its output comes from its input, and it reads and
7
+ * changes nothing else (roadmap R39).
8
+ */
9
+
10
+ import { z } from "zod";
11
+ import { defineModule, registerModules, PROCEDURE_REGISTRY, PURE_EFFECTS, type ProcedureModule, type ProcedureRegistry, createProcedure, zodAdapter } from "@procedures/client";
12
+ import { jsonQuery } from "./procedures/json/query.js";
13
+ import { jsonPatch } from "./procedures/json/patch.js";
14
+ import { jsonDiff } from "./procedures/json/diff.js";
15
+ import { jsonMerge } from "./procedures/json/merge.js";
16
+ import { jsonValidate } from "./procedures/json/validate.js";
17
+ import { yamlParse } from "./procedures/yaml/parse.js";
18
+ import { yamlStringify } from "./procedures/yaml/stringify.js";
19
+ import type {
20
+ JsonDiffInput,
21
+ JsonDiffOutput,
22
+ JsonMergeInput,
23
+ JsonMergeOutput,
24
+ JsonPatchInput,
25
+ JsonPatchOutput,
26
+ JsonQueryInput,
27
+ JsonQueryOutput,
28
+ JsonValidateInput,
29
+ JsonValidateOutput,
30
+ YamlParseInput,
31
+ YamlParseOutput,
32
+ YamlStringifyInput,
33
+ YamlStringifyOutput,
34
+ } from "./types.js";
35
+
36
+ // =============================================================================
37
+ // Schemas
38
+ // =============================================================================
39
+
40
+ /** A JSON value. The handler examines it, so a key `__proto__` of the data reaches the check. */
41
+ const json = (description: string) => z.unknown().describe(description);
42
+
43
+ const operation = z.object({
44
+ op: z.enum(["add", "remove", "replace", "move", "copy", "test"]).describe("The operation"),
45
+ path: z.string().describe("The JSON Pointer of the location"),
46
+ from: z.string().optional().describe("The JSON Pointer of the source of move and copy"),
47
+ value: json("The value of add, replace and test").optional(),
48
+ });
49
+
50
+ const JsonQueryInputSchema = z.object({
51
+ value: json("The JSON value to search"),
52
+ pointer: z.string().optional().describe("A JSON Pointer (RFC 6901), for example /items/0/name. Give pointer or path"),
53
+ path: z.string().optional().describe("A path query, for example items[*].name or $..id. Give pointer or path"),
54
+ });
55
+
56
+ const JsonQueryOutputSchema = z.object({
57
+ found: z.boolean().describe("True when one or more values match"),
58
+ value: json("The value of the first match").optional(),
59
+ matches: z.array(z.object({ pointer: z.string().describe("The JSON Pointer of the match"), value: json("The value of the match") })).describe("Each match, in document order"),
60
+ });
61
+
62
+ const JsonPatchInputSchema = z.object({
63
+ value: json("The JSON value to change"),
64
+ patch: z.array(operation).describe("The operations of the JSON Patch (RFC 6902), in order"),
65
+ });
66
+
67
+ const JsonPatchOutputSchema = z.object({ value: json("The value after the patch") });
68
+
69
+ const JsonDiffInputSchema = z.object({
70
+ from: json("The first JSON value"),
71
+ to: json("The second JSON value"),
72
+ });
73
+
74
+ const JsonDiffOutputSchema = z.object({
75
+ patch: z.array(operation).describe("The JSON Patch (RFC 6902) that changes from into to"),
76
+ });
77
+
78
+ const JsonMergeInputSchema = z.object({
79
+ target: json("The JSON value to change"),
80
+ patch: json("The merge patch (RFC 7386): an object of changes, where null removes a key"),
81
+ });
82
+
83
+ const JsonMergeOutputSchema = z.object({ value: json("The value after the merge") });
84
+
85
+ const JsonValidateInputSchema = z.object({
86
+ value: json("The JSON value to examine"),
87
+ schema: json("The JSON Schema: an object or a boolean"),
88
+ });
89
+
90
+ const JsonValidateOutputSchema = z.object({
91
+ valid: z.boolean().describe("True when the check found no problem"),
92
+ errors: z.array(z.object({ pointer: z.string().describe("The JSON Pointer of the part"), message: z.string().describe("The problem") })).describe("Each problem"),
93
+ unchecked: z.array(z.string()).describe("The keywords of the schema that the check does not examine. Empty: the verdict is complete"),
94
+ });
95
+
96
+ const YamlParseInputSchema = z.object({
97
+ text: z.string().describe("The YAML text"),
98
+ all: z.boolean().optional().describe("True: the text can have more than one document, and value is the list of documents"),
99
+ });
100
+
101
+ const YamlParseOutputSchema = z.object({ value: json("The JSON value of the text") });
102
+
103
+ const YamlStringifyInputSchema = z.object({
104
+ value: json("The JSON value to write"),
105
+ indent: z.number().int().min(1).max(8).optional().describe("The spaces of one level of indentation (default: 2)"),
106
+ lineWidth: z.number().int().min(0).optional().describe("The longest line before a fold. 0: no fold (default: 80)"),
107
+ sortKeys: z.boolean().optional().describe("True: the keys of each map in alphabetical order"),
108
+ });
109
+
110
+ const YamlStringifyOutputSchema = z.object({ text: z.string().describe("The YAML text") });
111
+
112
+ // =============================================================================
113
+ // Procedures
114
+ // =============================================================================
115
+
116
+ const jsonQueryProcedure = createProcedure()
117
+ .path(["json", "query"])
118
+ .input(zodAdapter<JsonQueryInput>(JsonQueryInputSchema))
119
+ .output(zodAdapter<JsonQueryOutput>(JsonQueryOutputSchema))
120
+ .meta({
121
+ description: "Find the values at a JSON Pointer or a path in a JSON value",
122
+ args: [],
123
+ shorts: { pointer: "p" },
124
+ output: "json",
125
+ effects: PURE_EFFECTS,
126
+ })
127
+ .handler(async (input: JsonQueryInput): Promise<JsonQueryOutput> => jsonQuery(input))
128
+ .build();
129
+
130
+ const jsonPatchProcedure = createProcedure()
131
+ .path(["json", "patch"])
132
+ .input(zodAdapter<JsonPatchInput>(JsonPatchInputSchema))
133
+ .output(zodAdapter<JsonPatchOutput>(JsonPatchOutputSchema))
134
+ .meta({
135
+ description: "Apply an RFC 6902 JSON Patch to a JSON value",
136
+ args: [],
137
+ shorts: {},
138
+ output: "json",
139
+ effects: PURE_EFFECTS,
140
+ })
141
+ .handler(async (input: JsonPatchInput): Promise<JsonPatchOutput> => jsonPatch(input))
142
+ .build();
143
+
144
+ const jsonDiffProcedure = createProcedure()
145
+ .path(["json", "diff"])
146
+ .input(zodAdapter<JsonDiffInput>(JsonDiffInputSchema))
147
+ .output(zodAdapter<JsonDiffOutput>(JsonDiffOutputSchema))
148
+ .meta({
149
+ description: "Give the RFC 6902 JSON Patch that changes one JSON value into another",
150
+ args: [],
151
+ shorts: {},
152
+ output: "json",
153
+ effects: PURE_EFFECTS,
154
+ })
155
+ .handler(async (input: JsonDiffInput): Promise<JsonDiffOutput> => jsonDiff(input))
156
+ .build();
157
+
158
+ const jsonMergeProcedure = createProcedure()
159
+ .path(["json", "merge"])
160
+ .input(zodAdapter<JsonMergeInput>(JsonMergeInputSchema))
161
+ .output(zodAdapter<JsonMergeOutput>(JsonMergeOutputSchema))
162
+ .meta({
163
+ description: "Apply an RFC 7386 JSON merge patch to a JSON value",
164
+ args: [],
165
+ shorts: {},
166
+ output: "json",
167
+ effects: PURE_EFFECTS,
168
+ })
169
+ .handler(async (input: JsonMergeInput): Promise<JsonMergeOutput> => jsonMerge(input))
170
+ .build();
171
+
172
+ const jsonValidateProcedure = createProcedure()
173
+ .path(["json", "validate"])
174
+ .input(zodAdapter<JsonValidateInput>(JsonValidateInputSchema))
175
+ .output(zodAdapter<JsonValidateOutput>(JsonValidateOutputSchema))
176
+ .meta({
177
+ description: "Examine a JSON value against a JSON Schema",
178
+ args: [],
179
+ shorts: {},
180
+ output: "json",
181
+ effects: PURE_EFFECTS,
182
+ })
183
+ .handler(async (input: JsonValidateInput): Promise<JsonValidateOutput> => jsonValidate(input))
184
+ .build();
185
+
186
+ const yamlParseProcedure = createProcedure()
187
+ .path(["yaml", "parse"])
188
+ .input(zodAdapter<YamlParseInput>(YamlParseInputSchema))
189
+ .output(zodAdapter<YamlParseOutput>(YamlParseOutputSchema))
190
+ .meta({
191
+ description: "Parse YAML text into a JSON value",
192
+ args: ["text"],
193
+ shorts: { all: "a" },
194
+ output: "json",
195
+ effects: PURE_EFFECTS,
196
+ })
197
+ .handler(async (input: YamlParseInput): Promise<YamlParseOutput> => yamlParse(input))
198
+ .build();
199
+
200
+ const yamlStringifyProcedure = createProcedure()
201
+ .path(["yaml", "stringify"])
202
+ .input(zodAdapter<YamlStringifyInput>(YamlStringifyInputSchema))
203
+ .output(zodAdapter<YamlStringifyOutput>(YamlStringifyOutputSchema))
204
+ .meta({
205
+ description: "Write a JSON value as YAML text",
206
+ args: [],
207
+ shorts: { indent: "i", sortKeys: "s" },
208
+ output: "json",
209
+ effects: PURE_EFFECTS,
210
+ })
211
+ .handler(async (input: YamlStringifyInput): Promise<YamlStringifyOutput> => yamlStringify(input))
212
+ .build();
213
+
214
+ /**
215
+ * The module of this package, for `createRegistry()`: its procedures as data. Importing this
216
+ * file registers nothing.
217
+ */
218
+ export const jsonModule: ProcedureModule = defineModule("@procedures/json", [
219
+ jsonQueryProcedure,
220
+ jsonPatchProcedure,
221
+ jsonDiffProcedure,
222
+ jsonMergeProcedure,
223
+ jsonValidateProcedure,
224
+ yamlParseProcedure,
225
+ yamlStringifyProcedure,
226
+ ]);
227
+
228
+ export default jsonModule;
229
+
230
+ /**
231
+ * This function registers the procedures of @procedures/json in a registry. The default is the
232
+ * global registry.
233
+ */
234
+ export function registerJsonProcedures(registry: ProcedureRegistry = PROCEDURE_REGISTRY): void {
235
+ registerModules(registry, [jsonModule]);
236
+ }