@ai-react-markdown/engine 2.3.0 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1393,14 +1393,51 @@ declare function normalizeForMatch(s: string): string;
1393
1393
  *
1394
1394
  * @module components/shortenDocumentId
1395
1395
  */
1396
+ /**
1397
+ * Whether `id` is ill-formed UTF-16 (contains at least one unpaired
1398
+ * surrogate — typically the fallout of an upstream pipeline slicing a
1399
+ * string mid-emoji). Exported as the single owner of the detection
1400
+ * semantics so consumers (e.g. core's dev-mode documentId warning) cannot
1401
+ * drift from the branch condition inside `shortenDocumentId`.
1402
+ */
1403
+ declare function hasLoneSurrogate(id: string): boolean;
1396
1404
  /**
1397
1405
  * Shorten a `documentId` for use inside an HTML id prefix.
1398
1406
  *
1407
+ * Well-formed ids keep the original contract byte-for-byte: at or below
1408
+ * `threshold` they pass through verbatim; above it they hash via
1409
+ * MurmurHash3 over UTF-8 bytes to a 1–6 char Base62 form.
1410
+ *
1411
+ * Ill-formed UTF-16 ids (unpaired surrogates, e.g. from an upstream
1412
+ * pipeline slicing a string mid-emoji) are ALWAYS hashed, regardless of
1413
+ * length — over their raw UTF-16LE code units, seeded with
1414
+ * `ILL_FORMED_SEED`. Rationale (issue #32):
1415
+ *
1416
+ * - Returned verbatim, a lone surrogate makes the caller's
1417
+ * `encodeURIComponent` throw `URIError: URI malformed` synchronously,
1418
+ * aborting the whole render before any markdown is parsed.
1419
+ * - Hashing the WHATWG lossy projection (lone surrogates → U+FFFD, what
1420
+ * `TextEncoder`/`toWellFormed()` do) was rejected: it silently merges
1421
+ * distinct corrupted ids — and merges them with a well-formed id
1422
+ * containing a literal U+FFFD — turning the old loud error into a
1423
+ * silent cross-document anchor collision.
1424
+ * - Escaping into a well-formed string before hashing was rejected too:
1425
+ * a naive marker scheme is not injective (marker-lookalike content
1426
+ * forges it), and an escape-the-escape scheme is strictly more code
1427
+ * than hashing the raw code units, which is injective for free.
1428
+ *
1429
+ * Distinct ids therefore produce distinct prefixes up to the generic 2^32
1430
+ * birthday bound — the same guarantee long well-formed ids always had —
1431
+ * with no equivalence-class carve-out for corrupted input.
1432
+ *
1399
1433
  * @param id - The raw documentId (consumer-supplied or `useId()` fallback).
1400
- * @param threshold - Only ids strictly longer than this get hashed.
1401
- * Defaults to `16`, which leaves short hand-picked ids and React's
1402
- * `useId()` outputs unchanged but catches UUIDs (36 chars) and nanoids.
1403
- * @returns Either the original `id` (when short) or a 1–6 char Base62 hash.
1434
+ * Any JavaScript string is accepted, including ill-formed UTF-16.
1435
+ * @param threshold - Only well-formed ids strictly longer than this get
1436
+ * hashed. Defaults to `16`, which leaves short hand-picked ids and
1437
+ * React's `useId()` outputs unchanged but catches UUIDs (36 chars) and
1438
+ * nanoids. Ill-formed ids ignore the threshold — they always hash.
1439
+ * @returns The original `id` (well-formed and short) or a 1–6 char Base62
1440
+ * hash (well-formed long, or ill-formed of any length).
1404
1441
  */
1405
1442
  declare function shortenDocumentId(id: string, threshold?: number): string;
1406
1443
 
@@ -1860,4 +1897,4 @@ type RemendPreprocessorOptions = Omit<RemendOptions, 'katex' | 'inlineKatex'>;
1860
1897
  */
1861
1898
  declare function createRemendPreprocessor(options?: RemendPreprocessorOptions): AIMDContentPreprocessor;
1862
1899
 
1863
- export { type AIMDContentPreprocessor, type AIMarkdownEnginePlugin, type AIMarkdownEnginePluginName, type AdvanceOptions, type AdvanceResult, type AllowElement, type ChunkData, type Contribution, type CrossChunkHandlerOptions, DEFAULT_PAYLOAD, DEF_LINE_START_RE, type DefLabelScanner, type DefLabels, type Deprecation, type EnginePluginInternals, type EnginePluginStage, type ExtractContributionsOptions, type FootnoteDef, type FreezeBoundaryOptions, type IncrementalParseState, type IncrementalStage, type LinkDef, PIPELINE_STAGES, type ParsedMarkdown, type PhantomLabels, type PipelineOptions, type PipelineStage, type RefKind, type RefRecord, type Registry, type RegistryInternal, type RehypePlugins, type RehypeRebaseHashLinksOptions, type RemarkPlugins, type RemarkRehypeOptions, type RemendPreprocessorOptions, SENTINEL_FN_CONTENT, SENTINEL_LINK_URL, SMOOTH_STREAM_PACING_PRESETS, STAGE_MEASURE_PREFIX, type SanitizeSchema, type SmoothStreamController, type SmoothStreamOptions, type SmoothStreamPacing, type SmoothStreamPacingParams, type TransformContext, type UrlAttrKey, type UrlAttrTag, type UrlTransform, advanceIncrementalParse, attributeHastChildren, buildCoreRehypePlugins, buildCoreRemarkPlugins, buildCoreRemarkRehypeOptions, buildCrossChunkHandlers, buildPhantomSuffix, buildTransform, codePointSnapshots, collectDefLabels, computeFreezeBoundary, createDefLabelScanner, createFile, createIncrementalLatexPreprocessor, createProcessor, createRegistry, createRemendPreprocessor, createSmoothStreamController, defaultEnginePlugins, defaultUrlTransform, definitionList, extendSanitizeSchema, extractContributions, extractDefBodiesFromHast, getEnginePluginInternals, highlight, isFootnoteSection, lastRegionStart, measureStage, mergeClassNameAllowlist, normalizeForMatch, normalizeId, pangu, parseStage, preprocessAIMDContent, preprocessLaTeX, rehypeFooterAdorn, rehypeRebaseHashLinks, removeComments, sanitizeCrossChunkUrl, sanitizeSchema, shortenDocumentId, smartypants, sourceIdFromFootnoteLiId, splitByProtectedRegions, subscribeStageTimings, transformStage, withDefs };
1900
+ export { type AIMDContentPreprocessor, type AIMarkdownEnginePlugin, type AIMarkdownEnginePluginName, type AdvanceOptions, type AdvanceResult, type AllowElement, type ChunkData, type Contribution, type CrossChunkHandlerOptions, DEFAULT_PAYLOAD, DEF_LINE_START_RE, type DefLabelScanner, type DefLabels, type Deprecation, type EnginePluginInternals, type EnginePluginStage, type ExtractContributionsOptions, type FootnoteDef, type FreezeBoundaryOptions, type IncrementalParseState, type IncrementalStage, type LinkDef, PIPELINE_STAGES, type ParsedMarkdown, type PhantomLabels, type PipelineOptions, type PipelineStage, type RefKind, type RefRecord, type Registry, type RegistryInternal, type RehypePlugins, type RehypeRebaseHashLinksOptions, type RemarkPlugins, type RemarkRehypeOptions, type RemendPreprocessorOptions, SENTINEL_FN_CONTENT, SENTINEL_LINK_URL, SMOOTH_STREAM_PACING_PRESETS, STAGE_MEASURE_PREFIX, type SanitizeSchema, type SmoothStreamController, type SmoothStreamOptions, type SmoothStreamPacing, type SmoothStreamPacingParams, type TransformContext, type UrlAttrKey, type UrlAttrTag, type UrlTransform, advanceIncrementalParse, attributeHastChildren, buildCoreRehypePlugins, buildCoreRemarkPlugins, buildCoreRemarkRehypeOptions, buildCrossChunkHandlers, buildPhantomSuffix, buildTransform, codePointSnapshots, collectDefLabels, computeFreezeBoundary, createDefLabelScanner, createFile, createIncrementalLatexPreprocessor, createProcessor, createRegistry, createRemendPreprocessor, createSmoothStreamController, defaultEnginePlugins, defaultUrlTransform, definitionList, extendSanitizeSchema, extractContributions, extractDefBodiesFromHast, getEnginePluginInternals, hasLoneSurrogate, highlight, isFootnoteSection, lastRegionStart, measureStage, mergeClassNameAllowlist, normalizeForMatch, normalizeId, pangu, parseStage, preprocessAIMDContent, preprocessLaTeX, rehypeFooterAdorn, rehypeRebaseHashLinks, removeComments, sanitizeCrossChunkUrl, sanitizeSchema, shortenDocumentId, smartypants, sourceIdFromFootnoteLiId, splitByProtectedRegions, subscribeStageTimings, transformStage, withDefs };
package/dist/index.d.ts CHANGED
@@ -1393,14 +1393,51 @@ declare function normalizeForMatch(s: string): string;
1393
1393
  *
1394
1394
  * @module components/shortenDocumentId
1395
1395
  */
1396
+ /**
1397
+ * Whether `id` is ill-formed UTF-16 (contains at least one unpaired
1398
+ * surrogate — typically the fallout of an upstream pipeline slicing a
1399
+ * string mid-emoji). Exported as the single owner of the detection
1400
+ * semantics so consumers (e.g. core's dev-mode documentId warning) cannot
1401
+ * drift from the branch condition inside `shortenDocumentId`.
1402
+ */
1403
+ declare function hasLoneSurrogate(id: string): boolean;
1396
1404
  /**
1397
1405
  * Shorten a `documentId` for use inside an HTML id prefix.
1398
1406
  *
1407
+ * Well-formed ids keep the original contract byte-for-byte: at or below
1408
+ * `threshold` they pass through verbatim; above it they hash via
1409
+ * MurmurHash3 over UTF-8 bytes to a 1–6 char Base62 form.
1410
+ *
1411
+ * Ill-formed UTF-16 ids (unpaired surrogates, e.g. from an upstream
1412
+ * pipeline slicing a string mid-emoji) are ALWAYS hashed, regardless of
1413
+ * length — over their raw UTF-16LE code units, seeded with
1414
+ * `ILL_FORMED_SEED`. Rationale (issue #32):
1415
+ *
1416
+ * - Returned verbatim, a lone surrogate makes the caller's
1417
+ * `encodeURIComponent` throw `URIError: URI malformed` synchronously,
1418
+ * aborting the whole render before any markdown is parsed.
1419
+ * - Hashing the WHATWG lossy projection (lone surrogates → U+FFFD, what
1420
+ * `TextEncoder`/`toWellFormed()` do) was rejected: it silently merges
1421
+ * distinct corrupted ids — and merges them with a well-formed id
1422
+ * containing a literal U+FFFD — turning the old loud error into a
1423
+ * silent cross-document anchor collision.
1424
+ * - Escaping into a well-formed string before hashing was rejected too:
1425
+ * a naive marker scheme is not injective (marker-lookalike content
1426
+ * forges it), and an escape-the-escape scheme is strictly more code
1427
+ * than hashing the raw code units, which is injective for free.
1428
+ *
1429
+ * Distinct ids therefore produce distinct prefixes up to the generic 2^32
1430
+ * birthday bound — the same guarantee long well-formed ids always had —
1431
+ * with no equivalence-class carve-out for corrupted input.
1432
+ *
1399
1433
  * @param id - The raw documentId (consumer-supplied or `useId()` fallback).
1400
- * @param threshold - Only ids strictly longer than this get hashed.
1401
- * Defaults to `16`, which leaves short hand-picked ids and React's
1402
- * `useId()` outputs unchanged but catches UUIDs (36 chars) and nanoids.
1403
- * @returns Either the original `id` (when short) or a 1–6 char Base62 hash.
1434
+ * Any JavaScript string is accepted, including ill-formed UTF-16.
1435
+ * @param threshold - Only well-formed ids strictly longer than this get
1436
+ * hashed. Defaults to `16`, which leaves short hand-picked ids and
1437
+ * React's `useId()` outputs unchanged but catches UUIDs (36 chars) and
1438
+ * nanoids. Ill-formed ids ignore the threshold — they always hash.
1439
+ * @returns The original `id` (well-formed and short) or a 1–6 char Base62
1440
+ * hash (well-formed long, or ill-formed of any length).
1404
1441
  */
1405
1442
  declare function shortenDocumentId(id: string, threshold?: number): string;
1406
1443
 
@@ -1860,4 +1897,4 @@ type RemendPreprocessorOptions = Omit<RemendOptions, 'katex' | 'inlineKatex'>;
1860
1897
  */
1861
1898
  declare function createRemendPreprocessor(options?: RemendPreprocessorOptions): AIMDContentPreprocessor;
1862
1899
 
1863
- export { type AIMDContentPreprocessor, type AIMarkdownEnginePlugin, type AIMarkdownEnginePluginName, type AdvanceOptions, type AdvanceResult, type AllowElement, type ChunkData, type Contribution, type CrossChunkHandlerOptions, DEFAULT_PAYLOAD, DEF_LINE_START_RE, type DefLabelScanner, type DefLabels, type Deprecation, type EnginePluginInternals, type EnginePluginStage, type ExtractContributionsOptions, type FootnoteDef, type FreezeBoundaryOptions, type IncrementalParseState, type IncrementalStage, type LinkDef, PIPELINE_STAGES, type ParsedMarkdown, type PhantomLabels, type PipelineOptions, type PipelineStage, type RefKind, type RefRecord, type Registry, type RegistryInternal, type RehypePlugins, type RehypeRebaseHashLinksOptions, type RemarkPlugins, type RemarkRehypeOptions, type RemendPreprocessorOptions, SENTINEL_FN_CONTENT, SENTINEL_LINK_URL, SMOOTH_STREAM_PACING_PRESETS, STAGE_MEASURE_PREFIX, type SanitizeSchema, type SmoothStreamController, type SmoothStreamOptions, type SmoothStreamPacing, type SmoothStreamPacingParams, type TransformContext, type UrlAttrKey, type UrlAttrTag, type UrlTransform, advanceIncrementalParse, attributeHastChildren, buildCoreRehypePlugins, buildCoreRemarkPlugins, buildCoreRemarkRehypeOptions, buildCrossChunkHandlers, buildPhantomSuffix, buildTransform, codePointSnapshots, collectDefLabels, computeFreezeBoundary, createDefLabelScanner, createFile, createIncrementalLatexPreprocessor, createProcessor, createRegistry, createRemendPreprocessor, createSmoothStreamController, defaultEnginePlugins, defaultUrlTransform, definitionList, extendSanitizeSchema, extractContributions, extractDefBodiesFromHast, getEnginePluginInternals, highlight, isFootnoteSection, lastRegionStart, measureStage, mergeClassNameAllowlist, normalizeForMatch, normalizeId, pangu, parseStage, preprocessAIMDContent, preprocessLaTeX, rehypeFooterAdorn, rehypeRebaseHashLinks, removeComments, sanitizeCrossChunkUrl, sanitizeSchema, shortenDocumentId, smartypants, sourceIdFromFootnoteLiId, splitByProtectedRegions, subscribeStageTimings, transformStage, withDefs };
1900
+ export { type AIMDContentPreprocessor, type AIMarkdownEnginePlugin, type AIMarkdownEnginePluginName, type AdvanceOptions, type AdvanceResult, type AllowElement, type ChunkData, type Contribution, type CrossChunkHandlerOptions, DEFAULT_PAYLOAD, DEF_LINE_START_RE, type DefLabelScanner, type DefLabels, type Deprecation, type EnginePluginInternals, type EnginePluginStage, type ExtractContributionsOptions, type FootnoteDef, type FreezeBoundaryOptions, type IncrementalParseState, type IncrementalStage, type LinkDef, PIPELINE_STAGES, type ParsedMarkdown, type PhantomLabels, type PipelineOptions, type PipelineStage, type RefKind, type RefRecord, type Registry, type RegistryInternal, type RehypePlugins, type RehypeRebaseHashLinksOptions, type RemarkPlugins, type RemarkRehypeOptions, type RemendPreprocessorOptions, SENTINEL_FN_CONTENT, SENTINEL_LINK_URL, SMOOTH_STREAM_PACING_PRESETS, STAGE_MEASURE_PREFIX, type SanitizeSchema, type SmoothStreamController, type SmoothStreamOptions, type SmoothStreamPacing, type SmoothStreamPacingParams, type TransformContext, type UrlAttrKey, type UrlAttrTag, type UrlTransform, advanceIncrementalParse, attributeHastChildren, buildCoreRehypePlugins, buildCoreRemarkPlugins, buildCoreRemarkRehypeOptions, buildCrossChunkHandlers, buildPhantomSuffix, buildTransform, codePointSnapshots, collectDefLabels, computeFreezeBoundary, createDefLabelScanner, createFile, createIncrementalLatexPreprocessor, createProcessor, createRegistry, createRemendPreprocessor, createSmoothStreamController, defaultEnginePlugins, defaultUrlTransform, definitionList, extendSanitizeSchema, extractContributions, extractDefBodiesFromHast, getEnginePluginInternals, hasLoneSurrogate, highlight, isFootnoteSection, lastRegionStart, measureStage, mergeClassNameAllowlist, normalizeForMatch, normalizeId, pangu, parseStage, preprocessAIMDContent, preprocessLaTeX, rehypeFooterAdorn, rehypeRebaseHashLinks, removeComments, sanitizeCrossChunkUrl, sanitizeSchema, shortenDocumentId, smartypants, sourceIdFromFootnoteLiId, splitByProtectedRegions, subscribeStageTimings, transformStage, withDefs };
@@ -62,6 +62,7 @@ __export(src_exports, {
62
62
  extractContributions: () => extractContributions,
63
63
  extractDefBodiesFromHast: () => extractDefBodiesFromHast,
64
64
  getEnginePluginInternals: () => getEnginePluginInternals,
65
+ hasLoneSurrogate: () => hasLoneSurrogate,
65
66
  highlight: () => highlight,
66
67
  isFootnoteSection: () => isFootnoteSection,
67
68
  lastRegionStart: () => lastRegionStart,
@@ -3319,7 +3320,9 @@ var C1 = 3432918353;
3319
3320
  var C2 = 461845907;
3320
3321
  var UTF8_ENCODER = /* @__PURE__ */ new TextEncoder();
3321
3322
  function murmur3_32(s, seed = 0) {
3322
- const bytes = UTF8_ENCODER.encode(s);
3323
+ return murmur3_32_bytes(UTF8_ENCODER.encode(s), seed);
3324
+ }
3325
+ function murmur3_32_bytes(bytes, seed = 0) {
3323
3326
  const len = bytes.length;
3324
3327
  let h1 = seed >>> 0;
3325
3328
  const nblocks = len >>> 2;
@@ -3353,7 +3356,22 @@ function murmur3_32(s, seed = 0) {
3353
3356
  h1 ^= h1 >>> 16;
3354
3357
  return h1 >>> 0;
3355
3358
  }
3359
+ var LONE_SURROGATE_RE = /[\uD800-\uDFFF]/u;
3360
+ function hasLoneSurrogate(id) {
3361
+ return LONE_SURROGATE_RE.test(id);
3362
+ }
3363
+ var ILL_FORMED_SEED = 1;
3364
+ function utf16leBytes(s) {
3365
+ const bytes = new Uint8Array(s.length * 2);
3366
+ for (let i = 0; i < s.length; i++) {
3367
+ const c = s.charCodeAt(i);
3368
+ bytes[i * 2] = c & 255;
3369
+ bytes[i * 2 + 1] = c >>> 8;
3370
+ }
3371
+ return bytes;
3372
+ }
3356
3373
  function shortenDocumentId(id, threshold = 16) {
3374
+ if (hasLoneSurrogate(id)) return toBase62(murmur3_32_bytes(utf16leBytes(id), ILL_FORMED_SEED));
3357
3375
  if (id.length <= threshold) return id;
3358
3376
  return toBase62(murmur3_32(id));
3359
3377
  }
@@ -4123,6 +4141,7 @@ function createRemendPreprocessor(options) {
4123
4141
  extractContributions,
4124
4142
  extractDefBodiesFromHast,
4125
4143
  getEnginePluginInternals,
4144
+ hasLoneSurrogate,
4126
4145
  highlight,
4127
4146
  isFootnoteSection,
4128
4147
  lastRegionStart,