mirror of
https://github.com/larvit/adf-codec.git
synced 2026-11-12 11:48:29 +01:00
Answer product-owner round 1: 0 🔴, 4 🟡 (0 misread); correct the README claims, name the getter remedy, record the name panel
This commit is contained in:
@@ -73,7 +73,7 @@ In `docs/decisions.md`:
|
|||||||
- HTML writes a URL as the ADF holds it, whatever its scheme
|
- HTML writes a URL as the ADF holds it, whatever its scheme
|
||||||
- Nothing recurses unbounded
|
- Nothing recurses unbounded
|
||||||
- JSON text is read by `parseJsonText`
|
- JSON text is read by `parseJsonText`
|
||||||
- A call reads its input once into a plain copy
|
- An ADF document is read once into a plain copy
|
||||||
- Nothing spreads an unbounded array
|
- Nothing spreads an unbounded array
|
||||||
- A retry loop checks its own termination
|
- A retry loop checks its own termination
|
||||||
- After 32 passes, a line carries or drops every em, strike and strong mark
|
- After 32 passes, a line carries or drops every em, strike and strong mark
|
||||||
@@ -134,7 +134,7 @@ parenthesized value set reading `string`; fenced examples are skipped. Keep pros
|
|||||||
- Two-space indent, English everywhere. Alphabetical order wherever order carries no meaning,
|
- Two-space indent, English everywhere. Alphabetical order wherever order carries no meaning,
|
||||||
keyed on the name a line introduces: an import sorts on its first binding, type imports ahead of
|
keyed on the name a line introduces: an import sorts on its first binding, type imports ahead of
|
||||||
value imports, so moving or renaming a module reorders nothing.
|
value imports, so moving or renaming a module reorders nothing.
|
||||||
- No casts: `as`, `as unknown as`, non-null `!`. A boundary owes a type guard validating the
|
- No casts: `as`, `as unknown as`, non-null `!`. A boundary owes a reader validating the
|
||||||
fields it claims (`readAdfDocument`); past it everything is typed. Make invalid states
|
fields it claims (`readAdfDocument`); past it everything is typed. Make invalid states
|
||||||
unrepresentable.
|
unrepresentable.
|
||||||
- Failures are values: everything returns `Result<T>`, nothing throws. `try/catch` only wrapped
|
- Failures are values: everything returns `Result<T>`, nothing throws. `try/catch` only wrapped
|
||||||
|
|||||||
+4
-3
@@ -5,9 +5,10 @@
|
|||||||
- **Breaking:** `readAdfDocument` replaces `isAdfDocument`: it returns a `Result<AdfDocument>` whose
|
- **Breaking:** `readAdfDocument` replaces `isAdfDocument`: it returns a `Result<AdfDocument>` whose
|
||||||
error names the branch that refused, and `MIGRATION.md` shows the change.
|
error names the branch that refused, and `MIGRATION.md` shows the change.
|
||||||
- `readAdfDocument`, `adfToHtml`, `adfToLosslessMarkdown` and `adfToPortableMarkdown` read the
|
- `readAdfDocument`, `adfToHtml`, `adfToLosslessMarkdown` and `adfToPortableMarkdown` read the
|
||||||
document once into a plain copy, so a document holding an accessor property, or a Proxy that
|
document once into a plain copy. A document holding an accessor property, or a Proxy that throws,
|
||||||
throws, returns `not-an-adf-document` where it ran the getter, looped or threw. A document of
|
returns `not-an-adf-document`, where `0.3.1` ran the getter, looped or threw. The copy keeps own
|
||||||
more than 10,000,000 JSON values returns `unsupported-node-shape`.
|
enumerable keys alone, so an inherited or non-enumerable key drops. A document of more than
|
||||||
|
10,000,000 JSON values returns `unsupported-node-shape`.
|
||||||
|
|
||||||
## 0.3.1
|
## 0.3.1
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -9,6 +9,7 @@
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `if (isAdfDocument(value)) convert(value)` | `const read = readAdfDocument(value); if (read.ok) convert(read.value)` |
|
| `if (isAdfDocument(value)) convert(value)` | `const read = readAdfDocument(value); if (read.ok) convert(read.value)` |
|
||||||
| `const valid = isAdfDocument(value)` | `const valid = readAdfDocument(value).ok` |
|
| `const valid = isAdfDocument(value)` | `const valid = readAdfDocument(value).ok` |
|
||||||
|
| `docs.filter(isAdfDocument)` | `docs.filter((value) => readAdfDocument(value).ok)` |
|
||||||
|
|
||||||
Convert `read.value`: it is a copy that shares no object with `value`. The copy keeps own
|
Convert `read.value`: it is a copy that shares no object with `value`. The copy keeps own
|
||||||
enumerable keys, as `JSON.stringify` does, so an inherited or non-enumerable key drops. A document
|
enumerable keys, as `JSON.stringify` does, so an inherited or non-enumerable key drops. A document
|
||||||
@@ -16,8 +17,10 @@ nesting past 500 levels still reads; the converters refuse it as `unsupported-ne
|
|||||||
|
|
||||||
`readAdfDocument`, `adfToHtml`, `adfToLosslessMarkdown` and `adfToPortableMarkdown` read a document
|
`readAdfDocument`, `adfToHtml`, `adfToLosslessMarkdown` and `adfToPortableMarkdown` read a document
|
||||||
once into that copy. A document holding an accessor property, or a Proxy that throws, is
|
once into that copy. A document holding an accessor property, or a Proxy that throws, is
|
||||||
`not-an-adf-document` in `0.4.0`, where `0.3.1` ran the getter or threw. A document holding more
|
`not-an-adf-document` in `0.4.0`, where `0.3.1` ran the getter, looped or threw. A document of a
|
||||||
than 10,000,000 JSON values is `unsupported-node-shape`.
|
class that keeps its fields behind getters (Vue 2, MobX) is refused too: hand in a snapshot,
|
||||||
|
`JSON.parse(JSON.stringify(document))`. A document holding more than 10,000,000 JSON values is
|
||||||
|
`unsupported-node-shape`.
|
||||||
|
|
||||||
## From `0.2.0` to `0.3.0`
|
## From `0.2.0` to `0.3.0`
|
||||||
|
|
||||||
|
|||||||
@@ -333,12 +333,11 @@ Each call's name says which format it reads or writes:
|
|||||||
| `htmlToAdf` | reads the HTML `adfToHtml` writes, also as a browser's `innerHTML` or a form post spells it, a carry element from its `data-adf`; refuses any other HTML, an edit to what a carry element shows included | `engine-limit`, `not-a-string`, `unmappable-html`, `unsupported-nesting-depth`, `unsupported-node-shape` |
|
| `htmlToAdf` | reads the HTML `adfToHtml` writes, also as a browser's `innerHTML` or a form post spells it, a carry element from its `data-adf`; refuses any other HTML, an edit to what a carry element shows included | `engine-limit`, `not-a-string`, `unmappable-html`, `unsupported-nesting-depth`, `unsupported-node-shape` |
|
||||||
| `losslessMarkdownToAdf` | reads the lossless flavour, markdown `adfToLosslessMarkdown` wrote, edited or not | every code in [The errors](#the-errors) except `not-an-adf-document` and `unsupported-document-version` |
|
| `losslessMarkdownToAdf` | reads the lossless flavour, markdown `adfToLosslessMarkdown` wrote, edited or not | every code in [The errors](#the-errors) except `not-an-adf-document` and `unsupported-document-version` |
|
||||||
| `portableMarkdownToAdf` | reads CommonMark, GFM's pipe tables and `~~`, alerts and task markers, Obsidian's callouts and `==`; a directive and an `adf:` fence read as text and code | `engine-limit`, `malformed-pipe-table`, `not-a-string`, `unmappable-html`, `unsupported-nesting-depth`, `unsupported-node-shape` |
|
| `portableMarkdownToAdf` | reads CommonMark, GFM's pipe tables and `~~`, alerts and task markers, Obsidian's callouts and `==`; a directive and an `adf:` fence read as text and code | `engine-limit`, `malformed-pipe-table`, `not-a-string`, `unmappable-html`, `unsupported-nesting-depth`, `unsupported-node-shape` |
|
||||||
| `readAdfDocument` | reads ADF as JSON, a value you hold from a parser or a store, into an `AdfDocument`, and writes nothing | `not-an-adf-document`, `unsupported-node-shape` |
|
| `readAdfDocument` | checks a parsed value you hold from a parser or a store, never JSON text, and returns a copy of it as an `AdfDocument`; writes nothing | `engine-limit`, `not-an-adf-document`, `unsupported-node-shape` |
|
||||||
|
|
||||||
`readAdfDocument` reads the JSON you receive into an `AdfDocument`, copying it once. It reads data
|
`readAdfDocument` reads the JSON you receive into an `AdfDocument`, copying it once. It copies own
|
||||||
properties and own enumerable keys alone, as `JSON.stringify` does, so an accessor property or a
|
enumerable keys and data properties alone, so an accessor property or a Proxy that throws is
|
||||||
Proxy that throws is `not-an-adf-document` and never a throw, and the three writers read their
|
`not-an-adf-document` and never a throw. The three writers read their document the same way. It checks the shape the converters require: `type: 'doc'`, a finite
|
||||||
document the same way. It checks the shape the converters require: `type: 'doc'`, a finite
|
|
||||||
`version` (the converters take only 1), nodes holding only `attrs`, `content`, `marks`, `text` and
|
`version` (the converters take only 1), nodes holding only `attrs`, `content`, `marks`, `text` and
|
||||||
`type`, attribute values that are JSON values, and no object holding itself. It reads a document
|
`type`, attribute values that are JSON values, and no object holding itself. It reads a document
|
||||||
nesting past 500 levels; the converters refuse that one with `unsupported-nesting-depth`.
|
nesting past 500 levels; the converters refuse that one with `unsupported-nesting-depth`.
|
||||||
@@ -611,16 +610,17 @@ Which reader raises which code: [The calls](#the-calls). Codes every direction r
|
|||||||
### Emitting
|
### Emitting
|
||||||
|
|
||||||
`adfToLosslessMarkdown`, `adfToPortableMarkdown` and `adfToHtml` raise these, and the codes under
|
`adfToLosslessMarkdown`, `adfToPortableMarkdown` and `adfToHtml` raise these, and the codes under
|
||||||
[Either direction](#either-direction):
|
[Either direction](#either-direction). `readAdfDocument` raises `not-an-adf-document` and
|
||||||
|
`unsupported-node-shape`, and `engine-limit`.
|
||||||
|
|
||||||
| Code | Fires when | What you can do |
|
| Code | Fires when | What you can do |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `not-an-adf-document` | the value handed in is no ADF document — a missing or wrong `type`, a stray key, a node that is not a node, an object in the document holding itself, an accessor property, a Proxy that throws, an attribute value that is not a plain object, array or JSON primitive, such as a `Date` or `Map` | read the boundary you receive JSON at with `readAdfDocument`; the message names the branch that refused. Build an attribute value as JSON holds it: `date.toISOString()`, `Object.fromEntries(map)`, `{ ...instance }` |
|
| `not-an-adf-document` | the value handed in is no ADF document — a missing or wrong `type`, a stray key, a node that is not a node, an object in the document holding itself, an accessor property, a Proxy that throws, an attribute value that is not a plain object, array or JSON primitive, such as a `Date` or `Map` | read the boundary you receive JSON at with `readAdfDocument`; the message names the branch that refused. Where your objects hold getters (Vue 2, MobX, a class model), hand in a snapshot, `JSON.parse(JSON.stringify(document))`. Build an attribute value as JSON holds it: `date.toISOString()`, `Object.fromEntries(map)`, `{ ...instance }` |
|
||||||
| `unsupported-document-version` | the document's `version` is not 1 | keep the ADF and pass the document over, or show it read-only; the version is the site's, not yours to change |
|
| `unsupported-document-version` | the document's `version` is not 1 | keep the ADF and pass the document over, or show it read-only; the version is the site's, not yours to change |
|
||||||
|
|
||||||
### Either direction
|
### Either direction
|
||||||
|
|
||||||
Every call can raise these:
|
Every call can raise `engine-limit`, and every conversion `unsupported-nesting-depth`:
|
||||||
|
|
||||||
| Code | Fires when | What you can do |
|
| Code | Fires when | What you can do |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
@@ -737,8 +737,8 @@ Serves [Goals](#goals) 1, 4 and 5.
|
|||||||
- Past that and `~~`, `losslessMarkdownToAdf` reads no GFM: an autolink literal and a `- [ ]`
|
- Past that and `~~`, `losslessMarkdownToAdf` reads no GFM: an autolink literal and a `- [ ]`
|
||||||
marker stay text, and a checklist is the `taskList` directive — `portableMarkdownToAdf` turns the
|
marker stay text, and a checklist is the `taskList` directive — `portableMarkdownToAdf` turns the
|
||||||
marker into a `taskList`.
|
marker into a `taskList`.
|
||||||
- A document nested deeper than 500 levels is an error result, not a stack overflow, and no call
|
- A document nested deeper than 500 levels is an error result from every conversion, not a stack
|
||||||
loops forever on a string, or on a value `JSON.parse` or `structuredClone` builds.
|
overflow, and no call loops forever on a string, or on any value you hand it.
|
||||||
- The emitted formats are semver surface
|
- The emitted formats are semver surface
|
||||||
([The formats are API](https://github.com/larvit/adf-codec/blob/main/docs/decisions.md#the-formats-are-api)).
|
([The formats are API](https://github.com/larvit/adf-codec/blob/main/docs/decisions.md#the-formats-are-api)).
|
||||||
|
|
||||||
|
|||||||
+20
-12
@@ -963,21 +963,29 @@ can read an escaped key as another key it read before, and the round-trip then r
|
|||||||
output. A test fails on `JSON.parse(` in a shipped file. The tests' own oracles keep `JSON.parse`,
|
output. A test fails on `JSON.parse(` in a shipped file. The tests' own oracles keep `JSON.parse`,
|
||||||
since a misread there fails the gate and never hides a bug.
|
since a misread there fails the gate and never hides a bug.
|
||||||
|
|
||||||
## A call reads its input once into a plain copy
|
## An ADF document is read once into a plain copy
|
||||||
|
|
||||||
2026-10-04, the maintainer; the 10 million 2026-10-05; the code and the name 2026-11-09, applying
|
2026-10-04, the maintainer. The 10 million 2026-10-05, applying §Output past V8's longest string is
|
||||||
§Which code a cause takes and the entry for the long output. Goals 1 and 9. Valid while a persona
|
refused on every engine. The code, 2026-11-09, applying §Which code a cause takes. The name by a
|
||||||
can hand in a value with accessors or a Proxy.
|
developer panel, 2026-11-09. Goals 1 and 9. Valid while a persona can hand in a value with
|
||||||
|
accessors or a Proxy.
|
||||||
|
|
||||||
`readAdfDocument` and the three writers copy the input once, before any check, reading property
|
`readAdfDocument` and the three writers copy the input once, before any check, reading property
|
||||||
descriptors so no getter runs: an accessor property, or a Proxy trap that throws, is
|
descriptors so no getter runs: an accessor property, or a Proxy trap that throws, is
|
||||||
`not-an-adf-document`. The copy keeps own enumerable string keys, as `JSON.stringify` does, so an
|
`not-an-adf-document`. The copy keeps own enumerable string keys, as `JSON.stringify` does, so an
|
||||||
inherited or non-enumerable key drops, and reads an array by index, so no iterator runs. It refuses
|
inherited or non-enumerable key drops, and reads an array by index, so no iterator runs. Every
|
||||||
a cycle by the objects above the one it copies. Every later step reads the copy. The copy takes at
|
later step reads the copy.
|
||||||
most 10 million JSON values, each object, array, string, number, boolean and null counting one,
|
|
||||||
past which the document is `unsupported-node-shape`, since the document does not convert; an array
|
A cycle is an object that holds one of the objects above it on the walk's path; the copy refuses
|
||||||
claiming more elements than the values left is refused before it is read. Goal 9's size counts ADF
|
it. The copy takes at most 10 million JSON values, each object, array, string, number, boolean and
|
||||||
as JSON, so a shared object costs once per place it is held.
|
null counting one, past which the document is `unsupported-node-shape`, since the document does
|
||||||
|
not convert. An array whose length passes the values still free is refused before any element is
|
||||||
|
read. Goal 9's size counts ADF as JSON, so a shared object costs once per place it is held. The
|
||||||
|
width check on an object's keys is the array's, and no test pins it: a Proxy would need to return
|
||||||
|
10 million key names to reach it.
|
||||||
|
|
||||||
|
The panel read 5 of 7 for `readAdfDocument` over `parseAdfDocument`, which two read as taking JSON
|
||||||
|
text, and over `validateAdfDocument`, `checkAdfDocument`, `toAdfDocument` and `adfDocumentFrom`.
|
||||||
|
|
||||||
## Nothing spreads an unbounded array
|
## Nothing spreads an unbounded array
|
||||||
|
|
||||||
@@ -1093,8 +1101,8 @@ rather than a figure.
|
|||||||
|
|
||||||
A cost fix that changes no behaviour lands on the suite staying green with no fixture output
|
A cost fix that changes no behaviour lands on the suite staying green with no fixture output
|
||||||
changed, and a before-and-after figure in its PR; the gate times nothing. Measured and kept:
|
changed, and a before-and-after figure in its PR; the gate times nothing. Measured and kept:
|
||||||
`adfDocumentFault`'s shape and depth walks stay two — the parting gives depth its own code — at
|
The shape walk of `readAdfDocument` and the depth walk of `writableContent` stay two — the parting
|
||||||
52 ms for a 9 MB document the emit takes 314 ms over. The 500-level guard runs after the block walk,
|
gives depth its own code — at 52 ms for a 9 MB document the emit takes 314 ms over. The 500-level guard runs after the block walk,
|
||||||
so it doesn't bound the walk's cost.
|
so it doesn't bound the walk's cost.
|
||||||
|
|
||||||
## Only the hard break holds a raw newline
|
## Only the hard break holds a raw newline
|
||||||
|
|||||||
Reference in New Issue
Block a user