{"id":51557347,"url":"https://github.com/sebastienrousseau/noyalib-wasm","last_synced_at":"2026-07-10T06:31:43.829Z","repository":{"id":368938533,"uuid":"1287542250","full_name":"sebastienrousseau/noyalib-wasm","owner":"sebastienrousseau","description":"WebAssembly (wasm-bindgen) bindings for the noyalib YAML library — browser-ready parse / serialise / lossless edit.","archived":false,"fork":false,"pushed_at":"2026-07-02T21:13:40.000Z","size":251,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-02T23:12:26.078Z","etag":null,"topics":["browser","noyalib","parser","rust","serde","serialization","wasm","wasm-bindgen","webassembly","yaml","zero-unsafe"],"latest_commit_sha":null,"homepage":"https://docs.rs/noyalib-wasm","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sebastienrousseau.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-02T19:35:27.000Z","updated_at":"2026-07-02T19:36:43.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sebastienrousseau/noyalib-wasm","commit_stats":null,"previous_names":["sebastienrousseau/noyalib-wasm"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/sebastienrousseau/noyalib-wasm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sebastienrousseau%2Fnoyalib-wasm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sebastienrousseau%2Fnoyalib-wasm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sebastienrousseau%2Fnoyalib-wasm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sebastienrousseau%2Fnoyalib-wasm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sebastienrousseau","download_url":"https://codeload.github.com/sebastienrousseau/noyalib-wasm/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sebastienrousseau%2Fnoyalib-wasm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35323809,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-10T02:00:06.465Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["browser","noyalib","parser","rust","serde","serialization","wasm","wasm-bindgen","webassembly","yaml","zero-unsafe"],"created_at":"2026-07-10T06:31:43.716Z","updated_at":"2026-07-10T06:31:43.813Z","avatar_url":"https://github.com/sebastienrousseau.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- SPDX-License-Identifier: Apache-2.0 OR MIT --\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://cloudcdn.pro/noyalib/v1/logos/noyalib.svg\" alt=\"Noyalib logo\" width=\"128\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003enoyalib-wasm\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003e\u003ccode\u003ewasm-bindgen\u003c/code\u003e wrapper around noyalib —\n  pure-Rust YAML 1.2, zero \u003ccode\u003eunsafe\u003c/code\u003e, ~338 KB after\n  LTO. Runs in browsers, Node, Cloudflare Workers, Deno, and\n  any other WASM-capable host.\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/sebastienrousseau/noyalib/actions\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/sebastienrousseau/noyalib/ci.yml?style=for-the-badge\u0026logo=github\" alt=\"Build\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/@sebastienrousseau/noyalib-wasm\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/@sebastienrousseau/noyalib-wasm?style=for-the-badge\u0026color=fc8d62\u0026logo=npm\" alt=\"npm\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://docs.rs/noyalib-wasm\"\u003e\u003cimg src=\"https://img.shields.io/badge/docs.rs-noyalib--wasm-66c2a5?style=for-the-badge\u0026labelColor=555555\u0026logo=docs.rs\" alt=\"Docs.rs\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://bundlephobia.com/package/@sebastienrousseau/noyalib-wasm\"\u003e\u003cimg src=\"https://img.shields.io/bundlephobia/minzip/@sebastienrousseau/noyalib-wasm?style=for-the-badge\u0026color=informational\" alt=\"Bundle size\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://scorecard.dev/viewer/?uri=github.com/sebastienrousseau/noyalib\"\u003e\u003cimg src=\"https://img.shields.io/ossf-scorecard/github.com/sebastienrousseau/noyalib?style=for-the-badge\u0026label=OpenSSF%20Scorecard\u0026logo=openssf\" alt=\"OpenSSF Scorecard\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## Contents\n\n- [Install](#install) — npm, build from source\n- [Quick Start](#quick-start) — parse, edit, validate\n- [Why this approach?](#why-this-approach) — vs `js-yaml`\n- [Surface](#surface) — exported APIs\n- [Bundle size](#bundle-size) — what you ship to users\n- [Targets](#targets) — every wasm-pack flavour\n- [Provenance](#provenance) — npm + cosign\n- [Examples](#examples) — Node + browser demos\n- [When not to use noyalib-wasm](#when-not-to-use-noyalib-wasm)\n- [Documentation](#documentation)\n- [License](#license)\n\n---\n\n## Install\n\n```sh\nnpm install @sebastienrousseau/noyalib-wasm\n# or\npnpm add @sebastienrousseau/noyalib-wasm\n# or\nyarn add @sebastienrousseau/noyalib-wasm\n```\n\nOr build from source against any wasm-pack target:\n\n```sh\ngit clone https://github.com/sebastienrousseau/noyalib-wasm\ncd noyalib-wasm\nwasm-pack build --release --target bundler\n```\n\n\u003e **Split from the monorepo since v0.0.12.** Prior versions\n\u003e shipped from `sebastienrousseau/noyalib/crates/noyalib-wasm/`.\n\u003e From v0.0.12 onward `noyalib-wasm` lives here as its own crate,\n\u003e released in strict lockstep with the parent\n\u003e [`noyalib`](https://github.com/sebastienrousseau/noyalib) at\n\u003e the same version. See\n\u003e [ADR-0005](https://github.com/sebastienrousseau/noyalib/blob/main/doc/adr/0005-workspace-split.md)\n\u003e for the rationale and rollback recipe.\n\n---\n\n## Quick Start\n\n```js\nimport init, {\n  parse,\n  stringify,\n  validateJson,\n  getPath,\n  merge,\n  WasmDocument,\n} from \"@sebastienrousseau/noyalib-wasm\";\n\nawait init();          // load the WASM blob\n\n// Plain parse / stringify — like js-yaml. Mappings come back as\n// plain JS Objects (not `Map`), so dotted property access works.\nconst obj = parse(\"host: api.example.com\\nport: 8080\\n\");\nconsole.log(obj.host); // \"api.example.com\"\nconst yaml = stringify(obj);\n\n// Indexed read without going through `parse`.\nconst port = getPath(\"host: api.example.com\\nport: 8080\\n\", \"port\"); // 8080\n\n// JSON-compatible YAML 1.2 schema check.\nvalidateJson(\"a: 1\\nb: [2, 3]\\n\"); // true\n\n// Lossless CST edit — comments + indentation preserved.\nconst doc = new WasmDocument(source);\ndoc.set(\"server.port\", \"9090\");\nfs.writeFileSync(\"config.yaml\", doc.toString());\n```\n\n---\n\n## Why this approach?\n\n[`js-yaml`](https://github.com/nodeca/js-yaml) is the de-facto\nJS YAML parser, and it's good — but it makes two tradeoffs that\nhurt for editor and tooling workloads:\n\n1. **`js-yaml` discards comments by spec.** It implements the\n   YAML data model, which excludes comments. Round-tripping a\n   document through `parse` → `dump` strips every `#` line.\n   noyalib's `Document` API runs through a lossless CST that\n   reproduces the source byte-for-byte; only the surgically\n   touched span changes on a `set`.\n\n2. **`js-yaml` follows YAML 1.1 by default.** That's the\n   \"Norway problem\": `country: NO` parses as `country: false`,\n   silently rewriting the country code. noyalib defaults to\n   YAML 1.2 strict semantics; only `true` / `false` are\n   booleans.\n\n### Custom YAML tags\n\n`parse(yaml)` surfaces YAML tags as plain JS object keys:\n`!Color '#ff8800'` deserialises into `{ \"!Color\": \"#ff8800\" }`.\nThis matches the serde-bridge convention every other\n`serde-wasm-bindgen` consumer uses (the `Value::Tagged` variant\nserialises as a single-entry map for cross-format interop).\nRound-tripping via `stringify` does **not** restore the\nYAML-tag prefix — the JS object's tag-as-key shape becomes a\nquoted mapping key in the emitted YAML.\n\nFor editor / tooling workloads where the YAML-tag wire form\nmust survive a parse → emit cycle, use the `WasmDocument`\nclass instead. Its `set` / `setValue` are surgical edits\nthrough the CST, so untouched tag prefixes round-trip\nverbatim:\n\n```js\nconst doc = new WasmDocument(\"color: !Color '#ff8800'\\n\");\ndoc.set(\"color\", \"!Color '#00aaff'\");           // tag survives\nconsole.log(doc.toString());                    // \"color: !Color '#00aaff'\\n\"\n```\n\nOther differences worth knowing about:\n\n- **JSON Schema 2020-12 validation built in.** Same engine as\n  the `noyavalidate` CLI ships.\n- **Pure-Rust, zero `unsafe`.** Every byte of the parser,\n  scanner, formatter, and CST is checked at compile time by the\n  workspace `#![forbid(unsafe_code)]` lint.\n- **~338 KB bundle.** That's roughly the same size as `js-yaml`\n  minified + gzipped, with the lossless-CST surface and YAML\n  1.2 semantics baked in.\n\n---\n\n## Surface\n\nAll exports are camelCase, matching JS conventions.\n\n### Free functions\n\n| Export | What it does |\n|---|---|\n| `parse(yaml: string): any` | Parse a YAML document into a JS value. Mappings become plain Objects; sequences become Arrays; scalars become numbers / strings / booleans / null. Mirrors `js-yaml`'s `load`. |\n| `stringify(value: any): string` | Serialise a JS value back to YAML. |\n| `validateJson(yaml: string): boolean` | Validate that the document conforms to the YAML 1.2 JSON-compatible schema (only types JSON allows: null / bool / number / string / array / object). Returns `true` / `false`; structural JSON Schema 2020-12 validation is on the `noyavalidate` CLI roadmap. |\n| `getPath(yaml: string, path: string): any` | Indexed read without going through `parse`. Dotted paths (`\"server.host\"`); returns `null` if missing. |\n| `merge(base: string, override: string): string` | Deep-merge two YAML documents. Delegates to `noyalib::Value::merge`. |\n\n### `WasmDocument` class — lossless CST\n\nConstruct with `new WasmDocument(yaml)`. Every method preserves\ncomments and formatting around untouched spans byte-faithfully.\n\n| Method | What it does |\n|---|---|\n| `toString(): string` | Re-emit. Byte-identical to the parsed source if no edits were made. |\n| `get(path: string): any` | Parsed value at a dotted path. Returns `null` if missing. |\n| `getSource(path: string): string \\| null` | Raw source fragment at a dotted path (no re-quoting / canonicalisation). |\n| `set(path: string, fragment: string): void` | Surgically rewrite a value at a dotted path. The fragment is a YAML-shaped string (`\"9090\"`, `\"[1,2,3]\"`, …). |\n| `setValue(path: string, value: any): void` | Same as `set` but accepts a JS value instead of a YAML fragment. |\n| `spanAt(path: string): { start: number, end: number } \\| null` | Byte range of the value at a dotted path. |\n| `commentsAt(path: string): { before: string[], inline: string \\| null }` | Comments associated with the node at a path. |\n| `replaceSpan(start: number, end: number, replacement: string): void` | Primitive byte replacement. |\n\nEvery function is `async` only via `init()` — once the WASM\nblob is loaded, individual calls are synchronous.\n\n---\n\n## Bundle size\n\n| Build | Size (raw) | Size (gzip) |\n|---|---|---|\n| Default (`wasm-pack build --release --target bundler`) | ~338 KB | ~140 KB |\n| `--features wasm-opt` (post-build pass) | ~280 KB | ~115 KB |\n\nTree-shaking-friendly — the `Document` API and the plain\n`parse` / `stringify` API are independent modules; bundlers\ndrop whichever your code does not import.\n\nFor comparison: `js-yaml` 4.x lands around ~50 KB minified +\n~12 KB gzipped, but does not provide lossless-CST or schema\nvalidation.\n\n---\n\n## Targets\n\n`wasm-pack build` supports every target wasm-bindgen does:\n\n```sh\nwasm-pack build --target bundler    # webpack, rollup, esbuild\nwasm-pack build --target web        # native ES module via \u003cscript type=\"module\"\u003e\nwasm-pack build --target nodejs     # commonjs Node import\nwasm-pack build --target deno       # Deno-native module\nwasm-pack build --target no-modules # plain global, no module loader\n```\n\nCloudflare Workers and edge runtimes generally consume the\n`bundler` target via their packaging step.\n\n---\n\n## Provenance\n\nEvery release on npm carries an\n[npm provenance attestation](https://docs.npmjs.com/generating-provenance-statements)\nlinking the published bundle to the GitHub Actions run that\nproduced it. Verify via:\n\n```sh\nnpm view @sebastienrousseau/noyalib-wasm provenance\n```\n\nThe underlying `.wasm` is also signed with cosign keyless\nalongside every release; the verify command is identical to\nthe source crate's:\n\n```sh\ncosign verify-blob \\\n  --certificate-identity-regexp 'https://github.com/sebastienrousseau/noyalib/' \\\n  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \\\n  --certificate noyalib_wasm_bg.wasm.pem \\\n  --signature   noyalib_wasm_bg.wasm.sig \\\n  noyalib_wasm_bg.wasm\n```\n\nFull cookbook: [`pkg/VERIFY.md`](https://github.com/sebastienrousseau/noyalib/blob/main/pkg/VERIFY.md).\n\n---\n\n## Examples\n\nBrowser + Node demos under\n[`crates/noyalib-wasm/examples/`](examples/):\n\n| Path | Target | What it shows |\n|---|---|---|\n| [`node-stringify.js`](examples/node-stringify.js) | Node | `parse` + `stringify` round-trip. |\n| [`cst-edit.js`](examples/cst-edit.js) | Node | Lossless CST edit; comments + whitespace preserved. |\n| [`schema-validate.js`](examples/schema-validate.js) | Node | JSON Schema 2020-12 validation, good and bad cases. |\n| [`browser/index.html`](examples/browser/index.html) | Browser | Live in-page YAML editor with a parsed-JSON pane. |\n\n```bash\n# Node:\nwasm-pack build --release --target nodejs crates/noyalib-wasm\nnode crates/noyalib-wasm/examples/cst-edit.js\n\n# Browser:\nwasm-pack build --release --target web crates/noyalib-wasm\ncd crates/noyalib-wasm/examples/browser\npython3 -m http.server     # or any static-file server\n```\n\n---\n\n## When not to use noyalib-wasm\n\n- **You only ever consume YAML in Node and don't care about\n  comment-preserving edits or YAML 1.2 strictness.** `js-yaml`\n  is smaller (~50 KB minified) and the de-facto standard;\n  reach for it first.\n- **You need a streaming parser for multi-GB documents.** The\n  WASM bindings always read the full document into memory.\n  For TB-scale streaming workloads, drive the noyalib library\n  directly from a Rust process and pipe results out.\n\n---\n\n## Compatibility\n\n**MSRV: Rust 1.85.0** stable. The `wasm-bindgen` 0.2 ecosystem\nfloors the toolchain at 1.85; the core `noyalib` library\nitself stays at 1.75. CI verifies the floor on every PR via\nthe `Per-crate MSRV` workflow job. The bump policy lives in\n[`doc/POLICIES.md`](https://github.com/sebastienrousseau/noyalib/blob/main/doc/POLICIES.md#1-msrv-minimum-supported-rust-version).\n\n**Tier-1 WASM targets** (CI-verified each PR via\n`wasm-pack test --node`): `wasm32-unknown-unknown` produced\nunder every `wasm-pack` mode — `bundler` (Webpack, Rollup,\nesbuild, Vite), `web` (native ES module), `nodejs` (CJS),\n`deno`, `no-modules`. Cloudflare Workers, Deno, and Bun\nconsume the `bundler` target.\n\n---\n\n## Documentation\n\n- **Engineering policies** (MSRV, SemVer, security, performance, concurrency, platform support, feature flags):\n  [`doc/POLICIES.md`](https://github.com/sebastienrousseau/noyalib/blob/main/doc/POLICIES.md)\n- **Security policy**:\n  [`SECURITY.md`](https://github.com/sebastienrousseau/noyalib/blob/main/SECURITY.md)\n- **JS API reference**:\n  [`doc/js-api.md`](https://github.com/sebastienrousseau/noyalib/blob/main/crates/noyalib-wasm/doc/js-api.md)\n- **Bundling (Vite, Webpack, Next.js, Cloudflare Workers, Deno, Bun)**:\n  [`doc/bundling.md`](https://github.com/sebastienrousseau/noyalib/blob/main/crates/noyalib-wasm/doc/bundling.md)\n- **npm package**:\n  \u003chttps://www.npmjs.com/package/@sebastienrousseau/noyalib-wasm\u003e\n- **API reference (rustdoc)**: \u003chttps://docs.rs/noyalib-wasm\u003e\n- **Workspace README**:\n  \u003chttps://github.com/sebastienrousseau/noyalib#readme\u003e\n\n---\n\n## License\n\nDual-licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0)\nor [MIT](https://opensource.org/licenses/MIT), at your option.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsebastienrousseau%2Fnoyalib-wasm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsebastienrousseau%2Fnoyalib-wasm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsebastienrousseau%2Fnoyalib-wasm/lists"}