@osdlc/yaml-splice 0.1.4 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2f3fc7c: A malformed edit is now refused as the new `EditErrorCode` `InvalidEdit`, and an error that is not a refusal is passed through instead of being reported as `InvalidResult`.
8
+
9
+ - `InvalidEdit`: a `Set` with no `value` (or `value: undefined`), an unknown tag, or a `Set` value nested more than 124 levels deep (the JSON decoder's recursion limit). These were reported as `InvalidResult`, which says the edit would damage the document, when the edit itself was bad.
10
+ - **Breaking:** `editYaml` rethrows any error without a known code unchanged. Before, it wrapped it in `EditError('InvalidResult', …)`. A Rust panic in the WebAssembly module (`RuntimeError: unreachable`) now reaches the caller as a `RuntimeError`, so a crashed module no longer looks like a normal refusal. Code that catches only `EditError` now sees these errors propagate.
11
+
3
12
  ## 0.1.4
4
13
 
5
14
  ### Patch Changes
package/README.md CHANGED
@@ -40,6 +40,8 @@ ESM only. Node.js 22 or later, or Bun.
40
40
  ## Quick start
41
41
 
42
42
  ```ts
43
+ import assert from 'node:assert/strict'
44
+
43
45
  import { editYaml } from '@osdlc/yaml-splice'
44
46
 
45
47
  const source = new TextEncoder().encode("title: 'Draft' # shown in the header\nstatus: draft\n")
@@ -49,8 +51,8 @@ const out = editYaml(source, [
49
51
  { Rename: { from: 'title', to: 'name' } },
50
52
  ])
51
53
 
52
- new TextDecoder().decode(out.bytes) // "name: 'Draft' # shown in the header\nstatus: done\n"
53
- out.changed // true
54
+ assert.equal(new TextDecoder().decode(out.bytes), "name: 'Draft' # shown in the header\nstatus: done\n")
55
+ assert.equal(out.changed, true)
54
56
  ```
55
57
 
56
58
  More in `examples/`: setting values, removing and renaming in one batch, and
@@ -70,10 +72,13 @@ handling refusals.
70
72
  - `EditError` is the refusal. Its `code` (an `EditErrorCode`) says why, and
71
73
  `name` is the same code. `span`, an `EditSpan`, is the byte range
72
74
  `[start, end)` in the input that the refusal is about, when there is one.
73
- - `EditErrorCode` is one of `InvalidUtf8`, `MalformedYaml`,
75
+ - `EditErrorCode` is one of `InvalidEdit`, `InvalidUtf8`, `MalformedYaml`,
74
76
  `UnsupportedDocument`, `RootNotMapping`, `AmbiguousKey`, `MissingKey`,
75
77
  `KeyConflict`, `UnrenderableValue` and `InvalidResult`. A minor release may
76
78
  add a code, so give a `switch` over it a `default` branch.
79
+ - Any other error `editYaml` throws is not a refusal and is passed through
80
+ as is. A `RuntimeError` from the WebAssembly module means it crashed; do
81
+ not keep calling it as if the edit had been refused.
77
82
  - `EditSpan` is `{ start, end }`, in bytes. `RootNotMapping`, `AmbiguousKey`
78
83
  and `KeyConflict` always carry one; `MalformedYaml` and
79
84
  `UnsupportedDocument` carry one when the parser knows the location.
@@ -94,6 +99,11 @@ Each export has TSDoc in the source. Your editor shows it.
94
99
  byte zero.
95
100
  - `NaN` and `±Infinity` anywhere in a `Set` value are refused as
96
101
  `UnrenderableValue` before any byte is written.
102
+ - An edit the editor cannot read is refused as `InvalidEdit`: a `Set` with
103
+ no `value` (or `value: undefined`), an unknown tag, or a `Set` value
104
+ nested more than 124 levels deep. The last is the JSON decoder's recursion
105
+ limit (serde_json's 128 levels, less the levels the edit list itself
106
+ takes).
97
107
  - Each edit is checked against what it asked for: the addressed key must read
98
108
  back as set, removed or renamed, and no other key may change value or
99
109
  order. If not, the batch is refused as `InvalidResult` instead of returning
@@ -138,6 +148,8 @@ The experimental exports:
138
148
  | An edit that changes no value returns the input bytes | `tests/index.test.ts`, "a semantic no-op …" |
139
149
  | Non-finite numbers are refused before any write | `tests/index.test.ts`, "a non-finite number in a Set value" |
140
150
  | `EditErrorCode` lists every refusal the crate has | `tests/index.test.ts`, "EditErrorCode" |
151
+ | A malformed or too-deeply nested edit is `InvalidEdit` | `tests/index.test.ts`, "an edit the editor cannot read" |
152
+ | An error that is not a refusal is passed through | `tests/index.test.ts`, "an error that is not a refusal" |
141
153
 
142
154
  ## Runtimes and platforms
143
155
 
package/dist/index.d.ts CHANGED
@@ -35,11 +35,12 @@ export interface EditOutcome {
35
35
  readonly changed: boolean;
36
36
  }
37
37
  /**
38
- * Why an edit was refused: one code per variant of the crate's `EditError`.
39
- * A minor release may add a code, so a `switch` over it needs a `default`
40
- * branch.
38
+ * Why an edit was refused: one code per variant of the crate's `EditError`,
39
+ * plus `InvalidEdit` for an edit the editor could not read: a missing `value`,
40
+ * an unknown tag, or a `Set` value nested more than 124 levels deep. A minor
41
+ * release may add a code, so a `switch` over it needs a `default` branch.
41
42
  */
42
- export type EditErrorCode = 'InvalidUtf8' | 'MalformedYaml' | 'UnsupportedDocument' | 'RootNotMapping' | 'AmbiguousKey' | 'MissingKey' | 'KeyConflict' | 'UnrenderableValue' | 'InvalidResult';
43
+ export type EditErrorCode = 'InvalidEdit' | 'InvalidUtf8' | 'MalformedYaml' | 'UnsupportedDocument' | 'RootNotMapping' | 'AmbiguousKey' | 'MissingKey' | 'KeyConflict' | 'UnrenderableValue' | 'InvalidResult';
43
44
  /**
44
45
  * A byte span `[start, end)` into the input. `RootNotMapping`, `AmbiguousKey`
45
46
  * and `KeyConflict` always carry one; `MalformedYaml` and `UnsupportedDocument`
@@ -50,7 +51,7 @@ export interface EditSpan {
50
51
  readonly end: number;
51
52
  }
52
53
  /**
53
- * A typed, structured refusal from the Rust core. `name`/`code` is the variant
54
+ * A typed refusal of an edit batch. `name`/`code` is the variant
54
55
  * and `span` carries the byte range where one is known. Thrown by
55
56
  * {@link editYaml} in place of the raw bindgen error.
56
57
  */
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { editYaml as editYamlWasm, } from './wasm/yaml_splice_wasm.js';
16
16
  /**
17
- * A typed, structured refusal from the Rust core. `name`/`code` is the variant
17
+ * A typed refusal of an edit batch. `name`/`code` is the variant
18
18
  * and `span` carries the byte range where one is known. Thrown by
19
19
  * {@link editYaml} in place of the raw bindgen error.
20
20
  */
@@ -34,17 +34,20 @@ export class EditError extends Error {
34
34
  this.span = span;
35
35
  }
36
36
  }
37
- const ERROR_CODES = new Set([
38
- 'InvalidUtf8',
39
- 'MalformedYaml',
40
- 'UnsupportedDocument',
41
- 'RootNotMapping',
42
- 'AmbiguousKey',
43
- 'MissingKey',
44
- 'KeyConflict',
45
- 'UnrenderableValue',
46
- 'InvalidResult',
47
- ]);
37
+ // Every code, so the type check fails when a code is missing here.
38
+ const ERROR_CODE_TABLE = {
39
+ InvalidEdit: true,
40
+ InvalidUtf8: true,
41
+ MalformedYaml: true,
42
+ UnsupportedDocument: true,
43
+ RootNotMapping: true,
44
+ AmbiguousKey: true,
45
+ MissingKey: true,
46
+ KeyConflict: true,
47
+ UnrenderableValue: true,
48
+ InvalidResult: true,
49
+ };
50
+ const ERROR_CODES = new Set(Object.keys(ERROR_CODE_TABLE));
48
51
  /**
49
52
  * Edit `source` (raw UTF-8 YAML bytes) with an ordered batch of `edits`,
50
53
  * returning new bytes plus a `changed` flag, or throwing a typed
@@ -67,7 +70,7 @@ export function editYaml(source, edits) {
67
70
  result = editYamlWasm(source, editsJson);
68
71
  }
69
72
  catch (raw) {
70
- throw toEditError(raw);
73
+ throw isRefusal(raw) ? toEditError(raw) : raw;
71
74
  }
72
75
  // Read both fields into a plain object, THEN free the handle. The glue's
73
76
  // `bytes` getter returns an owned copy (`.slice()`), so it survives `free()`.
@@ -106,16 +109,25 @@ function assertFiniteValue(value, key) {
106
109
  assertFiniteValue(item, key);
107
110
  }
108
111
  }
109
- /** Map a thrown bindgen `Error` (`.name` = variant, `.span`) to `EditError`. */
112
+ /**
113
+ * The `.name` the wasm gives a crate `EditError` variant it has no name for
114
+ * (the crate's enum is `#[non_exhaustive]`). Still a refusal: reported as
115
+ * `InvalidResult`, as before 0.2.0.
116
+ */
117
+ const UNNAMED_REFUSAL = 'EditError';
118
+ /**
119
+ * Whether a thrown value is one of the wasm side's refusals: an `Error` whose
120
+ * `name` is a known code, or {@link UNNAMED_REFUSAL}. Anything else, such as a
121
+ * Rust panic (`RuntimeError: unreachable`), is rethrown as is: it means the
122
+ * module is broken, not that the edit was refused.
123
+ */
124
+ function isRefusal(raw) {
125
+ return raw instanceof Error && (ERROR_CODES.has(raw.name) || raw.name === UNNAMED_REFUSAL);
126
+ }
127
+ /** Map a bindgen refusal (`.name` = code, `.span`) to `EditError`. */
110
128
  function toEditError(raw) {
111
- if (raw instanceof Error) {
112
- const code = ERROR_CODES.has(raw.name)
113
- ? raw.name
114
- : 'InvalidResult';
115
- const span = readSpan(raw.span);
116
- return new EditError(code, raw.message, span);
117
- }
118
- return new EditError('InvalidResult', String(raw));
129
+ const code = ERROR_CODES.has(raw.name) ? raw.name : 'InvalidResult';
130
+ return new EditError(code, raw.message, readSpan(raw.span));
119
131
  }
120
132
  function readSpan(span) {
121
133
  if (span !== null &&
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osdlc/yaml-splice",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Edit top-level fields of a YAML mapping in place, on bytes: set, remove or rename a field, and every byte outside the edited span stays as it was, including comments, key order, quoting, a BOM and line endings. The yaml-splice Rust crate, built to WebAssembly. Node.js and Bun.",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "author": "sksizer",