@markup-carve/carve-wasm 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mark Scherer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,302 @@
1
+ # carve-wasm
2
+
3
+ WebAssembly build of the [Carve](https://markup-carve.github.io/carve/) markup
4
+ renderer, compiled from
5
+ [carve-rs](https://github.com/markup-carve/carve-rs). Lets the Rust
6
+ implementation run client-side in the browser and backs the **Rust (WASM)**
7
+ engine in the [Carve playground](https://markup-carve.github.io/carve/playground).
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @markup-carve/carve-wasm
13
+ ```
14
+
15
+ > Publishing to npm is pending. Until the package is live you can build locally
16
+ > (see [Build](#build) below) or consume the `pkg/` output directly.
17
+
18
+ ## Usage
19
+
20
+ ### HTML migration
21
+
22
+ `htmlToCarve(html, mode)` returns `{ value, report }`, using the same HTML5
23
+ import policy and canonical writer as carve-rs. Modes are `safe` (default),
24
+ `semantic`, and trusted-only `roundtrip`.
25
+
26
+ ```js
27
+ const { value, report } = htmlToCarve('<p>Hello <strong>world</strong></p>', 'safe')
28
+ ```
29
+
30
+ ### Core renderer
31
+
32
+ Renders Carve markup to HTML with no extensions enabled.
33
+
34
+ The published package is the **bundler** target (webpack, Vite, Rollup, ...):
35
+ the wasm initializes automatically, so the exports are synchronous - no `init()`
36
+ call.
37
+
38
+ ```js
39
+ import { toHtml } from '@markup-carve/carve-wasm'
40
+
41
+ const html = toHtml('# Hello, Carve!')
42
+ document.body.innerHTML = html
43
+ ```
44
+
45
+ ### Extensions
46
+
47
+ `extensions()` reports every extension this build accepts. The list comes from
48
+ the engine, so it cannot fall behind what the engine has:
49
+
50
+ ```js
51
+ import { extensions, toHtmlWithOptions } from '@markup-carve/carve-wasm'
52
+
53
+ extensions()
54
+ // ['autolink', 'citations', 'code-callouts', 'code-group', ...]
55
+
56
+ toHtmlWithOptions(src, { extensions: ['glossary', 'table-of-contents'] })
57
+ ```
58
+
59
+ Names are kebab-case; snake_case (`math_block`) is accepted too. An unknown name
60
+ throws, because an ignored extension renders as missing behavior that looks
61
+ like a Carve bug.
62
+
63
+ ### Full renderer (preview set)
64
+
65
+ `toHtmlFull` enables the preview set the playground uses: tab normalisation,
66
+ `<details>` fences, Mermaid diagrams, wikilinks, autolink, list-table, math
67
+ blocks, heading permalinks, citations, code callouts, external-link decoration,
68
+ code groups, and tabs.
69
+
70
+ It is a curated subset rather than everything registered. Extensions that
71
+ rewrite a document that never asked - `heading-numbers` numbers every heading,
72
+ `table-of-contents` injects a TOC - are wrong for a preview. Name them
73
+ explicitly through `toHtmlWithOptions` when you want them.
74
+
75
+ ```js
76
+ import { toHtmlFull } from '@markup-carve/carve-wasm'
77
+
78
+ const html = toHtmlFull('# Hello\n\n``` mermaid\ngraph TD; A-->B\n```\n')
79
+ document.body.innerHTML = html
80
+ ```
81
+
82
+ ### Symbols
83
+
84
+ A `:name:` symbol renders its literal `:name:` source unless the name is in the
85
+ **symbols map**. Pass one as a plain object (or a `Map`) to `toHtmlWithSymbols`,
86
+ or as the optional second argument of `toHtmlFull`:
87
+
88
+ ```js
89
+ import { toHtmlWithSymbols } from '@markup-carve/carve-wasm'
90
+
91
+ toHtmlWithSymbols('Ship it :rocket:', { rocket: '🚀' })
92
+ // => '<p>Ship it 🚀</p>'
93
+
94
+ toHtmlWithSymbols('Ship it :rocket: :shrug:', { rocket: '🚀' })
95
+ // => '<p>Ship it 🚀 :shrug:</p>' (an unmapped name stays literal)
96
+ ```
97
+
98
+ The word-boundary guard is unaffected by an active map: `a:b:c`, `10:30:` and
99
+ `me@example.com` never become symbols. Names and values must both be strings; a
100
+ non-string value throws a `TypeError`.
101
+
102
+ > **Security: symbol values are TRUSTED RAW output.**
103
+ > A mapped value is inserted into the output **unescaped** - the same trust
104
+ > class as a static `renderers` callback. `{ b: '<b>x</b>' }` emits a real
105
+ > `<b>` element, not escaped text. This is deliberate (processor configuration
106
+ > is trusted). **Never build a symbols map out of untrusted / user-supplied
107
+ > input.**
108
+
109
+ ### Section wrappers
110
+
111
+ A top-level heading is wrapped, along with the content following it up to the
112
+ next same-or-shallower heading, in a `<section>` carrying the heading's id (spec
113
+ PART 9 §13). Only the id moves - `{#install .featured}` gives
114
+ `<section id="install"><h2 class="featured">` - and a heading inside a
115
+ blockquote, div or list item is not wrapped at all.
116
+
117
+ `toHtmlWithOptions` is the general entry point, and `sections: false` renders
118
+ headings flat with the id back on the `<h*>`:
119
+
120
+ ```js
121
+ import { toHtmlWithOptions } from '@markup-carve/carve-wasm'
122
+
123
+ toHtmlWithOptions('# A\n\np\n', { sections: false })
124
+ // '<h1 id="A">A</h1>\n<p>p</p>'
125
+
126
+ toHtmlWithOptions(src, { sections: false, symbols: { rocket: '🚀' }, full: true })
127
+ ```
128
+
129
+ Every field is optional - `sections` (default `true`), `symbols` (same trusted-raw
130
+ contract as `toHtmlWithSymbols`), `extensions` (an array of names; takes
131
+ precedence over `full`), and `full` (default `false`, enabling the preview set).
132
+ Omitting the object, or passing `null`, renders with defaults, so the three
133
+ shorthands above remain the zero-config forms.
134
+
135
+ An unrecognized key is ignored, because the object is configuration and a typo
136
+ should not break a render. A recognized key with the wrong type throws a
137
+ `TypeError` instead of being coerced: JS truthiness would read
138
+ `{ sections: 'false' }` as `true`, the opposite of what was written.
139
+
140
+ This exists for a host whose CSS or JS assumes rendered blocks are direct
141
+ children of the content container - the `.stack > * + *` spacing idiom,
142
+ `:first-child`, `nth-child()` counting, `element.children` walks - all of which
143
+ stop matching once a wrapper sits in between. It is the one output change that
144
+ breaks a document whose *source* migrated cleanly.
145
+
146
+ Nothing else changes: ids, collision dedup, `</#id>` cross-references, implicit
147
+ `[Heading][]` references and heading numbering all resolve against the slug
148
+ rather than the element carrying it. The endnotes
149
+ `<section role="doc-endnotes">` is a separate construct and is still emitted.
150
+
151
+ ### TypeScript
152
+
153
+ The package ships `.d.ts` declarations. Types are inferred automatically when
154
+ imported from `@markup-carve/carve-wasm`.
155
+
156
+ ```ts
157
+ import { toHtml, toHtmlFull, version } from '@markup-carve/carve-wasm'
158
+
159
+ console.log(`carve-wasm v${version()}`)
160
+ const html: string = toHtml('_Hello_')
161
+ ```
162
+
163
+ ## API
164
+
165
+ | Export | Signature | Description |
166
+ |--------|-----------|-------------|
167
+ | `toHtml` | `(source: string) => string` | Core renderer, no extensions |
168
+ | `toHtmlWithSymbols` | `(source: string, symbols?: object \| null) => string` | Core renderer + a `:name:` -> value symbols map (values are raw, see above) |
169
+ | `toHtmlFull` | `(source: string, symbols?: object \| null) => string` | Core + common extensions (matches playground), optional symbols map |
170
+ | `toHtmlWithOptions` | `(source: string, options?: object \| null) => string` | General form: `{ sections?, symbols?, full? }`, every field optional |
171
+ | `parseJson` | `(source: string) => string` | The parsed AST as JSON (PART 12 exchange shape) |
172
+ | `version` | `() => string` | Returns the carve-wasm package version |
173
+
174
+ ### The parsed AST
175
+
176
+ `parseJson` returns the document as a JSON string - the [PART 12 exchange
177
+ shape](https://markup-carve.github.io/carve/ast-json), the same tree every Carve
178
+ engine publishes, so a consumer written against one implementation reads
179
+ another's output.
180
+
181
+ ```js
182
+ import { parseJson } from '@markup-carve/carve-wasm'
183
+
184
+ const ast = JSON.parse(parseJson('# Title\n\nBody[^a].\n\n[^a]: note\n'))
185
+ ast.children.map((n) => n.type) // ['heading', 'paragraph', 'footnote']
186
+ ast.children[0].pos // { startLine: 1, startColumn: 1, ... }
187
+ ```
188
+
189
+ The root carries exactly `type`, `children` and `srcByteLength`; frontmatter and
190
+ footnote definitions are block nodes inside `children`, not root fields. Every
191
+ node except the root carries `pos` when the engine could place it - 1-based
192
+ lines and columns, 0-based offsets, ends exclusive, counted in Unicode
193
+ **codepoints**, not bytes or UTF-16 units. A node the engine could not place,
194
+ such as reassembled table-cell text, carries no `pos` at all rather than an
195
+ invented one.
196
+
197
+ A string rather than a JS object: the caller runs `JSON.parse`, which the
198
+ browser does natively and faster than building the object graph across the wasm
199
+ boundary one property at a time - and it keeps the bytes available for a caller
200
+ that stores or forwards them.
201
+
202
+ ## carve-rs dependency pin
203
+
204
+ `Cargo.toml` pins an exact carve-rs commit, and `Cargo.lock` is committed
205
+ alongside it:
206
+
207
+ ```toml
208
+ carve = { package = "carve-lang", git = "https://github.com/markup-carve/carve-rs", rev = "..." }
209
+ ```
210
+
211
+ Read the current revision out of `Cargo.toml` rather than from a copy here - a
212
+ revision quoted in prose goes stale the first time someone bumps the manifest
213
+ without noticing the duplicate.
214
+
215
+ The engine is published as `carve-lang` (carve-rs renamed it from `carve`), so a
216
+ pin at any revision past that rename needs `package = "carve-lang"` as above.
217
+
218
+ The crate previously tracked carve-rs' default branch with no committed lock.
219
+ That never went stale, but it went the other way: every build resolved whatever
220
+ had landed upstream since, so the published package could carry an engine no CI
221
+ run here had ever built, and two clones a day apart could disagree. The pin
222
+ makes an engine change a reviewable line in a diff.
223
+
224
+ When bumping the `rev`, regenerate and commit `Cargo.lock` in the same change.
225
+ The lock records the resolved revision plus the rest of the tree; leaving it
226
+ behind gives every fresh clone a dirty working tree on its first build and lets
227
+ the package resolve to an engine other than the one that was tested.
228
+
229
+ ```sh
230
+ cargo update -p carve-lang --precise <sha> # or edit the rev and re-lock
231
+ cargo test && wasm-pack build --target nodejs && node tests/smoke.mjs
232
+ CARVE_SPEC_CORPUS=/path/to/carve/tests/corpus node tests/corpus.mjs
233
+ ```
234
+
235
+ That last line is the one that can tell a drifted pin from a current one.
236
+ `smoke.mjs` asserts hand-written expectations, which a stale engine satisfies
237
+ happily; `corpus.mjs` renders all ~530 mandatory spec documents through the
238
+ **built** artifact and requires byte-identical HTML. Without `CARVE_SPEC_CORPUS`
239
+ it prints a notice and exits 0, so a checkout without the spec repo still runs
240
+ the suite. CI always sets it.
241
+
242
+ It measures the binding as much as the engine: carve-rs is corpus-checked
243
+ upstream, but that says nothing about whether the wasm-bindgen layer drops a
244
+ field or mangles an option on the way through.
245
+
246
+ Regenerate the whole lock MSRV-aware, or it will quietly break the `rust-version`
247
+ this crate advertises. A plain `cargo generate-lockfile` on a current toolchain
248
+ picks the newest `wasm-bindgen`, which needs a newer Rust than the 1.75 declared
249
+ above - fine while CI only runs `stable`, and a hard failure for anyone actually
250
+ building on the floor:
251
+
252
+ ```sh
253
+ CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS=fallback cargo generate-lockfile
254
+ ```
255
+
256
+ Nothing in CI catches this today, since the workflow uses `stable` only. Adding
257
+ a 1.75 job (carve-rs has one) would turn it from a review question into a build
258
+ failure.
259
+
260
+ ## Build
261
+
262
+ The published package uses the **bundler** target (matching the release
263
+ workflow):
264
+
265
+ ```bash
266
+ cargo test
267
+ wasm-pack build --target bundler --scope markup-carve
268
+ ```
269
+
270
+ `wasm-pack` emits the package into `pkg/`. For a no-bundler / `<script type=module>`
271
+ setup use `--target web` (which exports a default `init()` you must `await`
272
+ before calling the renderers); for Node use `--target nodejs`.
273
+
274
+ Point the test suite at any built package with `CARVE_WASM_PKG`, rather than
275
+ only at `pkg/`:
276
+
277
+ ```sh
278
+ CARVE_WASM_PKG=some/other/pkg node tests/smoke.mjs
279
+ ```
280
+
281
+ Both wasm-pack targets load. Unset, it is `pkg/`, so nothing changes for the
282
+ ordinary loop.
283
+
284
+ ## Release
285
+
286
+ Publishing is gated. `.github/workflows/release.yml` has two jobs, and
287
+ `publish` declares `verify` in `needs:`, so it cannot start while the gate
288
+ fails. The gate is `scripts/verify-release-artifact.mjs`, and it is runnable by
289
+ hand against a local build:
290
+
291
+ ```sh
292
+ wasm-pack build --target bundler --scope markup-carve
293
+ CARVE_SPEC_CORPUS=/path/to/carve/tests/corpus node scripts/verify-release-artifact.mjs
294
+ ```
295
+
296
+ It runs `npm pack` and drives `tests/smoke.mjs` and `tests/corpus.mjs` at the
297
+ UNPACKED tarball, not at `pkg/`. The difference matters: npm uploads what the
298
+ generated `files` list names, so a payload file left out of it would never
299
+ reach the registry and would never have been tested either. The corpus
300
+ population comes from the spec's example pages, so a truncated corpus fails
301
+ here instead of passing over a subset, and an unset `CARVE_SPEC_CORPUS` is
302
+ refused rather than skipped.
@@ -0,0 +1,120 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /**
5
+ * Every extension name this build accepts, in registry order.
6
+ *
7
+ * Taken from the engine, so a new extension is reachable as soon as the pin
8
+ * moves. Nothing here lists names.
9
+ */
10
+ export function extensions(): string[];
11
+
12
+ /**
13
+ * Import HTML through the Rust HTML5 DOM and canonical Carve writer.
14
+ *
15
+ * Returns `{ value, report }`; `report.diagnostics` makes every lossy import
16
+ * decision observable. `roundtrip` is only safe for Carve-produced HTML.
17
+ */
18
+ export function htmlToCarve(source: string, mode?: string | null): any;
19
+
20
+ /**
21
+ * Parse Carve source and return its AST as a JSON string.
22
+ *
23
+ * The PART 12 exchange shape (https://markup-carve.github.io/carve/ast-json):
24
+ * the same tree every Carve engine publishes, so a consumer written against
25
+ * one implementation reads another's output. The root carries exactly `type`,
26
+ * `children` and `srcByteLength`; frontmatter and footnote definitions are
27
+ * block nodes inside `children`, not root fields.
28
+ *
29
+ * Returns a STRING rather than a JS object: the caller runs `JSON.parse`, which
30
+ * is what a browser does natively and faster than building the object graph
31
+ * across the wasm boundary one property at a time. It also keeps the bytes
32
+ * available for a caller that stores or forwards them.
33
+ *
34
+ * Position tracking is on for this entry point and nowhere else. PART 12 §4
35
+ * lets an engine gate tracking behind a parse option but requires the
36
+ * serialized form to carry it, and rendering would pay for spans nobody reads.
37
+ */
38
+ export function parseJson(source: string): string;
39
+
40
+ export function toHtml(source: string): string;
41
+
42
+ /**
43
+ * Render with the preview extension set enabled (`PREVIEW_EXTENSIONS`), so the
44
+ * WASM engine matches an extensions-on host such as the docs Playground rather
45
+ * than the core-only `toHtml`.
46
+ *
47
+ * The set is curated, not "everything the engine has": `heading-numbers` and
48
+ * `table-of-contents` rewrite a document that never asked for it, which is
49
+ * wrong for a preview. Callers who want an exact set pass `extensions` to
50
+ * `toHtmlWithOptions`, and `extensions()` reports what this build accepts.
51
+ *
52
+ * The optional second argument is the same **symbols map** as
53
+ * [`to_html_with_symbols`], with the same trusted-raw contract: mapped values
54
+ * are emitted UNESCAPED, so never feed it untrusted input.
55
+ */
56
+ export function toHtmlFull(source: string, symbols?: object | null): string;
57
+
58
+ /**
59
+ * Render with an options object, the general form of the three shorthands
60
+ * above.
61
+ *
62
+ * ```js
63
+ * toHtmlWithOptions('# A\n\np\n', { sections: false })
64
+ * // '<h1 id="A">A</h1>\n<p>p</p>'
65
+ *
66
+ * toHtmlWithOptions(src, { sections: false, symbols: { rocket: '🚀' }, full: true })
67
+ * ```
68
+ *
69
+ * Every field is optional:
70
+ *
71
+ * * `sections` (default `true`) - wrap each top-level heading, and the content
72
+ * following it up to the next same-or-shallower heading, in a
73
+ * `<section id="…">` (spec PART 9 §13). `false` renders headings flat with
74
+ * the id back on the `<h*>` and the former section children as siblings.
75
+ * For a host whose CSS or JS assumes rendered blocks are direct children of
76
+ * the content container - the `.stack > * + *` spacing idiom,
77
+ * `:first-child`, `nth-child()` counting, `element.children` walks - the
78
+ * wrapper is the one output change a clean source migration still breaks.
79
+ * * `symbols` - the same map as [`to_html_with_symbols`], with the same
80
+ * TRUSTED-RAW contract: mapped values are emitted UNESCAPED, so never build
81
+ * it from untrusted input.
82
+ * * `extensions` - an array of extension names to enable, e.g.
83
+ * `["glossary", "table-of-contents"]`. `extensions()` reports what this
84
+ * build accepts. An unknown name throws: a silently ignored extension would
85
+ * render as missing behavior that looks like a Carve bug. Takes precedence
86
+ * over `full`.
87
+ * * `full` (default `false`) - enable the preview extension set instead of
88
+ * rendering core-only.
89
+ *
90
+ * An unrecognized key is ignored: the object is configuration, and a caller
91
+ * who mistypes one deserves the render to still work. A wrong TYPE on a key
92
+ * that is recognized does throw, because that changes behavior silently.
93
+ *
94
+ * Turning sections off changes nothing else. Ids, collision dedup, `</#id>`
95
+ * crossrefs, implicit `[Heading][]` references and heading numbering all
96
+ * resolve against the slug rather than the element carrying it, and the
97
+ * endnotes `<section role="doc-endnotes">` is a separate construct that is
98
+ * still emitted.
99
+ */
100
+ export function toHtmlWithOptions(source: string, options?: object | null): string;
101
+
102
+ /**
103
+ * Render with the core profile and a **symbols map**: `{ rocket: "🚀" }` (a
104
+ * plain object or a `Map`). A `:name:` symbol whose name is in the map renders
105
+ * the mapped value; an unmapped `:name:` stays literal `:name:` text, and the
106
+ * leading word-boundary guard still applies (`a:b:c`, `10:30:`,
107
+ * `me@example.com` never become symbols).
108
+ *
109
+ * Names and values must both be strings; a non-string value throws a JS
110
+ * `TypeError`.
111
+ *
112
+ * SECURITY: a mapped value is inserted as **TRUSTED RAW output in the target
113
+ * format** - it is NOT escaped, the same trust class as the static `renderers`
114
+ * map. So `{ b: "<b>x</b>" }` emits a real `<b>` element, not escaped text.
115
+ * This is deliberate: processor configuration is trusted. NEVER build a
116
+ * symbols map out of untrusted / user-supplied input.
117
+ */
118
+ export function toHtmlWithSymbols(source: string, symbols?: object | null): string;
119
+
120
+ export function version(): string;
package/carve_wasm.js ADDED
@@ -0,0 +1,9 @@
1
+ /* @ts-self-types="./carve_wasm.d.ts" */
2
+
3
+ import * as wasm from "./carve_wasm_bg.wasm";
4
+ import { __wbg_set_wasm } from "./carve_wasm_bg.js";
5
+ __wbg_set_wasm(wasm);
6
+ wasm.__wbindgen_start();
7
+ export {
8
+ extensions, htmlToCarve, parseJson, toHtml, toHtmlFull, toHtmlWithOptions, toHtmlWithSymbols, version
9
+ } from "./carve_wasm_bg.js";
@@ -0,0 +1,482 @@
1
+ /**
2
+ * Every extension name this build accepts, in registry order.
3
+ *
4
+ * Taken from the engine, so a new extension is reachable as soon as the pin
5
+ * moves. Nothing here lists names.
6
+ * @returns {string[]}
7
+ */
8
+ export function extensions() {
9
+ const ret = wasm.extensions();
10
+ var v1 = getArrayJsValueFromWasm0(ret[0], ret[1]).slice();
11
+ wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
12
+ return v1;
13
+ }
14
+
15
+ /**
16
+ * Import HTML through the Rust HTML5 DOM and canonical Carve writer.
17
+ *
18
+ * Returns `{ value, report }`; `report.diagnostics` makes every lossy import
19
+ * decision observable. `roundtrip` is only safe for Carve-produced HTML.
20
+ * @param {string} source
21
+ * @param {string | null} [mode]
22
+ * @returns {any}
23
+ */
24
+ export function htmlToCarve(source, mode) {
25
+ const ptr0 = passStringToWasm0(source, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
26
+ const len0 = WASM_VECTOR_LEN;
27
+ var ptr1 = isLikeNone(mode) ? 0 : passStringToWasm0(mode, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
28
+ var len1 = WASM_VECTOR_LEN;
29
+ const ret = wasm.htmlToCarve(ptr0, len0, ptr1, len1);
30
+ if (ret[2]) {
31
+ throw takeFromExternrefTable0(ret[1]);
32
+ }
33
+ return takeFromExternrefTable0(ret[0]);
34
+ }
35
+
36
+ /**
37
+ * Parse Carve source and return its AST as a JSON string.
38
+ *
39
+ * The PART 12 exchange shape (https://markup-carve.github.io/carve/ast-json):
40
+ * the same tree every Carve engine publishes, so a consumer written against
41
+ * one implementation reads another's output. The root carries exactly `type`,
42
+ * `children` and `srcByteLength`; frontmatter and footnote definitions are
43
+ * block nodes inside `children`, not root fields.
44
+ *
45
+ * Returns a STRING rather than a JS object: the caller runs `JSON.parse`, which
46
+ * is what a browser does natively and faster than building the object graph
47
+ * across the wasm boundary one property at a time. It also keeps the bytes
48
+ * available for a caller that stores or forwards them.
49
+ *
50
+ * Position tracking is on for this entry point and nowhere else. PART 12 §4
51
+ * lets an engine gate tracking behind a parse option but requires the
52
+ * serialized form to carry it, and rendering would pay for spans nobody reads.
53
+ * @param {string} source
54
+ * @returns {string}
55
+ */
56
+ export function parseJson(source) {
57
+ let deferred2_0;
58
+ let deferred2_1;
59
+ try {
60
+ const ptr0 = passStringToWasm0(source, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
61
+ const len0 = WASM_VECTOR_LEN;
62
+ const ret = wasm.parseJson(ptr0, len0);
63
+ deferred2_0 = ret[0];
64
+ deferred2_1 = ret[1];
65
+ return getStringFromWasm0(ret[0], ret[1]);
66
+ } finally {
67
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
68
+ }
69
+ }
70
+
71
+ /**
72
+ * @param {string} source
73
+ * @returns {string}
74
+ */
75
+ export function toHtml(source) {
76
+ let deferred2_0;
77
+ let deferred2_1;
78
+ try {
79
+ const ptr0 = passStringToWasm0(source, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
80
+ const len0 = WASM_VECTOR_LEN;
81
+ const ret = wasm.toHtml(ptr0, len0);
82
+ deferred2_0 = ret[0];
83
+ deferred2_1 = ret[1];
84
+ return getStringFromWasm0(ret[0], ret[1]);
85
+ } finally {
86
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Render with the preview extension set enabled (`PREVIEW_EXTENSIONS`), so the
92
+ * WASM engine matches an extensions-on host such as the docs Playground rather
93
+ * than the core-only `toHtml`.
94
+ *
95
+ * The set is curated, not "everything the engine has": `heading-numbers` and
96
+ * `table-of-contents` rewrite a document that never asked for it, which is
97
+ * wrong for a preview. Callers who want an exact set pass `extensions` to
98
+ * `toHtmlWithOptions`, and `extensions()` reports what this build accepts.
99
+ *
100
+ * The optional second argument is the same **symbols map** as
101
+ * [`to_html_with_symbols`], with the same trusted-raw contract: mapped values
102
+ * are emitted UNESCAPED, so never feed it untrusted input.
103
+ * @param {string} source
104
+ * @param {object | null} [symbols]
105
+ * @returns {string}
106
+ */
107
+ export function toHtmlFull(source, symbols) {
108
+ let deferred3_0;
109
+ let deferred3_1;
110
+ try {
111
+ const ptr0 = passStringToWasm0(source, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
112
+ const len0 = WASM_VECTOR_LEN;
113
+ const ret = wasm.toHtmlFull(ptr0, len0, isLikeNone(symbols) ? 0 : addToExternrefTable0(symbols));
114
+ var ptr2 = ret[0];
115
+ var len2 = ret[1];
116
+ if (ret[3]) {
117
+ ptr2 = 0; len2 = 0;
118
+ throw takeFromExternrefTable0(ret[2]);
119
+ }
120
+ deferred3_0 = ptr2;
121
+ deferred3_1 = len2;
122
+ return getStringFromWasm0(ptr2, len2);
123
+ } finally {
124
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Render with an options object, the general form of the three shorthands
130
+ * above.
131
+ *
132
+ * ```js
133
+ * toHtmlWithOptions('# A\n\np\n', { sections: false })
134
+ * // '<h1 id="A">A</h1>\n<p>p</p>'
135
+ *
136
+ * toHtmlWithOptions(src, { sections: false, symbols: { rocket: '🚀' }, full: true })
137
+ * ```
138
+ *
139
+ * Every field is optional:
140
+ *
141
+ * * `sections` (default `true`) - wrap each top-level heading, and the content
142
+ * following it up to the next same-or-shallower heading, in a
143
+ * `<section id="…">` (spec PART 9 §13). `false` renders headings flat with
144
+ * the id back on the `<h*>` and the former section children as siblings.
145
+ * For a host whose CSS or JS assumes rendered blocks are direct children of
146
+ * the content container - the `.stack > * + *` spacing idiom,
147
+ * `:first-child`, `nth-child()` counting, `element.children` walks - the
148
+ * wrapper is the one output change a clean source migration still breaks.
149
+ * * `symbols` - the same map as [`to_html_with_symbols`], with the same
150
+ * TRUSTED-RAW contract: mapped values are emitted UNESCAPED, so never build
151
+ * it from untrusted input.
152
+ * * `extensions` - an array of extension names to enable, e.g.
153
+ * `["glossary", "table-of-contents"]`. `extensions()` reports what this
154
+ * build accepts. An unknown name throws: a silently ignored extension would
155
+ * render as missing behavior that looks like a Carve bug. Takes precedence
156
+ * over `full`.
157
+ * * `full` (default `false`) - enable the preview extension set instead of
158
+ * rendering core-only.
159
+ *
160
+ * An unrecognized key is ignored: the object is configuration, and a caller
161
+ * who mistypes one deserves the render to still work. A wrong TYPE on a key
162
+ * that is recognized does throw, because that changes behavior silently.
163
+ *
164
+ * Turning sections off changes nothing else. Ids, collision dedup, `</#id>`
165
+ * crossrefs, implicit `[Heading][]` references and heading numbering all
166
+ * resolve against the slug rather than the element carrying it, and the
167
+ * endnotes `<section role="doc-endnotes">` is a separate construct that is
168
+ * still emitted.
169
+ * @param {string} source
170
+ * @param {object | null} [options]
171
+ * @returns {string}
172
+ */
173
+ export function toHtmlWithOptions(source, options) {
174
+ let deferred3_0;
175
+ let deferred3_1;
176
+ try {
177
+ const ptr0 = passStringToWasm0(source, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
178
+ const len0 = WASM_VECTOR_LEN;
179
+ const ret = wasm.toHtmlWithOptions(ptr0, len0, isLikeNone(options) ? 0 : addToExternrefTable0(options));
180
+ var ptr2 = ret[0];
181
+ var len2 = ret[1];
182
+ if (ret[3]) {
183
+ ptr2 = 0; len2 = 0;
184
+ throw takeFromExternrefTable0(ret[2]);
185
+ }
186
+ deferred3_0 = ptr2;
187
+ deferred3_1 = len2;
188
+ return getStringFromWasm0(ptr2, len2);
189
+ } finally {
190
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Render with the core profile and a **symbols map**: `{ rocket: "🚀" }` (a
196
+ * plain object or a `Map`). A `:name:` symbol whose name is in the map renders
197
+ * the mapped value; an unmapped `:name:` stays literal `:name:` text, and the
198
+ * leading word-boundary guard still applies (`a:b:c`, `10:30:`,
199
+ * `me@example.com` never become symbols).
200
+ *
201
+ * Names and values must both be strings; a non-string value throws a JS
202
+ * `TypeError`.
203
+ *
204
+ * SECURITY: a mapped value is inserted as **TRUSTED RAW output in the target
205
+ * format** - it is NOT escaped, the same trust class as the static `renderers`
206
+ * map. So `{ b: "<b>x</b>" }` emits a real `<b>` element, not escaped text.
207
+ * This is deliberate: processor configuration is trusted. NEVER build a
208
+ * symbols map out of untrusted / user-supplied input.
209
+ * @param {string} source
210
+ * @param {object | null} [symbols]
211
+ * @returns {string}
212
+ */
213
+ export function toHtmlWithSymbols(source, symbols) {
214
+ let deferred3_0;
215
+ let deferred3_1;
216
+ try {
217
+ const ptr0 = passStringToWasm0(source, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
218
+ const len0 = WASM_VECTOR_LEN;
219
+ const ret = wasm.toHtmlWithSymbols(ptr0, len0, isLikeNone(symbols) ? 0 : addToExternrefTable0(symbols));
220
+ var ptr2 = ret[0];
221
+ var len2 = ret[1];
222
+ if (ret[3]) {
223
+ ptr2 = 0; len2 = 0;
224
+ throw takeFromExternrefTable0(ret[2]);
225
+ }
226
+ deferred3_0 = ptr2;
227
+ deferred3_1 = len2;
228
+ return getStringFromWasm0(ptr2, len2);
229
+ } finally {
230
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
231
+ }
232
+ }
233
+
234
+ /**
235
+ * @returns {string}
236
+ */
237
+ export function version() {
238
+ let deferred1_0;
239
+ let deferred1_1;
240
+ try {
241
+ const ret = wasm.version();
242
+ deferred1_0 = ret[0];
243
+ deferred1_1 = ret[1];
244
+ return getStringFromWasm0(ret[0], ret[1]);
245
+ } finally {
246
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
247
+ }
248
+ }
249
+ export function __wbg___wbindgen_boolean_get_a86c216575a75c30(arg0) {
250
+ const v = arg0;
251
+ const ret = typeof(v) === 'boolean' ? v : undefined;
252
+ return isLikeNone(ret) ? 0xFFFFFF : ret ? 1 : 0;
253
+ }
254
+ export function __wbg___wbindgen_is_null_344c8750a8525473(arg0) {
255
+ const ret = arg0 === null;
256
+ return ret;
257
+ }
258
+ export function __wbg___wbindgen_is_undefined_c0cca72b82b86f4d(arg0) {
259
+ const ret = arg0 === undefined;
260
+ return ret;
261
+ }
262
+ export function __wbg___wbindgen_string_get_914df97fcfa788f2(arg0, arg1) {
263
+ const obj = arg1;
264
+ const ret = typeof(obj) === 'string' ? obj : undefined;
265
+ var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
266
+ var len1 = WASM_VECTOR_LEN;
267
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
268
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
269
+ }
270
+ export function __wbg___wbindgen_throw_81fc77679af83bc6(arg0, arg1) {
271
+ throw new Error(getStringFromWasm0(arg0, arg1));
272
+ }
273
+ export function __wbg_entries_616b1a459b85be0b(arg0) {
274
+ const ret = Object.entries(arg0);
275
+ return ret;
276
+ }
277
+ export function __wbg_entries_7d89e479ff037f54(arg0) {
278
+ const ret = arg0.entries();
279
+ return ret;
280
+ }
281
+ export function __wbg_from_741da0f916ab74aa(arg0) {
282
+ const ret = Array.from(arg0);
283
+ return ret;
284
+ }
285
+ export function __wbg_get_4848e350b40afc16(arg0, arg1) {
286
+ const ret = arg0[arg1 >>> 0];
287
+ return ret;
288
+ }
289
+ export function __wbg_get_f96702c6245e4ef9() { return handleError(function (arg0, arg1) {
290
+ const ret = Reflect.get(arg0, arg1);
291
+ return ret;
292
+ }, arguments); }
293
+ export function __wbg_get_unchecked_7d7babe32e9e6a54(arg0, arg1) {
294
+ const ret = arg0[arg1 >>> 0];
295
+ return ret;
296
+ }
297
+ export function __wbg_instanceof_Map_a10a2795ef4bfe97(arg0) {
298
+ let result;
299
+ try {
300
+ result = arg0 instanceof Map;
301
+ } catch (_) {
302
+ result = false;
303
+ }
304
+ const ret = result;
305
+ return ret;
306
+ }
307
+ export function __wbg_instanceof_Object_72ee0c53dd8f0726(arg0) {
308
+ let result;
309
+ try {
310
+ result = arg0 instanceof Object;
311
+ } catch (_) {
312
+ result = false;
313
+ }
314
+ const ret = result;
315
+ return ret;
316
+ }
317
+ export function __wbg_isArray_db61795ad004c139(arg0) {
318
+ const ret = Array.isArray(arg0);
319
+ return ret;
320
+ }
321
+ export function __wbg_length_6e821edde497a532(arg0) {
322
+ const ret = arg0.length;
323
+ return ret;
324
+ }
325
+ export function __wbg_new_47dfee3855dec446(arg0, arg1) {
326
+ const ret = new TypeError(getStringFromWasm0(arg0, arg1));
327
+ return ret;
328
+ }
329
+ export function __wbg_new_4f9fafbb3909af72() {
330
+ const ret = new Object();
331
+ return ret;
332
+ }
333
+ export function __wbg_parse_545d11396395fbbd() { return handleError(function (arg0, arg1) {
334
+ const ret = JSON.parse(getStringFromWasm0(arg0, arg1));
335
+ return ret;
336
+ }, arguments); }
337
+ export function __wbg_set_8ee2d34facb8466e() { return handleError(function (arg0, arg1, arg2) {
338
+ const ret = Reflect.set(arg0, arg1, arg2);
339
+ return ret;
340
+ }, arguments); }
341
+ export function __wbindgen_cast_0000000000000001(arg0, arg1) {
342
+ // Cast intrinsic for `Ref(String) -> Externref`.
343
+ const ret = getStringFromWasm0(arg0, arg1);
344
+ return ret;
345
+ }
346
+ export function __wbindgen_init_externref_table() {
347
+ const table = wasm.__wbindgen_externrefs;
348
+ const offset = table.grow(4);
349
+ table.set(0, undefined);
350
+ table.set(offset + 0, undefined);
351
+ table.set(offset + 1, null);
352
+ table.set(offset + 2, true);
353
+ table.set(offset + 3, false);
354
+ }
355
+ function addToExternrefTable0(obj) {
356
+ const idx = wasm.__externref_table_alloc();
357
+ wasm.__wbindgen_externrefs.set(idx, obj);
358
+ return idx;
359
+ }
360
+
361
+ function getArrayJsValueFromWasm0(ptr, len) {
362
+ ptr = ptr >>> 0;
363
+ const mem = getDataViewMemory0();
364
+ const result = [];
365
+ for (let i = ptr; i < ptr + 4 * len; i += 4) {
366
+ result.push(wasm.__wbindgen_externrefs.get(mem.getUint32(i, true)));
367
+ }
368
+ wasm.__externref_drop_slice(ptr, len);
369
+ return result;
370
+ }
371
+
372
+ let cachedDataViewMemory0 = null;
373
+ function getDataViewMemory0() {
374
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
375
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
376
+ }
377
+ return cachedDataViewMemory0;
378
+ }
379
+
380
+ function getStringFromWasm0(ptr, len) {
381
+ ptr = ptr >>> 0;
382
+ return decodeText(ptr, len);
383
+ }
384
+
385
+ let cachedUint8ArrayMemory0 = null;
386
+ function getUint8ArrayMemory0() {
387
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
388
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
389
+ }
390
+ return cachedUint8ArrayMemory0;
391
+ }
392
+
393
+ function handleError(f, args) {
394
+ try {
395
+ return f.apply(this, args);
396
+ } catch (e) {
397
+ const idx = addToExternrefTable0(e);
398
+ wasm.__wbindgen_exn_store(idx);
399
+ }
400
+ }
401
+
402
+ function isLikeNone(x) {
403
+ return x === undefined || x === null;
404
+ }
405
+
406
+ function passStringToWasm0(arg, malloc, realloc) {
407
+ if (realloc === undefined) {
408
+ const buf = cachedTextEncoder.encode(arg);
409
+ const ptr = malloc(buf.length, 1) >>> 0;
410
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
411
+ WASM_VECTOR_LEN = buf.length;
412
+ return ptr;
413
+ }
414
+
415
+ let len = arg.length;
416
+ let ptr = malloc(len, 1) >>> 0;
417
+
418
+ const mem = getUint8ArrayMemory0();
419
+
420
+ let offset = 0;
421
+
422
+ for (; offset < len; offset++) {
423
+ const code = arg.charCodeAt(offset);
424
+ if (code > 0x7F) break;
425
+ mem[ptr + offset] = code;
426
+ }
427
+ if (offset !== len) {
428
+ if (offset !== 0) {
429
+ arg = arg.slice(offset);
430
+ }
431
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
432
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
433
+ const ret = cachedTextEncoder.encodeInto(arg, view);
434
+
435
+ offset += ret.written;
436
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
437
+ }
438
+
439
+ WASM_VECTOR_LEN = offset;
440
+ return ptr;
441
+ }
442
+
443
+ function takeFromExternrefTable0(idx) {
444
+ const value = wasm.__wbindgen_externrefs.get(idx);
445
+ wasm.__externref_table_dealloc(idx);
446
+ return value;
447
+ }
448
+
449
+ let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
450
+ cachedTextDecoder.decode();
451
+ const MAX_SAFARI_DECODE_BYTES = 2146435072;
452
+ let numBytesDecoded = 0;
453
+ function decodeText(ptr, len) {
454
+ numBytesDecoded += len;
455
+ if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
456
+ cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
457
+ cachedTextDecoder.decode();
458
+ numBytesDecoded = len;
459
+ }
460
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
461
+ }
462
+
463
+ const cachedTextEncoder = new TextEncoder();
464
+
465
+ if (!('encodeInto' in cachedTextEncoder)) {
466
+ cachedTextEncoder.encodeInto = function (arg, view) {
467
+ const buf = cachedTextEncoder.encode(arg);
468
+ view.set(buf);
469
+ return {
470
+ read: arg.length,
471
+ written: buf.length
472
+ };
473
+ };
474
+ }
475
+
476
+ let WASM_VECTOR_LEN = 0;
477
+
478
+
479
+ let wasm;
480
+ export function __wbg_set_wasm(val) {
481
+ wasm = val;
482
+ }
Binary file
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@markup-carve/carve-wasm",
3
+ "type": "module",
4
+ "description": "WASM bindings for the Carve parser and HTML renderer.",
5
+ "version": "0.1.0",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/markup-carve/carve-wasm"
10
+ },
11
+ "files": [
12
+ "carve_wasm_bg.wasm",
13
+ "carve_wasm.js",
14
+ "carve_wasm_bg.js",
15
+ "carve_wasm.d.ts"
16
+ ],
17
+ "main": "carve_wasm.js",
18
+ "homepage": "https://markup-carve.github.io/carve/",
19
+ "types": "carve_wasm.d.ts",
20
+ "sideEffects": [
21
+ "./carve_wasm.js",
22
+ "./snippets/*"
23
+ ]
24
+ }