{"_id":"@aahoughton/oav-stream-validator","_rev":"5-696ebc63803ddbc15717c513595f310f","name":"@aahoughton/oav-stream-validator","dist-tags":{"experimental":"0.2.0","latest":"1.1.0"},"versions":{"0.1.0":{"name":"@aahoughton/oav-stream-validator","version":"0.1.0","keywords":["json-schema","json-schema-2020-12","openapi","openapi-3.0","openapi-3.1","openapi-3.2","sax","stream","streaming","validation","validator"],"author":{"name":"Andrew Houghton","email":"aah@roarmouse.org"},"license":"MIT","_id":"@aahoughton/oav-stream-validator@0.1.0","maintainers":[{"name":"aahoughton","email":"aah@roarmouse.org"}],"homepage":"https://github.com/aahoughton/oav#readme","bugs":{"url":"https://github.com/aahoughton/oav/issues"},"//":"Incubating: published to the `experimental` dist-tag (publishConfig.tag), not `latest`, and versioned independently of the lockstep oav-core family. No prepublishOnly: release.yml runs `pnpm test` from root before publish.","dist":{"shasum":"e2dce60b27eda50b80a43d6b1b2dd765b8616871","tarball":"https://registry.npmjs.org/@aahoughton/oav-stream-validator/-/oav-stream-validator-0.1.0.tgz","fileCount":9,"integrity":"sha512-93HhhDSDpqTfGOAg4d1b4fMs68H29DizNg7H/SiMgwlAxjzhS4auYs4OKCS4qOYNUj7cXNa3xjtw5GOB49/t/g==","signatures":[{"sig":"MEQCIDdnbW1rw7+q0jsgSevAnGhu6RTYhBPV28p1iRP2Y0RjAiA9wu4g9edBdHe20TigmtTfTsOQ1Told/PfTmLI+Oet0Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":629141},"main":"./dist/index.cjs","type":"module","_from":"file:aahoughton-oav-stream-validator-0.1.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=22"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc -b"},"_npmUser":{"name":"aahoughton","email":"aah@roarmouse.org"},"_resolved":"/Users/aah/personal/oav/packages/stream-validator/aahoughton-oav-stream-validator-0.1.0.tgz","_integrity":"sha512-93HhhDSDpqTfGOAg4d1b4fMs68H29DizNg7H/SiMgwlAxjzhS4auYs4OKCS4qOYNUj7cXNa3xjtw5GOB49/t/g==","repository":{"url":"git+https://github.com/aahoughton/oav.git","type":"git","directory":"packages/stream-validator"},"_npmVersion":"10.9.4","description":"Streaming JSON Schema 2020-12 validator for @aahoughton/oav-core. Validates a JSON document against a resolved schema as it streams, echoing bytes through unchanged while reporting violations on a side channel; memory stays bounded for forward-decidable s","directories":{},"sideEffects":false,"_nodeVersion":"22.22.0","dependencies":{"@aahoughton/oav-core":"3.4.0"},"publishConfig":{"tag":"experimental","access":"public","provenance":true},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/oav-stream-validator_0.1.0_1781999601420_0.19406317889911495","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Renamed to @oaverify/stream."},"0.2.0":{"name":"@aahoughton/oav-stream-validator","version":"0.2.0","keywords":["json-schema","json-schema-2020-12","json-schema-validator","oas","openapi","openapi-3.0","openapi-3.1","openapi-3.2","openapi-validator","sax","stream","streaming","validation","validator"],"author":{"name":"Andrew Houghton","email":"aah@roarmouse.org"},"license":"MIT","_id":"@aahoughton/oav-stream-validator@0.2.0","maintainers":[{"name":"aahoughton","email":"aah@roarmouse.org"}],"homepage":"https://github.com/aahoughton/oav#readme","bugs":{"url":"https://github.com/aahoughton/oav/issues"},"//":"Incubating: published to the `experimental` dist-tag (publishConfig.tag), not `latest`, and versioned independently of the lockstep oav-core family. No prepublishOnly: release.yml runs `pnpm test` from root before publish.","dist":{"shasum":"d73e268bd6c09dcba272b2b8b7ab96ef3c203526","tarball":"https://registry.npmjs.org/@aahoughton/oav-stream-validator/-/oav-stream-validator-0.2.0.tgz","fileCount":9,"integrity":"sha512-MSZv96Ugko/lEmWRTz+SfbF1lKvJvq4m84Bbh4UlxMzic/fXRxZNkXTdquCWyayD2tSTo1sJRWQEFlG+xlUrpQ==","signatures":[{"sig":"MEUCIQCDiHrMmA8SgYS6Fk8gbYtqTpXQHiiEzyTcpK65ozOOLgIgTQi8FFzOluXS3PyvZGsOSQvuZiQvTEu3nSIhaEA4atA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aahoughton%2foav-stream-validator@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":706969},"main":"./dist/index.cjs","type":"module","_from":"file:pkgs/aahoughton-oav-stream-validator-0.2.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=22"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc -b"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:95242dcb-1cc4-4710-867d-33e275c580f0"}},"_resolved":"/home/runner/work/oav/oav/pkgs/aahoughton-oav-stream-validator-0.2.0.tgz","_integrity":"sha512-MSZv96Ugko/lEmWRTz+SfbF1lKvJvq4m84Bbh4UlxMzic/fXRxZNkXTdquCWyayD2tSTo1sJRWQEFlG+xlUrpQ==","repository":{"url":"git+https://github.com/aahoughton/oav.git","type":"git","directory":"packages/stream-validator"},"_npmVersion":"11.13.0","description":"Streaming OpenAPI / JSON Schema 2020-12 validator. Validates a JSON body as it streams with bounded memory for multi-GB payloads, echoing bytes through unchanged while reporting violations on a side channel. Built on @aahoughton/oav-core.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","dependencies":{"@aahoughton/oav-core":"3.5.0"},"publishConfig":{"tag":"experimental","access":"public","provenance":true},"_hasShrinkwrap":false,"readmeFilename":"README.md","_npmOperationalInternal":{"tmp":"tmp/oav-stream-validator_0.2.0_1782139853790_0.45927216822073746","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Renamed to @oaverify/stream."},"1.0.0":{"name":"@aahoughton/oav-stream-validator","version":"1.0.0","keywords":["json-schema","json-schema-2020-12","json-schema-validator","oas","openapi","openapi-3.0","openapi-3.1","openapi-3.2","openapi-validator","sax","stream","streaming","validation","validator"],"author":{"name":"Andrew Houghton","email":"aah@roarmouse.org"},"license":"MIT","_id":"@aahoughton/oav-stream-validator@1.0.0","maintainers":[{"name":"aahoughton","email":"aah@roarmouse.org"}],"homepage":"https://github.com/aahoughton/oav#readme","bugs":{"url":"https://github.com/aahoughton/oav/issues"},"//":"Published to the default `latest` dist-tag, versioned independently of the lockstep oav-core family (its own 1.x line, semver from 1.0). No prepublishOnly: release.yml runs `pnpm test` from root before publish.","dist":{"shasum":"13f7a478e28b7bcba53717169fc03d25342b5752","tarball":"https://registry.npmjs.org/@aahoughton/oav-stream-validator/-/oav-stream-validator-1.0.0.tgz","fileCount":7,"integrity":"sha512-DNiPufhY0CE0h7mBTghB6H2Ss7bS4/3DVqNGHQJCmZjWIN0oEHiiYWEilp1LjywwnCDlrjl0+jZxWWvttQ2eyw==","signatures":[{"sig":"MEYCIQDhO5vh1Yd4mOVU8LKpNFi6wK5YtiKics5tKadNabbkMgIhANHqPCtX1gcpCjR22+EMKJNjjdZtuB/OzFH6GnDmVjQE","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aahoughton%2foav-stream-validator@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":362305},"main":"./dist/index.cjs","type":"module","_from":"file:pkgs/aahoughton-oav-stream-validator-1.0.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=22"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc -b"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:95242dcb-1cc4-4710-867d-33e275c580f0"}},"_resolved":"/home/runner/work/oav/oav/pkgs/aahoughton-oav-stream-validator-1.0.0.tgz","_integrity":"sha512-DNiPufhY0CE0h7mBTghB6H2Ss7bS4/3DVqNGHQJCmZjWIN0oEHiiYWEilp1LjywwnCDlrjl0+jZxWWvttQ2eyw==","repository":{"url":"git+https://github.com/aahoughton/oav.git","type":"git","directory":"packages/stream-validator"},"_npmVersion":"11.13.0","description":"Streaming OpenAPI / JSON Schema 2020-12 validator. Validates a JSON body as it streams with bounded memory for multi-GB payloads, echoing bytes through unchanged while reporting violations on a side channel. Built on @aahoughton/oav-core.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","dependencies":{"@aahoughton/oav-core":"3.6.0"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/oav-stream-validator_1.0.0_1782314717607_0.4428555454169938","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Renamed to @oaverify/stream."},"1.1.0":{"name":"@aahoughton/oav-stream-validator","version":"1.1.0","keywords":["json-schema","json-schema-2020-12","json-schema-validator","oas","openapi","openapi-3.0","openapi-3.1","openapi-3.2","openapi-validator","sax","stream","streaming","validation","validator"],"author":{"name":"Andrew Houghton","email":"aah@roarmouse.org"},"license":"MIT","_id":"@aahoughton/oav-stream-validator@1.1.0","maintainers":[{"name":"aahoughton","email":"aah@roarmouse.org"}],"homepage":"https://github.com/aahoughton/oav#readme","bugs":{"url":"https://github.com/aahoughton/oav/issues"},"//":"Published to the default `latest` dist-tag, versioned independently of the lockstep oav-core family (its own 1.x line, semver from 1.0). No prepublishOnly: release.yml runs `pnpm test` from root before publish.","dist":{"shasum":"d0eeac23876bbad2258b50ca23a50c7517ad09b2","tarball":"https://registry.npmjs.org/@aahoughton/oav-stream-validator/-/oav-stream-validator-1.1.0.tgz","fileCount":7,"integrity":"sha512-PT09ryjUEisLOH5xKvmtEC3wRQZw1V0lE2eIyiE3RuHqsr2qhZjSFs9e09s7cTbsDpEkT34Z1fIsug3At096tA==","signatures":[{"sig":"MEYCIQCEnCsbwJEsYHksRXkCYDrukEIRvU+cr70ANIPq9cQztQIhAPvECT/ehXRkKeD5m8J6oVx+fFdhFZRrORwQyo+6pB/a","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aahoughton%2foav-stream-validator@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":414004},"main":"./dist/index.cjs","type":"module","_from":"file:pkgs/aahoughton-oav-stream-validator-1.1.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=22"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc -b"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:95242dcb-1cc4-4710-867d-33e275c580f0"}},"_resolved":"/home/runner/work/oav/oav/pkgs/aahoughton-oav-stream-validator-1.1.0.tgz","_integrity":"sha512-PT09ryjUEisLOH5xKvmtEC3wRQZw1V0lE2eIyiE3RuHqsr2qhZjSFs9e09s7cTbsDpEkT34Z1fIsug3At096tA==","repository":{"url":"git+https://github.com/aahoughton/oav.git","type":"git","directory":"packages/stream-validator"},"_npmVersion":"11.13.0","description":"Streaming OpenAPI / JSON Schema 2020-12 validator. Validates a JSON body as it streams with bounded memory for multi-GB payloads, echoing bytes through unchanged while reporting violations on a side channel. Built on @aahoughton/oav-core.","directories":{},"sideEffects":false,"_nodeVersion":"24.17.0","dependencies":{"@aahoughton/oav-core":"3.7.0"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/oav-stream-validator_1.1.0_1782425658851_0.9427776293962589","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Renamed to @oaverify/stream."}},"time":{"created":"2026-06-20T23:53:21.325Z","modified":"2026-07-28T04:31:02.573Z","0.1.0":"2026-06-20T23:53:21.587Z","0.2.0":"2026-06-22T14:50:53.966Z","1.0.0":"2026-06-24T15:25:17.778Z","1.1.0":"2026-06-25T22:14:18.979Z"},"bugs":{"url":"https://github.com/aahoughton/oav/issues"},"author":{"name":"Andrew Houghton","email":"aah@roarmouse.org"},"license":"MIT","homepage":"https://github.com/aahoughton/oav#readme","keywords":["json-schema","json-schema-2020-12","json-schema-validator","oas","openapi","openapi-3.0","openapi-3.1","openapi-3.2","openapi-validator","sax","stream","streaming","validation","validator"],"repository":{"url":"git+https://github.com/aahoughton/oav.git","type":"git","directory":"packages/stream-validator"},"description":"Streaming OpenAPI / JSON Schema 2020-12 validator. Validates a JSON body as it streams with bounded memory for multi-GB payloads, echoing bytes through unchanged while reporting violations on a side channel. Built on @aahoughton/oav-core.","maintainers":[{"name":"aahoughton","email":"aah@roarmouse.org"}],"readme":"# oav-stream-validator\n\nA streaming JSON Schema 2020-12 validator for\n[`oav-core`](https://www.npmjs.com/package/@aahoughton/oav-core). It\nvalidates a JSON document against a resolved schema **as it streams**,\nechoing the input bytes through unchanged while reporting violations on a\nside channel. Memory is bounded for forward-decidable schemas with\nstructural bounds (or configured caps), so multi-GB request bodies\nvalidate without materializing in heap.\n\nThis is a second engine, not a mode of the in-memory validator.\n`oav-core`'s compiler is pull-based over a fully-parsed value; this engine\nis push-based over a token stream. It reuses `oav-core`'s in-memory\nvalidator for the subtrees a compile-time classifier marks BUFFER (so\n`format` assertion runs in that delegate, against the formats you register\nthrough the `formats` option; no format library is bundled by default),\nand reuses its flat error model.\n\nThin: this package bundles nothing from `oav-core`. It declares\n`@aahoughton/oav-core` as a regular dependency, so installing the stream\nvalidator pulls the engine it delegates to along with it.\n\n```bash\nnpm install @aahoughton/oav-stream-validator\n```\n\n> **Versioned independently of the `oav-core` family.** This package tracks\n> its own version line rather than the lockstep `oav-core` version, and\n> follows semver from `1.0` (a breaking change bumps the major). The public\n> surface is small and additive-by-design.\n\n## Usage\n\n```ts\nimport { pipeline } from \"node:stream/promises\";\nimport { createStreamValidator } from \"@aahoughton/oav-stream-validator\";\n\nconst validator = createStreamValidator(schema); // throws here if the schema can't be streamed\n\ntry {\n  await pipeline(request, validator, fs.createWriteStream(tmp));\n  await rename(tmp, final); // reached only on a clean finish = valid\n} catch (err) {\n  // ValidationFailedError (well-formed but invalid) or a parse / I/O error\n  await unlink(tmp).catch(() => {});\n}\n\n// Or observe the side channel directly:\nvalidator.on(\"violation\", (v) => console.warn(v.code, v.path, v.byteOffset));\nconst verdict = await validator.result; // { valid, violations, peakBufferedBytes }\n```\n\nOutput bytes are the input verbatim (provisional until a clean finish).\nThe default policy is `terminate` with `maxErrors: 1` (the first violation\ndestroys the stream and rejects the `pipeline`); `detach` instead seals\nthe verdict and raw-copies the tail.\n\nCount and length limits resolve as early as the input allows: an\n**over-limit** (`maxItems`, `maxProperties`, `maxLength`) fails at the\noffending element / key / code point, before the rest of the value\nstreams, so under `terminate` an over-count body is rejected without\nechoing its tail downstream. An **under-limit** (`minItems`,\n`minProperties`, `minLength`) can only be known once the scope closes, so\nit reports at the closing delimiter. The verdict is identical either way;\neager enforcement only moves _when_ the violation surfaces (and its byte\noffset points at the cause rather than the delimiter).\n\n### Supported schemas\n\nThe STREAM keyword set (`type`, scalar/string/number constraints,\n`properties` / `items` / `required` / bounds / `propertyNames` /\n`dependentRequired`, `$ref` recursion, boolean schemas) validates on the\nforward spine in one pass. Forward composition (`allOf` / `anyOf` /\n`oneOf` / `not` / `if`, all branches forward) **TEEs**: the value's events\nfan out to one forward sub-spine per branch, so a composition body still\nstreams without materializing. Everything that genuinely needs the whole\nvalue (object/array `enum` / `const`, `dependentSchemas`, `discriminator`,\n`contains`, `uniqueItems`, a composition with a non-forward branch, or\n`format` under an OpenAPI dialect) is a **BUFFER island**: the subtree is\nmaterialized and delegated to `@oav/schema`'s in-memory validator,\nbounded by `maxBufferedBytes`. Only a REJECT keyword\n(`unevaluatedProperties` / `unevaluatedItems`), an unknown keyword, or an\nunresolvable `$ref` fails fast at construction.\n\nOpenAPI: pass `openApiVersion: \"3.0\" | \"3.1\" | \"3.2\"`. 3.0 is normalized\nto 2020-12 shape (`nullable`, boolean `exclusive*`, `$ref` sibling\nsuppression) before classification; all three select OpenAPI semantics\n(`format` asserts).\n\nThe engine validates one resolved schema and resolves `$ref` against\n**the schema you pass**, not a separate document. An extracted request\nbody that is (or contains) an internal ref like\n`#/components/schemas/Pet` must carry the document's ref containers\n(`components` / `$defs` / `definitions`) alongside it, or construction\nthrows `unresolvable $ref`. Routing, content negotiation, OpenAPI version\ndetection, and body-schema lookup stay the caller's job; this package\nvalidates one resolved schema, so those concerns sit above it. The\nbridge from a resolved document is short:\n\n```ts\nimport { resolveSpec } from \"@aahoughton/oav-core/spec\";\nimport { createStreamValidator } from \"@aahoughton/oav-stream-validator\";\n\nconst doc = await resolveSpec(source); // inlines external refs; internal refs stay\nconst op = doc.paths[\"/pets\"].post; // your router selects the operation\nconst bodySchema = op.requestBody.content[\"application/json\"].schema;\n\nconst validator = createStreamValidator(\n  { ...bodySchema, components: doc.components }, // carry the ref container\n  { openApiVersion: \"3.1\" },\n);\n```\n\nObservability and edit hooks: `keyEvents` emits a `key` event per object\nkey (optionally path-filtered); `onScopeClose(at, cb)` observes a\nforward-decidable scope at its close, and `editClose(at, cb)` appends\nbytes before a scope's closing delimiter (append-only; appended bytes are\nnot validated). A `ScopeContext` carries the scope path, verdict, member\ncount, and a `field(name, value)` helper.\n\nReshaping the envelope: `editMember(at, cb)` renames or drops an object\nmember as it streams, the in-place edit `editClose` cannot do. The hook\nfires at the member's value start (so it knows the value type) and returns\n`{ action: \"rename\", key }`, `{ action: \"drop\" }`, or `{ action: \"keep\" }`\n(or `null`). A `rename` rewrites the key token only and streams the value\nverbatim, so renaming a key in front of a multi-GB array never buffers the\narray. A `drop` suppresses the member and absorbs one delimiter, leaving\nvalid JSON.\n\n```ts\nconst validator = createStreamValidator(bodySchema);\nvalidator.editMember([\"message_ids\"], () => ({ action: \"rename\", key: \"records\" }));\nvalidator.editMember([\"legacy_field\"], () => ({ action: \"drop\" }));\n// {\"message_ids\":[...big...],\"legacy_field\":1,\"keep\":2}\n//   -> {\"records\":[...big...],\"keep\":2}\n```\n\n`at` matches the member's **full path** (the enclosing scope plus the key),\nthe same coordinate `valueEvents.at` uses. Validation is pre-edit: the\ninput shape is validated as received (a dropped member is still validated;\nthe edit only changes the output). A rename whose target collides with a\nkey in the same object, and two hooks returning conflicting edits for one\nmember, are fatal. Two caps bound the buffering the edit introduces and\ndefault finite (unlike the resource limits above): `maxMemberPrefixBytes`\n(the held key-to-value span, default 4 KiB) and `maxMemberDropBytes` (a\ndropped member's span, default 64 KiB); over-cap is fatal. Dropping a\ncontainer-valued member is not supported on the stream path; rename works\nfor any value type.\n\nRecovering scalar fields: `valueEvents` emits a `value` event when a\nscalar object-member value completes, carrying the member's absolute\ninput-byte span (`valueStart` / `valueEnd`). Code that needs a few small\ntop-level scalars (an id, a version, a timestamp) recovers them without\nmaterializing the body or running a second parser: slice\n`[valueStart, valueEnd)` from its own copy of the input (a string span\nincludes its quotes, so the slice is valid JSON) and `JSON.parse` it. The\nspan is in the same pre-injection input space as `editClose` and\nviolations, so slice the **input**, not the echoed output (under\n`editClose` the output is respliced and its offsets shift).\n\n```ts\nconst captured = new Map<string, unknown>();\nconst validator = createStreamValidator(bodySchema, {\n  // Decode the matched scalars under a byte cap.\n  valueEvents: { at: (path) => path.length === 1, capture: true },\n});\nvalidator.on(\"value\", (e) => captured.set(e.key, e.value));\n// `e.value` is the decoded scalar (present when within `maxCaptureBytes`);\n// `e.truncated` flags an over-cap value (span still reported). Omit\n// `capture` for span-only events and slice the bytes yourself.\n```\n\nA `value` event's `path` is the **full path to the value** (the enclosing\nscope plus the member key), the same coordinate `valueEvents.at` matches,\nso the filter and the event speak one path: a top-level member `{version}`\nis `[\"version\"]` (length 1), not `[]`. (`event.key` is that path's last\nsegment.) This differs from `keyEvents`, whose `at` and `path` are both\nthe enclosing scope.\n\n`valueEvents` fires for scalar object members on both the STREAM path and\nscalar BUFFER islands, so a `format`-bearing string (`date-time`, `uri`,\n`uuid`) reports its value even under an asserting OpenAPI dialect that\ndelegates it to the in-memory engine. Array elements, the root value,\ncontainer-valued members, and members under a TEE composition branch do\nnot fire. `capture` retains a matched member's decoded bytes bounded by\n`maxCaptureBytes` (default 64 KiB); an over-cap value reports\n`truncated: true` rather than buffering unbounded. A delegated scalar is\nalready buffered for its own check, so there `maxCaptureBytes` only gates\ndelivery (the memory bound is `maxBufferedBytes`).\n\n`onScopeClose` / `editClose` / `editMember` fire for STREAM structure\nonly. A member whose enclosing object the classifier routes to a BUFFER\nisland (`uniqueItems`, `contains`, an object-valued `const`) or a TEE\ncomposition branch (`oneOf`/`anyOf`/`allOf`) does not emit a hook, so which\nscopes and members a hook sees depends on the schema's classification.\n(A streamed-object member whose own value is a scalar BUFFER island, e.g.\na `format`-bearing scalar, is still editable: the enclosing object streams\nand the edit is decided at the value start.) Use the hooks\nfor observing/editing forward-decidable structure, not as a general JSON\nvisitor over an arbitrary schema.\n\n### Streamability analysis\n\n`analyzeStreamability(schema, options)` is the design-time companion to the\nruntime engine: it classifies a resolved schema and reports where it\nbuffers and how much, without reading a byte. The same classification the\nengine runs on, turned into a peak-buffer budget you check before deploy.\n\n```ts\nimport { analyzeStreamability } from \"@aahoughton/oav-stream-validator\";\n\nconst report = analyzeStreamability(bodySchema, { openApiVersion: \"3.1\" });\nreport.classification; // \"streamable\" | \"tee\" | \"buffer\"\nreport.peakBytes; // schema-intrinsic peak in wire bytes, or \"unbounded\"\nreport.effectivePeakBytes; // peak under maxBufferedBytes (passes clamp to the cap)\n\n// The punch list: positions with no structural bound fall back to the cap.\nfor (const p of report.positions.filter((p) => p.maxBytes === \"unbounded\")) {\n  console.warn(`${p.path || \"<root>\"}: ${p.keyword} unbounded (${p.unboundedBy})`);\n}\n```\n\nA peak is computable because the engine holds one materialized island at a\ntime: sequential positions (array items, object properties) buffer one at a\ntime, so the peak across siblings is a **max**, while a TEE's concurrent\nsub-spines **sum**. A BUFFER island is bounded by its subtree's structural\nkeywords (`maxLength` / `maxItems` / `const` / `enum`, and a closed\nobject's properties), and `\"unbounded\"` where one is missing (an open\nobject is unbounded regardless of `maxProperties`). Sizes are an upper-bound estimate\nin the same UTF-8 wire bytes `maxBufferedBytes` caps (heavy `\\uXXXX`\nescaping can exceed the per-character assumption), so treat the number as a\ncapacity-planning figure, not a runtime meter. An unstreamable schema\nthrows the same `ClassifierError` `createStreamValidator` raises.\n\nThe runtime meter is `verdict.peakBufferedBytes` on `validator.result`: the\nhigh-water buffered wire bytes an actual stream reached, in the same model\n(`0` when nothing buffered, a single island exact, sibling buffers maxed, a\nTEE's branches summed, plus any edit-hook retention). Compare it to this\nreport's `peakBytes` to see how close real traffic came to the predicted\npeak. The analyzer bounds the schema; `peakBufferedBytes` reports the input.\n\n`analyzeSpec(document, options)` rolls this up over a whole resolved\nOpenAPI document: one budget per operation, for the request body and each\nresponse body. A body whose schema cannot be classified is reported with\nan `error` field rather than throwing, so a sweep surveys the whole spec.\nThe `oav` CLI surfaces it as `oav stream-check <spec>` (a per-operation\ntable; `--verbose` lists each unbounded position, `--envelope json` emits\nthe `SpecBudget`, `--fail-on-unbounded` exits non-zero for CI):\n\n```ts\nimport { analyzeSpec } from \"@aahoughton/oav-stream-validator\";\n\nconst { document } = await resolveSpec(source);\nfor (const op of analyzeSpec(document).operations) {\n  for (const body of op.bodies) {\n    const peak = body.report?.peakBytes ?? `error: ${body.error}`;\n    console.log(`${op.method} ${op.path} ${body.role}${body.status ?? \"\"}: ${peak}`);\n  }\n}\n```\n\n## Status\n\nPublished to the default `latest` dist-tag, on its own `1.x` line\n(versioned independently of the `oav-core` family). The classifier\nco-evolves with `oav-core`'s keyword set inside the monorepo (a CI drift\ntest makes a divergence a build failure rather than silent breakage); the\npublished bundle pins `@aahoughton/oav-core` so the two move together.\n","readmeFilename":"README.md"}