@gmb/bitmark-parser 7.7.0 → 7.9.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.
Files changed (45) hide show
  1. package/README.md +99 -21
  2. package/config/bitmark.json +532 -209
  3. package/dist/browser/bitmark-parser.min.js +6 -6
  4. package/dist/browser/bitmark-parser.min.js.map +1 -1
  5. package/dist/browser/cjs/index.cjs +533 -275
  6. package/dist/browser/cjs/index.cjs.map +1 -1
  7. package/dist/browser/cjs/index.d.cts +103 -22
  8. package/dist/browser/esm/index.d.ts +103 -22
  9. package/dist/browser/esm/index.js +532 -275
  10. package/dist/browser/esm/index.js.map +1 -1
  11. package/dist/browser/esm/worker-entry.js +494 -264
  12. package/dist/browser/esm/worker-entry.js.map +1 -1
  13. package/dist/browser/wasm/bitmark_browser_full_wasm_bg.wasm +0 -0
  14. package/dist/browser/wasm/bitmark_json_wasm_bg.wasm +0 -0
  15. package/dist/browser/wasm/bitmark_wasm_bg.wasm +0 -0
  16. package/dist/index.cjs +43 -8
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +92 -20
  19. package/dist/index.d.ts +92 -20
  20. package/dist/index.js +42 -8
  21. package/dist/index.js.map +1 -1
  22. package/dist/legacy.cjs +19 -11
  23. package/dist/legacy.cjs.map +1 -1
  24. package/dist/legacy.d.cts +3 -1
  25. package/dist/legacy.d.ts +3 -1
  26. package/dist/legacy.js +19 -11
  27. package/dist/legacy.js.map +1 -1
  28. package/dist/worker-entry.cjs.map +1 -1
  29. package/package.json +7 -7
  30. package/schema/bitmark.schema.json +1 -1
  31. package/wasm/bitmark_wasm.d.ts +52 -30
  32. package/wasm/bitmark_wasm.js +209 -113
  33. package/wasm/bitmark_wasm_bg.wasm +0 -0
  34. package/wasm/bitmark_wasm_bg.wasm.d.ts +5 -2
  35. package/wasm/package.json +1 -1
  36. package/wasm-bitmark-json/bitmark_json_wasm.d.ts +73 -51
  37. package/wasm-bitmark-json/bitmark_json_wasm.js +257 -161
  38. package/wasm-bitmark-json/bitmark_json_wasm_bg.wasm +0 -0
  39. package/wasm-bitmark-json/bitmark_json_wasm_bg.wasm.d.ts +5 -2
  40. package/wasm-bitmark-json/package.json +1 -1
  41. package/wasm-browser-full/bitmark_browser_full_wasm.d.ts +52 -30
  42. package/wasm-browser-full/bitmark_browser_full_wasm.js +209 -113
  43. package/wasm-browser-full/bitmark_browser_full_wasm_bg.wasm +0 -0
  44. package/wasm-browser-full/bitmark_browser_full_wasm_bg.wasm.d.ts +5 -2
  45. package/wasm-browser-full/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../node_modules/tsup/assets/cjs_shims.js","../src/core/facade.ts","../src/core/hooked-transform.ts","../src/node/worker-entry.ts"],"names":["require","createRequire","workerData","parentPort"],"mappings":";;;;;;AAKA,IAAM,gBAAA,GAAmB,MACvB,OAAO,QAAA,KAAa,WAAA,GAChB,IAAI,GAAA,CAAI,CAAA,KAAA,EAAQ,UAAU,CAAA,CAAE,CAAA,CAAE,IAAA,GAC7B,QAAA,CAAS,aAAA,IAAiB,QAAA,CAAS,aAAA,CAAc,OAAA,CAAQ,WAAA,EAAY,KAAM,QAAA,GAC1E,QAAA,CAAS,aAAA,CAAc,GAAA,GACvB,IAAI,GAAA,CAAI,SAAA,EAAW,QAAA,CAAS,OAAO,CAAA,CAAE,IAAA;AAEtC,IAAM,gCAAgC,gBAAA,EAAiB;;;ACiBvD,IAAM,oBAAA,GAAgC,MAAA;;;ACqCtC,SAAS,WAAW,GAAA,EAAqB;AAC9C,EAAA,MAAM,CAAA,GAAI,IAAI,IAAA,EAAK;AACnB,EAAA,OAAO,CAAA,CAAE,UAAA,CAAW,GAAG,CAAA,IAAK,CAAA,CAAE,QAAA,CAAS,GAAG,CAAA,GAAI,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,GAAI,GAAA;AACjE;AA4JO,SAAS,SAAA,CAAU,cAAsB,YAAA,EAAuC;AACrF,EAAA,MAAM,EAAE,IAAA,EAAM,WAAA,EAAY,GAAI,IAAA,CAAK,MAAM,YAAY,CAAA;AACrD,EAAA,OAAO,EAAE,SAAS,YAAA,KAAiB,MAAA,GAAS,WAAW,IAAI,CAAA,GAAI,MAAM,WAAA,EAAY;AACnF;;;ACxNA,IAAMA,QAAAA,GAAUC,uBAAc,aAAe,CAAA;AAI7C,IAAM,YAAA,GAAwC;AAAA,EAC5C,IAAA,EAAM,yBAAA;AAAA,EACN,cAAA,EAAgB,mDAAA;AAAA,EAChB,cAAA,EAAgB;AAClB,CAAA;AAEA,IAAM,OAAA,GAAYC,2BAAkD,OAAA,IAClE,oBAAA;AACF,IAAM,IAAA,GAAOF,QAAAA,CAAQ,YAAA,CAAa,OAAO,CAAC,CAAA;AAkB1CG,yBAAA,EAAY,EAAA,CAAG,SAAA,EAAW,CAAC,GAAA,KAAoB;AAC7C,EAAA,MAAM,EAAE,EAAA,EAAI,GAAA,EAAI,GAAI,GAAA;AACpB,EAAA,IAAI;AACF,IAAA,MAAM,EAAE,OAAA,EAAS,WAAA,EAAY,GAAI,SAAA;AAAA,MAC/B,IAAA,CAAK,SAAA;AAAA,QACH,GAAA,CAAI,MAAA;AAAA,QACJ,GAAA,CAAI,WAAA;AAAA,QACJ,GAAA,CAAI,YAAA;AAAA,QACJ,GAAA,CAAI,IAAA;AAAA,QACJ,GAAA,CAAI,SAAA;AAAA,QACJ,KAAA;AAAA,QACA,CAAA;AAAA,QACA,GAAA,CAAI;AAAA,OACN;AAAA,MACA,GAAA,CAAI;AAAA,KACN;AACA,IAAAA,yBAAA,EAAY,YAAY,EAAE,EAAA,EAAI,IAAI,IAAA,EAAM,OAAA,EAAS,aAAa,CAAA;AAAA,EAChE,SAAS,CAAA,EAAG;AAGV,IAAAA,yBAAA,EAAY,WAAA,CAAY,EAAE,EAAA,EAAI,EAAA,EAAI,KAAA,EAAO,KAAA,EAAO,CAAA,YAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,MAAA,CAAO,CAAC,GAAG,CAAA;AAAA,EAC9F;AACF,CAAC,CAAA","file":"worker-entry.cjs","sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","// @zen-component: TS-VariantFacade\n//\n// Runtime variant selection (PLAN-166). The package ships one API surface\n// backed by a mutable reference to the ACTIVE wasm module; `init({ feature })`\n// selects which wasm variant backs it, and a later re-init swaps the module\n// atomically between calls (every wasm call is stateless string → string).\n// Platform entries (Node / browser) own the loading; this module owns the\n// reference, the feature vocabulary, and the error types.\n\nimport type { PatchDiagnostic } from \"../types.js\";\n\n/**\n * A deployable wasm feature set (PLAN-174).\n *\n * - `full` — everything, including the `info` META layer (tag descriptions,\n * group provenance, raw mappingKeys patterns). Native-CLI parity.\n * - `browser-full` — the same conversion capabilities as `full`, minus the\n * meta strings the browser should not pay to download.\n * - `bitmark-json` — bitmark ↔ JSON only.\n *\n * Size gating follows the DELIVERY CHANNEL, not the compilation target: a Node\n * backend loading wasm from disk has native-CLI requirements, while any entry\n * can be bundled for the browser. So every variant is buildable for both\n * targets and richness is always chosen explicitly. The per-entry defaults\n * below are conveniences; the contract is \"you get the variant you selected\".\n */\nexport type Feature = \"full\" | \"browser-full\" | \"bitmark-json\";\n\n/** Default variant for the Node entry — disk-loaded wasm, no download budget. */\nexport const NODE_DEFAULT_FEATURE: Feature = \"full\";\n\n/** Default variant for the browser entry — download latency is the budget. */\nexport const BROWSER_DEFAULT_FEATURE: Feature = \"browser-full\";\n\n/**\n * The wasm surface the shared wrappers drive. The optional members are the\n * capabilities a lean variant compiles out (calling their wrapper there\n * throws {@link UnsupportedFeatureError}).\n *\n * Every fallible member THROWS a JavaScript `Error` on failure (PLAN-192 D1);\n * its `message` is the core's error contract, `<kind> at <path>: <message>`,\n * with `<kind>` a stable kebab-case identifier. A returned string is always a\n * result — there is no in-band error prefix to test for.\n */\nexport interface WasmModule {\n convert(\n input: string,\n inputFormat: string,\n outputFormat: string,\n mode: string,\n pretty: boolean,\n indent: number,\n spacesAroundValues: number,\n includeUnknownProperties: boolean,\n mappingReport: boolean,\n /** `semantic-tokens` / `diagnostics` output and the bit spans: `\"utf-16\"` | `\"utf-8\"`, `\"\"` = default (PLAN-193, PLAN-221). */\n positionEncoding: string,\n /** `semantic-tokens` output only: `\"lsp\"` | `\"absolute\"`, `\"\"` = default. */\n tokensLayout: string,\n ): string;\n /**\n * `convert`'s arguments, then one flag per extra (PLAN-221 D1a). Returns a\n * {@link WasmConvertDetails} living in wasm memory: the caller must\n * `free()` it.\n */\n convert_with_details(\n input: string,\n inputFormat: string,\n outputFormat: string,\n mode: string,\n pretty: boolean,\n indent: number,\n spacesAroundValues: number,\n includeUnknownProperties: boolean,\n mappingReport: boolean,\n positionEncoding: string,\n tokensLayout: string,\n bitSpans: boolean,\n ): WasmConvertDetails;\n canonicalize(\n input: string,\n inputFormat: string,\n mode: string,\n pretty: boolean,\n indent: number,\n spacesAroundValues: number,\n ): string;\n transform(\n input: string,\n inputFormat: string,\n outputFormat: string,\n mode: string,\n patchJson: string,\n pretty: boolean,\n indent: number,\n spacesAroundValues: number,\n ): string;\n info(\n infoType: string,\n format: string,\n bit: string,\n pretty: boolean,\n indent: number,\n includeDeprecated: boolean,\n onlyDeprecated: boolean,\n full: boolean,\n language: string,\n ): string;\n /** Throws when the data is not valid for `dataType`. */\n register(dataType: string, data: string): void;\n count_bits(input: string): string;\n /** `positionEncoding`: `\"utf-16\"` | `\"utf-8\"`, `\"\"` = default (PLAN-221 D3). */\n split_bits(input: string, positionEncoding: string): string;\n split_json_bits(input: string): string;\n is_bitmark(input: string): boolean;\n prettify(json: string, indent: number): string;\n convert_text(input: string, direction: string, format: string, location: string): string;\n breakscape_text(input: string, format: string, location: string, subLocation: string): string;\n unbreakscape_text(input: string, format: string, location: string, subLocation: string): string;\n /**\n * Resolve a patch document against a document into ordered items\n * (PLAN-179), splitting it under `inputFormat` (`\"auto\"` | `\"bitmark\"` |\n * `\"json\"`) — the selector the caller already resolved, never a re-sniff.\n */\n resolve_patch_document(input: string, inputFormat: string, patchJson: string): string;\n /**\n * Present only where this build can render `info` as TEXT (PLAN-192 D6) —\n * the same \"presence is the capability\" signal as `diff`, so no caller\n * has to test for a variant name. Absent on `bitmark-json`, where `info`\n * serves JSON only.\n */\n info_text_supported?(): boolean;\n /** Completion at a position (PLAN-196); absent on a variant built without `editor`. */\n complete?(\n input: string,\n line: number,\n character: number,\n positionEncoding: string,\n includeDeprecated: boolean,\n triggerCharacter: string,\n ): string;\n /** One completion item with its documentation (PLAN-202); absent on a variant built without `editor`. */\n resolve?(\n input: string,\n line: number,\n character: number,\n positionEncoding: string,\n includeDeprecated: boolean,\n label: string,\n kind: number,\n ): string;\n /** Hover at a position (PLAN-196); absent on a variant built without `editor`. */\n hover?(input: string, line: number, character: number, positionEncoding: string): string;\n /** Semantic diff (PLAN-179); absent on a variant built without `diff`. */\n diff?(\n a: string,\n b: string,\n inputFormat: string,\n outputFormat: string,\n context: number,\n locate: boolean,\n similarity: number,\n bboxTolerance: number,\n spacesAroundValues: number,\n ): string;\n}\n\n/**\n * The result object of `convert_with_details` (PLAN-221 F7): the output and\n * each extra asked for, without a JSON round trip. One accessor per extra.\n */\nexport interface WasmConvertDetails {\n /** The output, moved out of wasm memory: a second call returns `\"\"`. */\n take_data(): string;\n /** Flat `[index, start, end, …]`; `undefined` when not asked for. */\n bit_spans(): Uint32Array | undefined;\n /** The spans' encoding; `undefined` when not asked for. */\n bit_spans_encoding(): string | undefined;\n free(): void;\n}\n\n/** Options accepted by `init` / `initSync` on every platform entry. */\nexport interface InitOptions {\n /** Which wasm variant to load. Default: `\"full\"`. */\n feature?: Feature;\n}\n\n/** A call arrived before any wasm module was initialised (browser only). */\nexport class NotInitializedError extends Error {\n constructor() {\n super(\"wasm not initialised: call init() (or init({ feature: … })) first\");\n this.name = \"NotInitializedError\";\n }\n}\n\n/** `register` rejected the supplied data. */\nexport class RegisterError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"RegisterError\";\n }\n}\n\n/**\n * `transform` skipped one or more patch entries (PLAN-209 T9). The document\n * was NOT returned: a silently ignored edit is worse than an error. Each\n * diagnostic names the entry's `path`, the error contract's `kind`\n * (`patch-parse-error`, `patch-index-out-of-range`, `patch-type-mismatch`)\n * and a `message`.\n */\nexport class PatchError extends Error {\n readonly diagnostics: PatchDiagnostic[];\n constructor(diagnostics: PatchDiagnostic[]) {\n const lines = diagnostics.map((d) => `${d.kind} at ${d.path}: ${d.message}`);\n super(\n `${diagnostics.length} patch ${diagnostics.length === 1 ? \"entry was\" : \"entries were\"} skipped:\\n` +\n lines.join(\"\\n\"),\n );\n this.name = \"PatchError\";\n this.diagnostics = diagnostics;\n }\n}\n\n/** A call needs a capability the ACTIVE wasm variant compiled out. */\nexport class UnsupportedFeatureError extends Error {\n constructor(what: string, active: Feature | undefined) {\n super(\n `${what} is not available in the loaded wasm module` +\n `${active ? ` (feature: \"${active}\")` : \"\"}; init({ feature: \"full\" }) to load it`,\n );\n this.name = \"UnsupportedFeatureError\";\n }\n}\n\nlet active: WasmModule | undefined;\nlet activeFeature: Feature | undefined;\nlet lazyLoader: (() => void) | undefined;\n\n// Data registered through `register`, kept so it can be REPLAYED onto each\n// newly activated module. Each variant is a separate wasm instance with its\n// own memory, so a registration made against one is invisible to the next —\n// and PLAN-166's promise is that swapping variants is invisible to the\n// caller. Keyed by type, because `register` replaces per type.\nconst registrations = new Map<string, string>();\n\n/**\n * The active wasm module. On Node the entry installs a lazy loader, so the\n * first call without an explicit `init` loads the default (`full`) variant\n * synchronously; in the browser an uninitialised call throws\n * {@link NotInitializedError} (wasm instantiation is async there).\n */\nexport function getActive(): WasmModule {\n if (!active && lazyLoader) lazyLoader();\n if (!active) throw new NotInitializedError();\n return active;\n}\n\n/** Swap the active module — the single atomic reference assignment. */\nexport function setActive(feature: Feature, module: WasmModule): void {\n // Replay first: a module that cannot take the registered data must not\n // become active half-configured. The data was accepted once already, so a\n // failure here is a bug worth surfacing rather than swallowing.\n for (const [type, data] of registrations) {\n try {\n module.register(type, data);\n } catch (e) {\n throw new RegisterError(e instanceof Error ? e.message : String(e));\n }\n }\n active = module;\n activeFeature = feature;\n}\n\n/**\n * Remember what `register` accepted, so it survives an `init` swap. Called\n * only after the ACTIVE module has accepted the same data.\n */\nexport function rememberRegistration(type: string, data: string): void {\n registrations.set(type, data);\n}\n\n/**\n * Install the Node lazy default loader (runs on the first call when no\n * explicit `init` happened; must call {@link setActive}).\n */\nexport function setLazyLoader(loader: () => void): void {\n lazyLoader = loader;\n}\n\n/** The active wasm feature, or `undefined` before the first init. */\nexport function variant(): Feature | undefined {\n return activeFeature;\n}\n","// @zen-component: TS-HookedTransform\n\nimport type { BitView, OutputFormat, PatchDiagnostic, PatchEntry, TransformOptions } from \"../types.js\";\nimport { PatchError } from \"./facade.js\";\n\n/** What the WASM `transform` export returns (PLAN-209 T9). */\nexport interface TransformEnvelope {\n data: string;\n diagnostics: PatchDiagnostic[];\n}\n\n/** One bit's rendered element plus the entries skipped on it. */\nexport interface BitOutput {\n element: string;\n diagnostics: PatchDiagnostic[];\n}\n\n/**\n * The minimal WASM surface the per-bit hook driver needs. Both the Node and\n * browser entry points satisfy this with their loaded module.\n */\nexport interface HookedWasm {\n is_bitmark(input: string): boolean;\n split_bits(input: string, positionEncoding: string): string;\n split_json_bits(input: string): string;\n prettify(json: string, indent: number): string;\n resolve_patch_document(input: string, inputFormat: string, patchJson: string): string;\n /** Returns the {@link TransformEnvelope} as JSON text. */\n transform(\n input: string,\n inputFormat: string,\n outputFormat: string,\n mode: string,\n patchJson: string,\n pretty: boolean,\n indent: number,\n spacesAroundValues: number,\n ): string;\n}\n\n/** A single bit's compute job: data only, safe to ship to a worker thread. */\nexport interface BitJob {\n index: number;\n source: string;\n inputFormat: \"bitmark\" | \"json\";\n outputFormat: OutputFormat;\n mode: string;\n patchJson: string;\n spacesAroundValues: number;\n}\n\n/**\n * The result of running `preHook` over a document's bits: the compute `jobs`\n * for the bits that survived (in document order) and the matching `views`\n * (so `postHook` can be called per surviving bit), plus the document-level\n * recombination settings.\n */\nexport interface TransformPlan {\n jobs: BitJob[];\n views: BitView[];\n output: OutputFormat;\n pretty: boolean;\n indent: number;\n}\n\n/** Strip the outer `[ … ]` of a compact one-element JSON array. */\nexport function stripArray(doc: string): string {\n const t = doc.trim();\n return t.startsWith(\"[\") && t.endsWith(\"]\") ? t.slice(1, -1) : doc;\n}\n\n/**\n * The `patch` option as the JSON text the WASM resolver takes — a patch\n * document or the bare-array every-bit shorthand — or `\"\"` when absent.\n */\nfunction patchJsonOf(patch: TransformOptions[\"patch\"]): string {\n if (patch === undefined) return \"\";\n if (typeof patch === \"string\") return patch.trim();\n return JSON.stringify(patch);\n}\n\n/**\n * One slot of the resolved patch document (see `resolve_patch_document`).\n * An inserted `bit` is a JSON bit or, as a string, one bit of bitmark\n * source (PLAN-209 D15).\n */\ninterface ResolvedItem {\n source: number | null;\n bit: Record<string, unknown> | string | null;\n patch: PatchEntry[];\n}\n\nfunction buildUnits(\n wasm: HookedWasm,\n input: string,\n inputIsBitmark: boolean,\n): { view: BitView; source: string }[] {\n if (inputIsBitmark) {\n // Only `index` / `source` are read: `\"utf-8\"` skips encoding the offsets.\n const slices = JSON.parse(wasm.split_bits(input, \"utf-8\")) as { index: number; source: string }[];\n return slices.map((s) => ({\n view: { format: \"bitmark\", index: s.index, total: slices.length, source: s.source },\n source: s.source,\n }));\n }\n const envelopes = JSON.parse(wasm.split_json_bits(input)) as {\n bit?: import(\"../generated/bit-types.js\").AnyBit;\n }[];\n return envelopes.map((env, index) => ({\n view: { format: \"json\", index, total: envelopes.length, bit: env.bit ?? {} },\n // Per-bit transform consumes a one-bit JSON document.\n source: JSON.stringify([env]),\n }));\n}\n\n/**\n * Split the document and run `preHook` (on the calling thread) for each bit,\n * producing the surviving compute jobs + their views. This is the main-thread\n * half shared by the sync and worker-pool drivers — only the resulting\n * data-only {@link BitJob}s ever cross to a worker.\n */\nexport function planTransform(\n wasm: HookedWasm,\n input: string,\n options: TransformOptions,\n): TransformPlan {\n const mode = options.mode ?? \"optimized\";\n const output: OutputFormat = options.outputFormat ?? \"json\";\n const spacesAroundValues = options.spacesAroundValues ?? 1;\n const indent = options.indent ?? 2;\n const pretty = options.pretty ?? false;\n\n const sel = options.inputFormat ?? \"auto\";\n const inputIsBitmark = sel === \"bitmark\" || (sel !== \"json\" && wasm.is_bitmark(input));\n const inputFormat = inputIsBitmark ? \"bitmark\" : \"json\";\n\n // The engine decides which output formats are per-bit (the whole-document\n // `lex` / `semantic-tokens` / `diagnostics` are not, and it refuses them\n // before any hook runs). Ask it the same way, once, with an empty document:\n // a document that splits into no bits would otherwise never carry that\n // refusal, and the hooked path would answer `\"\"` where the hookless one\n // throws (PLAN-192 D1 parity).\n wasm.transform(\"\", \"bitmark\", output, mode, \"\", false, indent, spacesAroundValues);\n\n const units = buildUnits(wasm, input, inputIsBitmark);\n const total = units.length;\n\n // Hooks see the ORIGINAL bits, in order, before the patch document\n // reshapes anything (same as the Rust pipeline's Phase A).\n const hooked: ({ patches: PatchEntry[]; outputFormat: OutputFormat } | null)[] = [];\n for (let index = 0; index < total; index++) {\n const { view } = units[index];\n view.index = index;\n view.total = total;\n const result = options.preHook ? options.preHook(view) || {} : {};\n hooked.push(result.drop ? null : { patches: result.patches ?? [], outputFormat: result.outputFormat ?? output });\n }\n\n // The patch document (or the every-bit shorthand) is resolved by the one\n // Rust implementation into the ordered output slots; without a patch the\n // slots are simply the original bits. The resolver splits under the SAME\n // input format resolved above — an explicit selection is never re-sniffed.\n const patchJson = patchJsonOf(options.patch);\n let resolved: ResolvedItem[];\n if (patchJson === \"\") {\n resolved = units.map((_, index) => ({ source: index, bit: null, patch: [] }));\n } else {\n resolved = JSON.parse(\n wasm.resolve_patch_document(input, inputFormat, patchJson),\n ) as ResolvedItem[];\n }\n\n const jobs: BitJob[] = [];\n const views: BitView[] = [];\n for (const item of resolved) {\n if (item.source !== null) {\n const hook = hooked[item.source];\n if (!hook) continue; // dropped by the pre-hook\n jobs.push({\n index: item.source,\n source: units[item.source].source,\n inputFormat,\n outputFormat: hook.outputFormat,\n mode,\n // The document's patches first, the hook's after (the hook contract).\n patchJson: JSON.stringify([...item.patch, ...hook.patches]),\n spacesAroundValues,\n });\n views.push(units[item.source].view);\n } else if (typeof item.bit === \"string\") {\n // An inserted bitmark bit (PLAN-209 D15), seen by `postHook` only.\n const index = jobs.length;\n jobs.push({\n index,\n source: item.bit,\n inputFormat: \"bitmark\",\n outputFormat: output,\n mode,\n patchJson: JSON.stringify(item.patch),\n spacesAroundValues,\n });\n views.push({ format: \"bitmark\", index, total, source: item.bit });\n } else {\n // An inserted bit: a one-bit JSON document, seen by `postHook` only.\n const bit = (item.bit ?? {}) as import(\"../generated/bit-types.js\").AnyBit;\n const index = jobs.length;\n jobs.push({\n index,\n source: JSON.stringify([{ bit }]),\n inputFormat: \"json\",\n outputFormat: output,\n mode,\n patchJson: JSON.stringify(item.patch),\n spacesAroundValues,\n });\n views.push({ format: \"json\", index, total, bit });\n }\n }\n return { jobs, views, output, pretty, indent };\n}\n\n/**\n * Read the WASM `transform` envelope for one bit: the element (compact JSON\n * stripped of its one-element array) and the entries skipped on it.\n */\nexport function bitOutput(envelopeJson: string, outputFormat: OutputFormat): BitOutput {\n const { data, diagnostics } = JSON.parse(envelopeJson) as TransformEnvelope;\n return { element: outputFormat === \"json\" ? stripArray(data) : data, diagnostics };\n}\n\n/**\n * Compute one bit's rendered element synchronously via WASM. The per-bit\n * element is always compact JSON (the document is prettified once on\n * recombination). Throws on failure; skipped entries come back as\n * diagnostics for the driver to raise.\n */\nexport function computeBit(wasm: HookedWasm, job: BitJob): BitOutput {\n return bitOutput(\n wasm.transform(\n job.source,\n job.inputFormat,\n job.outputFormat,\n job.mode,\n job.patchJson,\n false,\n 2,\n job.spacesAroundValues,\n ),\n job.outputFormat,\n );\n}\n\n/** Recombine per-bit elements into the final document, in document order. */\nexport function recombine(\n wasm: HookedWasm,\n elements: string[],\n output: OutputFormat,\n pretty: boolean,\n indent: number,\n): string {\n // Only JSON wraps as an array; every other per-bit output (bitmark, plain\n // text, a mapping such as html) is text and joins with newlines — the same\n // rule as the Rust pipeline's recombine.\n if (output !== \"json\") return elements.join(\"\\n\");\n const arr = `[${elements.join(\",\")}]`;\n return pretty ? wasm.prettify(arr, indent) : arr;\n}\n\n/**\n * Per-bit transform driver (synchronous, in-process): split the document, run\n * `preHook` for each bit on the calling thread, apply the static patch + any\n * hook patches, re-emit the bit, run `postHook`, then recombine in document\n * order.\n *\n * Hooks run synchronously on the calling thread; only data crosses into WASM.\n * Used when a `preHook`/`postHook` is supplied; the hookless path stays a\n * single `wasm.transform` call. Throws on failure (PLAN-192 D1), and a\n * `PatchError` when any entry was skipped (PLAN-209 T9).\n */\nexport function runHookedTransform(\n wasm: HookedWasm,\n input: string,\n options: TransformOptions,\n): string {\n const plan: TransformPlan = planTransform(wasm, input, options);\n\n const rendered: string[] = [];\n const skipped: PatchDiagnostic[] = [];\n for (let i = 0; i < plan.jobs.length; i++) {\n const { element, diagnostics } = computeBit(wasm, plan.jobs[i]);\n skipped.push(...diagnostics);\n options.postHook?.(plan.views[i], element);\n rendered.push(element);\n }\n // A skipped entry is never silent (PLAN-209 T9).\n if (skipped.length > 0) throw new PatchError(skipped);\n return recombine(wasm, rendered, plan.output, plan.pretty, plan.indent);\n}\n","// @zen-component: TS-WorkerEntry\n//\n// Node worker_threads entry for the parallel transform pool. Each worker loads\n// its own WASM instance — the VARIANT the spawning pool passes via workerData\n// (PLAN-166) — and computes one bit at a time from a data-only job. Hooks (JS\n// closures) never reach here — they run on the main thread.\n\nimport { createRequire } from \"node:module\";\nimport { parentPort, workerData } from \"node:worker_threads\";\nimport { type BitJob, bitOutput } from \"../core/hooked-transform.js\";\nimport { NODE_DEFAULT_FEATURE, type Feature } from \"../core/facade.js\";\n\nconst require = createRequire(import.meta.url);\n\n// Bundled to dist/worker-entry.cjs; the wasm glue dirs sit at the package\n// root. Mirrors the entry's variant map (facade.ts).\nconst VARIANT_GLUE: Record<Feature, string> = {\n full: \"../wasm/bitmark_wasm.js\",\n \"browser-full\": \"../wasm-browser-full/bitmark_browser_full_wasm.js\",\n \"bitmark-json\": \"../wasm-bitmark-json/bitmark_json_wasm.js\",\n};\n\nconst feature = ((workerData as { feature?: Feature } | undefined)?.feature ??\n NODE_DEFAULT_FEATURE) as Feature;\nconst wasm = require(VARIANT_GLUE[feature]) as {\n transform(\n input: string,\n inputFormat: string,\n outputFormat: string,\n mode: string,\n patchJson: string,\n pretty: boolean,\n indent: number,\n spacesAroundValues: number,\n ): string;\n};\n\ninterface JobMessage {\n id: number;\n job: BitJob;\n}\n\nparentPort?.on(\"message\", (msg: JobMessage) => {\n const { id, job } = msg;\n try {\n const { element, diagnostics } = bitOutput(\n wasm.transform(\n job.source,\n job.inputFormat,\n job.outputFormat,\n job.mode,\n job.patchJson,\n false,\n 2,\n job.spacesAroundValues,\n ),\n job.outputFormat,\n );\n parentPort?.postMessage({ id, ok: true, element, diagnostics });\n } catch (e) {\n // The wasm export throws on failure (PLAN-192 D1); the pool turns this\n // message back into a rejected promise on the main thread.\n parentPort?.postMessage({ id, ok: false, error: e instanceof Error ? e.message : String(e) });\n }\n});\n"]}
1
+ {"version":3,"sources":["../../../node_modules/tsup/assets/cjs_shims.js","../src/core/facade.ts","../src/core/hooked-transform.ts","../src/node/worker-entry.ts"],"names":["require","createRequire","workerData","parentPort"],"mappings":";;;;;;AAKA,IAAM,gBAAA,GAAmB,MACvB,OAAO,QAAA,KAAa,WAAA,GAChB,IAAI,GAAA,CAAI,CAAA,KAAA,EAAQ,UAAU,CAAA,CAAE,CAAA,CAAE,IAAA,GAC7B,QAAA,CAAS,aAAA,IAAiB,QAAA,CAAS,aAAA,CAAc,OAAA,CAAQ,WAAA,EAAY,KAAM,QAAA,GAC1E,QAAA,CAAS,aAAA,CAAc,GAAA,GACvB,IAAI,GAAA,CAAI,SAAA,EAAW,QAAA,CAAS,OAAO,CAAA,CAAE,IAAA;AAEtC,IAAM,gCAAgC,gBAAA,EAAiB;;;ACiBvD,IAAM,oBAAA,GAAgC,MAAA;;;ACqCtC,SAAS,WAAW,GAAA,EAAqB;AAC9C,EAAA,MAAM,CAAA,GAAI,IAAI,IAAA,EAAK;AACnB,EAAA,OAAO,CAAA,CAAE,UAAA,CAAW,GAAG,CAAA,IAAK,CAAA,CAAE,QAAA,CAAS,GAAG,CAAA,GAAI,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,GAAI,GAAA;AACjE;AA4JO,SAAS,SAAA,CAAU,cAAsB,YAAA,EAAuC;AACrF,EAAA,MAAM,EAAE,IAAA,EAAM,WAAA,EAAY,GAAI,IAAA,CAAK,MAAM,YAAY,CAAA;AACrD,EAAA,OAAO,EAAE,SAAS,YAAA,KAAiB,MAAA,GAAS,WAAW,IAAI,CAAA,GAAI,MAAM,WAAA,EAAY;AACnF;;;ACxNA,IAAMA,QAAAA,GAAUC,uBAAc,aAAe,CAAA;AAI7C,IAAM,YAAA,GAAwC;AAAA,EAC5C,IAAA,EAAM,yBAAA;AAAA,EACN,cAAA,EAAgB,mDAAA;AAAA,EAChB,cAAA,EAAgB;AAClB,CAAA;AAEA,IAAM,OAAA,GAAYC,2BAAkD,OAAA,IAClE,oBAAA;AACF,IAAM,IAAA,GAAOF,QAAAA,CAAQ,YAAA,CAAa,OAAO,CAAC,CAAA;AAkB1CG,yBAAA,EAAY,EAAA,CAAG,SAAA,EAAW,CAAC,GAAA,KAAoB;AAC7C,EAAA,MAAM,EAAE,EAAA,EAAI,GAAA,EAAI,GAAI,GAAA;AACpB,EAAA,IAAI;AACF,IAAA,MAAM,EAAE,OAAA,EAAS,WAAA,EAAY,GAAI,SAAA;AAAA,MAC/B,IAAA,CAAK,SAAA;AAAA,QACH,GAAA,CAAI,MAAA;AAAA,QACJ,GAAA,CAAI,WAAA;AAAA,QACJ,GAAA,CAAI,YAAA;AAAA,QACJ,GAAA,CAAI,IAAA;AAAA,QACJ,GAAA,CAAI,SAAA;AAAA,QACJ,KAAA;AAAA,QACA,CAAA;AAAA,QACA,GAAA,CAAI;AAAA,OACN;AAAA,MACA,GAAA,CAAI;AAAA,KACN;AACA,IAAAA,yBAAA,EAAY,YAAY,EAAE,EAAA,EAAI,IAAI,IAAA,EAAM,OAAA,EAAS,aAAa,CAAA;AAAA,EAChE,SAAS,CAAA,EAAG;AAGV,IAAAA,yBAAA,EAAY,WAAA,CAAY,EAAE,EAAA,EAAI,EAAA,EAAI,KAAA,EAAO,KAAA,EAAO,CAAA,YAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,MAAA,CAAO,CAAC,GAAG,CAAA;AAAA,EAC9F;AACF,CAAC,CAAA","file":"worker-entry.cjs","sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","// @zen-component: TS-VariantFacade\n//\n// Runtime variant selection (PLAN-166). The package ships one API surface\n// backed by a mutable reference to the ACTIVE wasm module; `init({ feature })`\n// selects which wasm variant backs it, and a later re-init swaps the module\n// atomically between calls (every wasm call is stateless string → string).\n// Platform entries (Node / browser) own the loading; this module owns the\n// reference, the feature vocabulary, and the error types.\n\nimport type { PatchDiagnostic } from \"../types.js\";\n\n/**\n * A deployable wasm feature set (PLAN-174).\n *\n * - `full` — everything, including the `info` META layer (tag descriptions,\n * group provenance, raw mappingKeys patterns). Native-CLI parity.\n * - `browser-full` — the same conversion capabilities as `full`, minus the\n * meta strings the browser should not pay to download.\n * - `bitmark-json` — bitmark ↔ JSON only.\n *\n * Size gating follows the DELIVERY CHANNEL, not the compilation target: a Node\n * backend loading wasm from disk has native-CLI requirements, while any entry\n * can be bundled for the browser. So every variant is buildable for both\n * targets and richness is always chosen explicitly. The per-entry defaults\n * below are conveniences; the contract is \"you get the variant you selected\".\n */\nexport type Feature = \"full\" | \"browser-full\" | \"bitmark-json\";\n\n/** Default variant for the Node entry — disk-loaded wasm, no download budget. */\nexport const NODE_DEFAULT_FEATURE: Feature = \"full\";\n\n/** Default variant for the browser entry — download latency is the budget. */\nexport const BROWSER_DEFAULT_FEATURE: Feature = \"browser-full\";\n\n/**\n * The wasm surface the shared wrappers drive. The optional members are the\n * capabilities a lean variant compiles out (calling their wrapper there\n * throws {@link UnsupportedFeatureError}).\n *\n * Every fallible member THROWS a JavaScript `Error` on failure (PLAN-192 D1);\n * its `message` is the core's error contract, `<kind> at <path>: <message>`,\n * with `<kind>` a stable kebab-case identifier. A returned string is always a\n * result — there is no in-band error prefix to test for.\n */\nexport interface WasmModule {\n /** The wasm's own version (PLAN-224). */\n version(): string;\n convert(\n input: string,\n inputFormat: string,\n outputFormat: string,\n mode: string,\n pretty: boolean,\n indent: number,\n spacesAroundValues: number,\n includeUnknownProperties: boolean,\n mappingReport: boolean,\n /** `semantic-tokens` / `diagnostics` output and the bit spans: `\"utf-16\"` | `\"utf-8\"`, `\"\"` = default (PLAN-193, PLAN-221). */\n positionEncoding: string,\n /** `semantic-tokens` output only: `\"lsp\"` | `\"absolute\"`, `\"\"` = default. */\n tokensLayout: string,\n ): string;\n /**\n * `convert`'s arguments, then one flag per extra (PLAN-221 D1a). Returns a\n * {@link WasmConvertDetails} living in wasm memory: the caller must\n * `free()` it.\n */\n convert_with_details(\n input: string,\n inputFormat: string,\n outputFormat: string,\n mode: string,\n pretty: boolean,\n indent: number,\n spacesAroundValues: number,\n includeUnknownProperties: boolean,\n mappingReport: boolean,\n positionEncoding: string,\n tokensLayout: string,\n bitSpans: boolean,\n ): WasmConvertDetails;\n canonicalize(\n input: string,\n inputFormat: string,\n mode: string,\n pretty: boolean,\n indent: number,\n spacesAroundValues: number,\n ): string;\n transform(\n input: string,\n inputFormat: string,\n outputFormat: string,\n mode: string,\n patchJson: string,\n pretty: boolean,\n indent: number,\n spacesAroundValues: number,\n ): string;\n info(\n infoType: string,\n format: string,\n bit: string,\n pretty: boolean,\n indent: number,\n includeDeprecated: boolean,\n onlyDeprecated: boolean,\n full: boolean,\n language: string,\n ): string;\n /**\n * Bit templates (PLAN-225) — ABSENT on a variant built without the\n * `template` feature (presence is the capability).\n */\n template?(\n bit: string,\n format: string,\n full: boolean,\n includeDeprecated: boolean,\n pretty: boolean,\n indent: number,\n ): string;\n template_all?(includeDeprecated: boolean, pretty: boolean, indent: number): string;\n /** Throws when the data is not valid for `dataType`. */\n register(dataType: string, data: string): void;\n count_bits(input: string): string;\n /** `positionEncoding`: `\"utf-16\"` | `\"utf-8\"`, `\"\"` = default (PLAN-221 D3). */\n split_bits(input: string, positionEncoding: string): string;\n split_json_bits(input: string): string;\n is_bitmark(input: string): boolean;\n prettify(json: string, indent: number): string;\n convert_text(input: string, direction: string, format: string, location: string): string;\n breakscape_text(input: string, format: string, location: string, subLocation: string): string;\n unbreakscape_text(input: string, format: string, location: string, subLocation: string): string;\n /**\n * Resolve a patch document against a document into ordered items\n * (PLAN-179), splitting it under `inputFormat` (`\"auto\"` | `\"bitmark\"` |\n * `\"json\"`) — the selector the caller already resolved, never a re-sniff.\n */\n resolve_patch_document(input: string, inputFormat: string, patchJson: string): string;\n /**\n * Present only where this build can render `info` as TEXT (PLAN-192 D6) —\n * the same \"presence is the capability\" signal as `diff`, so no caller\n * has to test for a variant name. Absent on `bitmark-json`, where `info`\n * serves JSON only.\n */\n info_text_supported?(): boolean;\n /** Completion at a position (PLAN-196); absent on a variant built without `editor`. */\n complete?(\n input: string,\n line: number,\n character: number,\n positionEncoding: string,\n includeDeprecated: boolean,\n triggerCharacter: string,\n bitTemplate: boolean,\n ): string;\n /** One completion item with its documentation (PLAN-202); absent on a variant built without `editor`. */\n resolve?(\n input: string,\n line: number,\n character: number,\n positionEncoding: string,\n includeDeprecated: boolean,\n bitTemplate: boolean,\n label: string,\n kind: number,\n ): string;\n /** Hover at a position (PLAN-196); absent on a variant built without `editor`. */\n hover?(input: string, line: number, character: number, positionEncoding: string): string;\n /** Semantic diff (PLAN-179); absent on a variant built without `diff`. */\n diff?(\n a: string,\n b: string,\n inputFormat: string,\n outputFormat: string,\n context: number,\n locate: boolean,\n similarity: number,\n bboxTolerance: number,\n spacesAroundValues: number,\n ): string;\n}\n\n/**\n * The result object of `convert_with_details` (PLAN-221 F7): the output and\n * each extra asked for, without a JSON round trip. One accessor per extra.\n */\nexport interface WasmConvertDetails {\n /** The output, moved out of wasm memory: a second call returns `\"\"`. */\n take_data(): string;\n /** Flat `[index, inputStart, inputEnd, outputStart, outputEnd, …]`;\n * `undefined` when not asked for. */\n bit_spans(): Uint32Array | undefined;\n /** The spans' encoding; `undefined` when not asked for. */\n bit_spans_encoding(): string | undefined;\n free(): void;\n}\n\n/** Options accepted by `init` / `initSync` on every platform entry. */\nexport interface InitOptions {\n /** Which wasm variant to load. Default: `\"full\"`. */\n feature?: Feature;\n}\n\n/** A call arrived before any wasm module was initialised (browser only). */\nexport class NotInitializedError extends Error {\n constructor() {\n super(\"wasm not initialised: call init() (or init({ feature: … })) first\");\n this.name = \"NotInitializedError\";\n }\n}\n\n/** `register` rejected the supplied data. */\nexport class RegisterError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"RegisterError\";\n }\n}\n\n/**\n * `transform` skipped one or more patch entries (PLAN-209 T9). The document\n * was NOT returned: a silently ignored edit is worse than an error. Each\n * diagnostic names the entry's `path`, the error contract's `kind`\n * (`patch-parse-error`, `patch-index-out-of-range`, `patch-type-mismatch`)\n * and a `message`.\n */\nexport class PatchError extends Error {\n readonly diagnostics: PatchDiagnostic[];\n constructor(diagnostics: PatchDiagnostic[]) {\n const lines = diagnostics.map((d) => `${d.kind} at ${d.path}: ${d.message}`);\n super(\n `${diagnostics.length} patch ${diagnostics.length === 1 ? \"entry was\" : \"entries were\"} skipped:\\n` +\n lines.join(\"\\n\"),\n );\n this.name = \"PatchError\";\n this.diagnostics = diagnostics;\n }\n}\n\n/** A call needs a capability the ACTIVE wasm variant compiled out. */\nexport class UnsupportedFeatureError extends Error {\n constructor(what: string, active: Feature | undefined) {\n super(\n `${what} is not available in the loaded wasm module` +\n `${active ? ` (feature: \"${active}\")` : \"\"}; init({ feature: \"full\" }) to load it`,\n );\n this.name = \"UnsupportedFeatureError\";\n }\n}\n\nlet active: WasmModule | undefined;\nlet activeFeature: Feature | undefined;\nlet lazyLoader: (() => void) | undefined;\n\n// Data registered through `register`, kept so it can be REPLAYED onto each\n// newly activated module. Each variant is a separate wasm instance with its\n// own memory, so a registration made against one is invisible to the next —\n// and PLAN-166's promise is that swapping variants is invisible to the\n// caller. Keyed by type, because `register` replaces per type.\nconst registrations = new Map<string, string>();\n\n/**\n * The active wasm module. On Node the entry installs a lazy loader, so the\n * first call without an explicit `init` loads the default (`full`) variant\n * synchronously; in the browser an uninitialised call throws\n * {@link NotInitializedError} (wasm instantiation is async there).\n */\nexport function getActive(): WasmModule {\n if (!active && lazyLoader) lazyLoader();\n if (!active) throw new NotInitializedError();\n return active;\n}\n\n/** Swap the active module — the single atomic reference assignment. */\nexport function setActive(feature: Feature, module: WasmModule): void {\n // Replay first: a module that cannot take the registered data must not\n // become active half-configured. The data was accepted once already, so a\n // failure here is a bug worth surfacing rather than swallowing.\n for (const [type, data] of registrations) {\n try {\n module.register(type, data);\n } catch (e) {\n throw new RegisterError(e instanceof Error ? e.message : String(e));\n }\n }\n active = module;\n activeFeature = feature;\n}\n\n/**\n * Remember what `register` accepted, so it survives an `init` swap. Called\n * only after the ACTIVE module has accepted the same data.\n */\nexport function rememberRegistration(type: string, data: string): void {\n registrations.set(type, data);\n}\n\n/**\n * Install the Node lazy default loader (runs on the first call when no\n * explicit `init` happened; must call {@link setActive}).\n */\nexport function setLazyLoader(loader: () => void): void {\n lazyLoader = loader;\n}\n\n/** The active wasm feature, or `undefined` before the first init. */\nexport function variant(): Feature | undefined {\n return activeFeature;\n}\n\n/**\n * The version of the parser actually running: the active wasm's, else —\n * before any engine is loaded — `fallback`, the wrapper's own (PLAN-224 D3).\n * Never throws and never loads an engine. An active wasm is always the\n * wrapper's release (`init` refuses any other), so the two agree.\n */\n// @zen-impl: PLAN-224 D3\nexport function engineVersion(fallback: string): string {\n return active ? active.version() : fallback;\n}\n","// @zen-component: TS-HookedTransform\n\nimport type { BitView, OutputFormat, PatchDiagnostic, PatchEntry, TransformOptions } from \"../types.js\";\nimport { PatchError } from \"./facade.js\";\n\n/** What the WASM `transform` export returns (PLAN-209 T9). */\nexport interface TransformEnvelope {\n data: string;\n diagnostics: PatchDiagnostic[];\n}\n\n/** One bit's rendered element plus the entries skipped on it. */\nexport interface BitOutput {\n element: string;\n diagnostics: PatchDiagnostic[];\n}\n\n/**\n * The minimal WASM surface the per-bit hook driver needs. Both the Node and\n * browser entry points satisfy this with their loaded module.\n */\nexport interface HookedWasm {\n is_bitmark(input: string): boolean;\n split_bits(input: string, positionEncoding: string): string;\n split_json_bits(input: string): string;\n prettify(json: string, indent: number): string;\n resolve_patch_document(input: string, inputFormat: string, patchJson: string): string;\n /** Returns the {@link TransformEnvelope} as JSON text. */\n transform(\n input: string,\n inputFormat: string,\n outputFormat: string,\n mode: string,\n patchJson: string,\n pretty: boolean,\n indent: number,\n spacesAroundValues: number,\n ): string;\n}\n\n/** A single bit's compute job: data only, safe to ship to a worker thread. */\nexport interface BitJob {\n index: number;\n source: string;\n inputFormat: \"bitmark\" | \"json\";\n outputFormat: OutputFormat;\n mode: string;\n patchJson: string;\n spacesAroundValues: number;\n}\n\n/**\n * The result of running `preHook` over a document's bits: the compute `jobs`\n * for the bits that survived (in document order) and the matching `views`\n * (so `postHook` can be called per surviving bit), plus the document-level\n * recombination settings.\n */\nexport interface TransformPlan {\n jobs: BitJob[];\n views: BitView[];\n output: OutputFormat;\n pretty: boolean;\n indent: number;\n}\n\n/** Strip the outer `[ … ]` of a compact one-element JSON array. */\nexport function stripArray(doc: string): string {\n const t = doc.trim();\n return t.startsWith(\"[\") && t.endsWith(\"]\") ? t.slice(1, -1) : doc;\n}\n\n/**\n * The `patch` option as the JSON text the WASM resolver takes — a patch\n * document or the bare-array every-bit shorthand — or `\"\"` when absent.\n */\nfunction patchJsonOf(patch: TransformOptions[\"patch\"]): string {\n if (patch === undefined) return \"\";\n if (typeof patch === \"string\") return patch.trim();\n return JSON.stringify(patch);\n}\n\n/**\n * One slot of the resolved patch document (see `resolve_patch_document`).\n * An inserted `bit` is a JSON bit or, as a string, one bit of bitmark\n * source (PLAN-209 D15).\n */\ninterface ResolvedItem {\n source: number | null;\n bit: Record<string, unknown> | string | null;\n patch: PatchEntry[];\n}\n\nfunction buildUnits(\n wasm: HookedWasm,\n input: string,\n inputIsBitmark: boolean,\n): { view: BitView; source: string }[] {\n if (inputIsBitmark) {\n // Only `index` / `source` are read: `\"utf-8\"` skips encoding the offsets.\n const slices = JSON.parse(wasm.split_bits(input, \"utf-8\")) as { index: number; source: string }[];\n return slices.map((s) => ({\n view: { format: \"bitmark\", index: s.index, total: slices.length, source: s.source },\n source: s.source,\n }));\n }\n const envelopes = JSON.parse(wasm.split_json_bits(input)) as {\n bit?: import(\"../generated/bit-types.js\").AnyBit;\n }[];\n return envelopes.map((env, index) => ({\n view: { format: \"json\", index, total: envelopes.length, bit: env.bit ?? {} },\n // Per-bit transform consumes a one-bit JSON document.\n source: JSON.stringify([env]),\n }));\n}\n\n/**\n * Split the document and run `preHook` (on the calling thread) for each bit,\n * producing the surviving compute jobs + their views. This is the main-thread\n * half shared by the sync and worker-pool drivers — only the resulting\n * data-only {@link BitJob}s ever cross to a worker.\n */\nexport function planTransform(\n wasm: HookedWasm,\n input: string,\n options: TransformOptions,\n): TransformPlan {\n const mode = options.mode ?? \"optimized\";\n const output: OutputFormat = options.outputFormat ?? \"json\";\n const spacesAroundValues = options.spacesAroundValues ?? 1;\n const indent = options.indent ?? 2;\n const pretty = options.pretty ?? false;\n\n const sel = options.inputFormat ?? \"auto\";\n const inputIsBitmark = sel === \"bitmark\" || (sel !== \"json\" && wasm.is_bitmark(input));\n const inputFormat = inputIsBitmark ? \"bitmark\" : \"json\";\n\n // The engine decides which output formats are per-bit (the whole-document\n // `lex` / `semantic-tokens` / `diagnostics` are not, and it refuses them\n // before any hook runs). Ask it the same way, once, with an empty document:\n // a document that splits into no bits would otherwise never carry that\n // refusal, and the hooked path would answer `\"\"` where the hookless one\n // throws (PLAN-192 D1 parity).\n wasm.transform(\"\", \"bitmark\", output, mode, \"\", false, indent, spacesAroundValues);\n\n const units = buildUnits(wasm, input, inputIsBitmark);\n const total = units.length;\n\n // Hooks see the ORIGINAL bits, in order, before the patch document\n // reshapes anything (same as the Rust pipeline's Phase A).\n const hooked: ({ patches: PatchEntry[]; outputFormat: OutputFormat } | null)[] = [];\n for (let index = 0; index < total; index++) {\n const { view } = units[index];\n view.index = index;\n view.total = total;\n const result = options.preHook ? options.preHook(view) || {} : {};\n hooked.push(result.drop ? null : { patches: result.patches ?? [], outputFormat: result.outputFormat ?? output });\n }\n\n // The patch document (or the every-bit shorthand) is resolved by the one\n // Rust implementation into the ordered output slots; without a patch the\n // slots are simply the original bits. The resolver splits under the SAME\n // input format resolved above — an explicit selection is never re-sniffed.\n const patchJson = patchJsonOf(options.patch);\n let resolved: ResolvedItem[];\n if (patchJson === \"\") {\n resolved = units.map((_, index) => ({ source: index, bit: null, patch: [] }));\n } else {\n resolved = JSON.parse(\n wasm.resolve_patch_document(input, inputFormat, patchJson),\n ) as ResolvedItem[];\n }\n\n const jobs: BitJob[] = [];\n const views: BitView[] = [];\n for (const item of resolved) {\n if (item.source !== null) {\n const hook = hooked[item.source];\n if (!hook) continue; // dropped by the pre-hook\n jobs.push({\n index: item.source,\n source: units[item.source].source,\n inputFormat,\n outputFormat: hook.outputFormat,\n mode,\n // The document's patches first, the hook's after (the hook contract).\n patchJson: JSON.stringify([...item.patch, ...hook.patches]),\n spacesAroundValues,\n });\n views.push(units[item.source].view);\n } else if (typeof item.bit === \"string\") {\n // An inserted bitmark bit (PLAN-209 D15), seen by `postHook` only.\n const index = jobs.length;\n jobs.push({\n index,\n source: item.bit,\n inputFormat: \"bitmark\",\n outputFormat: output,\n mode,\n patchJson: JSON.stringify(item.patch),\n spacesAroundValues,\n });\n views.push({ format: \"bitmark\", index, total, source: item.bit });\n } else {\n // An inserted bit: a one-bit JSON document, seen by `postHook` only.\n const bit = (item.bit ?? {}) as import(\"../generated/bit-types.js\").AnyBit;\n const index = jobs.length;\n jobs.push({\n index,\n source: JSON.stringify([{ bit }]),\n inputFormat: \"json\",\n outputFormat: output,\n mode,\n patchJson: JSON.stringify(item.patch),\n spacesAroundValues,\n });\n views.push({ format: \"json\", index, total, bit });\n }\n }\n return { jobs, views, output, pretty, indent };\n}\n\n/**\n * Read the WASM `transform` envelope for one bit: the element (compact JSON\n * stripped of its one-element array) and the entries skipped on it.\n */\nexport function bitOutput(envelopeJson: string, outputFormat: OutputFormat): BitOutput {\n const { data, diagnostics } = JSON.parse(envelopeJson) as TransformEnvelope;\n return { element: outputFormat === \"json\" ? stripArray(data) : data, diagnostics };\n}\n\n/**\n * Compute one bit's rendered element synchronously via WASM. The per-bit\n * element is always compact JSON (the document is prettified once on\n * recombination). Throws on failure; skipped entries come back as\n * diagnostics for the driver to raise.\n */\nexport function computeBit(wasm: HookedWasm, job: BitJob): BitOutput {\n return bitOutput(\n wasm.transform(\n job.source,\n job.inputFormat,\n job.outputFormat,\n job.mode,\n job.patchJson,\n false,\n 2,\n job.spacesAroundValues,\n ),\n job.outputFormat,\n );\n}\n\n/** Recombine per-bit elements into the final document, in document order. */\nexport function recombine(\n wasm: HookedWasm,\n elements: string[],\n output: OutputFormat,\n pretty: boolean,\n indent: number,\n): string {\n // Only JSON wraps as an array; every other per-bit output (bitmark, plain\n // text, a mapping such as html) is text and joins with newlines — the same\n // rule as the Rust pipeline's recombine.\n if (output !== \"json\") return elements.join(\"\\n\");\n const arr = `[${elements.join(\",\")}]`;\n return pretty ? wasm.prettify(arr, indent) : arr;\n}\n\n/**\n * Per-bit transform driver (synchronous, in-process): split the document, run\n * `preHook` for each bit on the calling thread, apply the static patch + any\n * hook patches, re-emit the bit, run `postHook`, then recombine in document\n * order.\n *\n * Hooks run synchronously on the calling thread; only data crosses into WASM.\n * Used when a `preHook`/`postHook` is supplied; the hookless path stays a\n * single `wasm.transform` call. Throws on failure (PLAN-192 D1), and a\n * `PatchError` when any entry was skipped (PLAN-209 T9).\n */\nexport function runHookedTransform(\n wasm: HookedWasm,\n input: string,\n options: TransformOptions,\n): string {\n const plan: TransformPlan = planTransform(wasm, input, options);\n\n const rendered: string[] = [];\n const skipped: PatchDiagnostic[] = [];\n for (let i = 0; i < plan.jobs.length; i++) {\n const { element, diagnostics } = computeBit(wasm, plan.jobs[i]);\n skipped.push(...diagnostics);\n options.postHook?.(plan.views[i], element);\n rendered.push(element);\n }\n // A skipped entry is never silent (PLAN-209 T9).\n if (skipped.length > 0) throw new PatchError(skipped);\n return recombine(wasm, rendered, plan.output, plan.pretty, plan.indent);\n}\n","// @zen-component: TS-WorkerEntry\n//\n// Node worker_threads entry for the parallel transform pool. Each worker loads\n// its own WASM instance — the VARIANT the spawning pool passes via workerData\n// (PLAN-166) — and computes one bit at a time from a data-only job. Hooks (JS\n// closures) never reach here — they run on the main thread.\n\nimport { createRequire } from \"node:module\";\nimport { parentPort, workerData } from \"node:worker_threads\";\nimport { type BitJob, bitOutput } from \"../core/hooked-transform.js\";\nimport { NODE_DEFAULT_FEATURE, type Feature } from \"../core/facade.js\";\n\nconst require = createRequire(import.meta.url);\n\n// Bundled to dist/worker-entry.cjs; the wasm glue dirs sit at the package\n// root. Mirrors the entry's variant map (facade.ts).\nconst VARIANT_GLUE: Record<Feature, string> = {\n full: \"../wasm/bitmark_wasm.js\",\n \"browser-full\": \"../wasm-browser-full/bitmark_browser_full_wasm.js\",\n \"bitmark-json\": \"../wasm-bitmark-json/bitmark_json_wasm.js\",\n};\n\nconst feature = ((workerData as { feature?: Feature } | undefined)?.feature ??\n NODE_DEFAULT_FEATURE) as Feature;\nconst wasm = require(VARIANT_GLUE[feature]) as {\n transform(\n input: string,\n inputFormat: string,\n outputFormat: string,\n mode: string,\n patchJson: string,\n pretty: boolean,\n indent: number,\n spacesAroundValues: number,\n ): string;\n};\n\ninterface JobMessage {\n id: number;\n job: BitJob;\n}\n\nparentPort?.on(\"message\", (msg: JobMessage) => {\n const { id, job } = msg;\n try {\n const { element, diagnostics } = bitOutput(\n wasm.transform(\n job.source,\n job.inputFormat,\n job.outputFormat,\n job.mode,\n job.patchJson,\n false,\n 2,\n job.spacesAroundValues,\n ),\n job.outputFormat,\n );\n parentPort?.postMessage({ id, ok: true, element, diagnostics });\n } catch (e) {\n // The wasm export throws on failure (PLAN-192 D1); the pool turns this\n // message back into a rejected promise on the main thread.\n parentPort?.postMessage({ id, ok: false, error: e instanceof Error ? e.message : String(e) });\n }\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmb/bitmark-parser",
3
- "version": "7.7.0",
3
+ "version": "7.9.0",
4
4
  "description": "A parser for bitmark text, powered by WebAssembly.",
5
5
  "author": "Get More Brain Ltd <info@getmorebrain.com>",
6
6
  "license": "ISC",
@@ -80,7 +80,7 @@
80
80
  "docs": "typedoc",
81
81
  "docs:serve": "npm run docs && node scripts/serve-docs.mjs",
82
82
  "build": "npm run version:check && npm run build:wasm && npm run build:ts && npm run build:binaries",
83
- "test": "node --test test/launcher-e2e.mjs && node test/smoke.mjs && node --test test/cross-validate.mjs && node --test test/cli.test.mjs && node --test test/unified.test.mjs && node --test test/bit-spans.test.mjs && node --test test/diff.test.mjs && node --test test/parallel.test.mjs && node --test test/transform-formats.test.mjs && node --test test/pool.test.mjs && node --test test/browser-init.test.mjs && node --test test/legacy.test.mjs && node --test test/signature-compat.test.mjs && node --test test/typed.test.mjs && node --test test/examples.test.mjs && node --test test/variant.test.mjs && npm run test:variants",
83
+ "test": "node --test test/launcher-e2e.mjs && node test/smoke.mjs && node --test test/cross-validate.mjs && node --test test/cli.test.mjs && node --test test/unified.test.mjs && node --test test/bit-spans.test.mjs && node --test test/diff.test.mjs && node --test test/parallel.test.mjs && node --test test/transform-formats.test.mjs && node --test test/pool.test.mjs && node --test test/browser-init.test.mjs && node --test test/browser-wasm-version.test.mjs && node --test test/legacy.test.mjs && node --test test/signature-compat.test.mjs && node --test test/typed.test.mjs && node --test test/examples.test.mjs && node --test test/variant.test.mjs && npm run test:variants",
84
84
  "test:variants": "BITMARK_TEST_VARIANT=browser-full node test/smoke.mjs && BITMARK_TEST_VARIANT=browser-full node --test test/cross-validate.mjs",
85
85
  "test:browser": "bash scripts/run-browser-tests.sh",
86
86
  "test:browser-smoke": "bash scripts/launch-browser-smoke.sh",
@@ -89,11 +89,11 @@
89
89
  "prepublishOnly": "npm run build && npm test"
90
90
  },
91
91
  "optionalDependencies": {
92
- "@gmb/bitmark-parser-darwin-arm64": "7.7.0",
93
- "@gmb/bitmark-parser-darwin-x64": "7.7.0",
94
- "@gmb/bitmark-parser-linux-arm64": "7.7.0",
95
- "@gmb/bitmark-parser-linux-x64": "7.7.0",
96
- "@gmb/bitmark-parser-win32-x64": "7.7.0"
92
+ "@gmb/bitmark-parser-darwin-arm64": "7.9.0",
93
+ "@gmb/bitmark-parser-darwin-x64": "7.9.0",
94
+ "@gmb/bitmark-parser-linux-arm64": "7.9.0",
95
+ "@gmb/bitmark-parser-linux-x64": "7.9.0",
96
+ "@gmb/bitmark-parser-win32-x64": "7.9.0"
97
97
  },
98
98
  "devDependencies": {
99
99
  "@types/node": "^25.2.2",
@@ -56601,7 +56601,7 @@
56601
56601
  ]
56602
56602
  }
56603
56603
  },
56604
- "$id": "https://schema.getmorebrain.com/bitmark/7.7.0.schema.json",
56604
+ "$id": "https://schema.getmorebrain.com/bitmark/7.9.0.schema.json",
56605
56605
  "$schema": "https://json-schema.org/draft/2020-12/schema",
56606
56606
  "description": "Generated from bitmark.json by bitmark-schemagen (PLAN-117). Do not edit by hand.",
56607
56607
  "items": {
@@ -45,6 +45,29 @@ export function info(info_type: string, format: string, bit: string, pretty: boo
45
45
  * variant name.
46
46
  */
47
47
  export function info_text_supported(): boolean;
48
+ /**
49
+ * Split a JSON document (a top-level array of bit envelopes) into its per-bit
50
+ * envelope objects, returned as a JSON array string. Lets a JS caller iterate
51
+ * JSON-input bits for per-bit hooks (the JSON counterpart of [`split_bits`]).
52
+ * Throws if the input is not a JSON array of bits.
53
+ */
54
+ export function split_json_bits(input: string): string;
55
+ /**
56
+ * Pretty-print a compact JSON string with the given indent. Used to format a
57
+ * document recombined on the JS side (the per-bit hook path) so its output
58
+ * matches the in-Rust recombination byte-for-byte.
59
+ */
60
+ export function prettify(json: string, indent: number): string;
61
+ /**
62
+ * Convert a standalone bitmark text fragment ↔ TextAst (ProseMirror JSON).
63
+ *
64
+ * `direction`: `"textToAst"` | `"astToText"` | `"astToPlainText"`.
65
+ * `format`: a bitmark text format (`"bitmark++"`, `"bitmark+"`) parses
66
+ * inline marks; anything else is treated as plain text (textToAst only).
67
+ * `location`: `"body"` (default) or `"tag"`.
68
+ * Returns the converted fragment; throws on failure.
69
+ */
70
+ export function convert_text(input: string, direction: string, format: string, location: string): string;
48
71
  /**
49
72
  * Apply a patch (a JSON array of patch entries) to every bit and re-emit.
50
73
  *
@@ -96,11 +119,12 @@ export function diff(a: string, b: string, input_format_sel: string, output_form
96
119
  * `trigger_character` is the character the editor sent as the trigger (LSP
97
120
  * `CompletionContext.triggerCharacter`; empty when invoked explicitly) — a
98
121
  * trigger that opens nothing where the cursor is answers the empty list
99
- * (PLAN-203 D1). ABSENT on a variant built without `editor` — the same
100
- * "presence is the capability" signal `diff` uses. Throws when the line is
101
- * past the text.
122
+ * (PLAN-203 D1); `bit_template` makes a bit-type item insert the bit's
123
+ * normal template as a snippet (PLAN-225 D9). ABSENT on a variant built
124
+ * without `editor` — the same "presence is the capability" signal `diff`
125
+ * uses. Throws when the line is past the text.
102
126
  */
103
- export function complete(input: string, line: number, character: number, position_encoding: string, include_deprecated: boolean, trigger_character: string): string;
127
+ export function complete(input: string, line: number, character: number, position_encoding: string, include_deprecated: boolean, trigger_character: string, bit_template: boolean): string;
104
128
  /**
105
129
  * One item of the list `complete` returns at this position, its Markdown
106
130
  * `documentation` filled in — LSP `completionItem/resolve` (PLAN-202):
@@ -109,12 +133,27 @@ export function complete(input: string, line: number, character: number, positio
109
133
  * name the item; the JSON `null` when the list has no such item. Arguments
110
134
  * otherwise as `complete`.
111
135
  */
112
- export function resolve(input: string, line: number, character: number, position_encoding: string, include_deprecated: boolean, label: string, kind: number): string;
136
+ export function resolve(input: string, line: number, character: number, position_encoding: string, include_deprecated: boolean, bit_template: boolean, label: string, kind: number): string;
113
137
  /**
114
138
  * The construct at a position, as an LSP `Hover` — or the JSON `null` when
115
139
  * there is nothing to show (PLAN-196 D8). Arguments as `complete`.
116
140
  */
117
141
  export function hover(input: string, line: number, character: number, position_encoding: string): string;
142
+ /**
143
+ * One bit's template (PLAN-225 D8): `format` is `text` (plain bitmark),
144
+ * `snippet` (LSP snippet) or `json` (the slot model with both renderings);
145
+ * `full` lists every tag and every attachable resource chain instead of
146
+ * the normal template; `include_deprecated` keeps deprecated tags. Throws
147
+ * for an unknown bit. ABSENT on a variant built without `template` — the
148
+ * "presence is the capability" signal `diff` uses.
149
+ */
150
+ export function template(bit: string, format: string, full: boolean, include_deprecated: boolean, pretty: boolean, indent: number): string;
151
+ /**
152
+ * Every authorable bit's `normal` and `full` template as one JSON object
153
+ * keyed by bit name (PLAN-225 D8); `include_deprecated` admits deprecated
154
+ * bits and tags.
155
+ */
156
+ export function template_all(include_deprecated: boolean, pretty: boolean, indent: number): string;
118
157
  /**
119
158
  * Register data with the parser process (PLAN-173 P12).
120
159
  *
@@ -152,34 +191,17 @@ export function count_bits(input: string): string;
152
191
  * feed a worker pool).
153
192
  */
154
193
  export function split_bits(input: string, position_encoding: string): string;
194
+ /**
195
+ * The version of this wasm (the crate version, kept in lockstep with the npm
196
+ * package). The browser entry checks it against its own before using the
197
+ * module, so a wrapper never drives another release's wasm (PLAN-224 D2).
198
+ */
199
+ export function version(): string;
155
200
  /**
156
201
  * Whether `input` looks like bitmark (vs JSON). Mirrors the auto-detection
157
202
  * used across the API so a JS caller can resolve `"auto"` consistently.
158
203
  */
159
204
  export function is_bitmark(input: string): boolean;
160
- /**
161
- * Split a JSON document (a top-level array of bit envelopes) into its per-bit
162
- * envelope objects, returned as a JSON array string. Lets a JS caller iterate
163
- * JSON-input bits for per-bit hooks (the JSON counterpart of [`split_bits`]).
164
- * Throws if the input is not a JSON array of bits.
165
- */
166
- export function split_json_bits(input: string): string;
167
- /**
168
- * Pretty-print a compact JSON string with the given indent. Used to format a
169
- * document recombined on the JS side (the per-bit hook path) so its output
170
- * matches the in-Rust recombination byte-for-byte.
171
- */
172
- export function prettify(json: string, indent: number): string;
173
- /**
174
- * Convert a standalone bitmark text fragment ↔ TextAst (ProseMirror JSON).
175
- *
176
- * `direction`: `"textToAst"` | `"astToText"` | `"astToPlainText"`.
177
- * `format`: a bitmark text format (`"bitmark++"`, `"bitmark+"`) parses
178
- * inline marks; anything else is treated as plain text (textToAst only).
179
- * `location`: `"body"` (default) or `"tag"`.
180
- * Returns the converted fragment; throws on failure.
181
- */
182
- export function convert_text(input: string, direction: string, format: string, location: string): string;
183
205
  /**
184
206
  * Convert between bitmark and JSON with explicit input/output formats + mode.
185
207
  *
@@ -219,8 +241,8 @@ export class ConvertDetails {
219
241
  */
220
242
  bit_spans_encoding(): string | undefined;
221
243
  /**
222
- * The bit spans, flat `[index, start, end, …]`; `undefined` when not
223
- * asked for.
244
+ * The bit spans, flat `[index, inputStart, inputEnd, outputStart,
245
+ * outputEnd, …]` (PLAN-223 F5); `undefined` when not asked for.
224
246
  */
225
247
  bit_spans(): Uint32Array | undefined;
226
248
  /**