rusteerio 1.1.0 → 1.1.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.
Files changed (45) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/README.md +37 -37
  4. package/THIRD_PARTY_NOTICES.md +1 -1
  5. package/dist/{chunk-FPRP6UIX.mjs → chunk-HN63P3U7.mjs} +625 -401
  6. package/dist/chunk-HN63P3U7.mjs.map +1 -0
  7. package/dist/index.cjs +624 -400
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.mts +3 -2
  10. package/dist/index.d.ts +3 -2
  11. package/dist/index.mjs +1 -1
  12. package/dist/slim.cjs +555 -407
  13. package/dist/slim.cjs.map +1 -1
  14. package/dist/slim.d.mts +2 -2
  15. package/dist/slim.d.ts +2 -2
  16. package/dist/slim.mjs +1 -1
  17. package/dist/{types-Cs9UP1Vv.d.mts → types-CJsc0PH-.d.mts} +12 -0
  18. package/dist/{types-Cs9UP1Vv.d.ts → types-CJsc0PH-.d.ts} +12 -0
  19. package/dist/utils.d.mts +1 -1
  20. package/dist/utils.d.ts +1 -1
  21. package/native/src/arena.rs +163 -7
  22. package/native/src/attributes.rs +18 -1
  23. package/native/src/codec.rs +79 -0
  24. package/native/src/lib.rs +117 -30
  25. package/native/src/snapshot.rs +144 -89
  26. package/native/src/tokenizer.rs +34 -20
  27. package/package.json +1 -1
  28. package/rusteerio.darwin-arm64.node +0 -0
  29. package/rusteerio.darwin-x64.node +0 -0
  30. package/rusteerio.linux-arm64-gnu.node +0 -0
  31. package/rusteerio.linux-arm64-musl.node +0 -0
  32. package/rusteerio.linux-x64-gnu.node +0 -0
  33. package/rusteerio.linux-x64-musl.node +0 -0
  34. package/rusteerio.win32-arm64-msvc.node +0 -0
  35. package/rusteerio.win32-x64-msvc.node +0 -0
  36. package/src/extract.ts +117 -24
  37. package/src/index.ts +27 -6
  38. package/src/match.ts +4 -4
  39. package/src/native.ts +4 -0
  40. package/src/nodes.ts +70 -10
  41. package/src/router.ts +80 -4
  42. package/src/selection.ts +18 -17
  43. package/src/snapshot.ts +37 -0
  44. package/src/transport.ts +1 -1
  45. package/dist/chunk-FPRP6UIX.mjs.map +0 -1
package/dist/slim.cjs CHANGED
@@ -244,7 +244,7 @@ function wrapNativeBinding(binding) {
244
244
  if (raw.NativeHookStream) wrapped.NativeHookStream = wrapConstructor(raw.NativeHookStream, ([isDocument2, options, bridge]) => [isDocument2, encodeOptions(options), wrapBridge(bridge)], wrapStream);
245
245
  if (raw.parseWithTreeAdapter) wrapped.parseWithTreeAdapter = (source, isDocument2, options, bridge) => invoke(() => raw.parseWithTreeAdapter(encodeString(source), isDocument2, encodeOptions(options), wrapBridge(bridge)));
246
246
  if (raw.parseAsync) wrapped.parseAsync = (source, isDocument2, options) => invoke(() => raw.parseAsync(encodeString(source), isDocument2, encodeOptions(options)));
247
- if (raw.parseManyAsync) wrapped.parseManyAsync = (sources, isDocument2, threads, options) => invoke(() => raw.parseManyAsync(sources.map(encodeString), isDocument2, threads, encodeOptions(options)));
247
+ if (raw.parseManyAsync) wrapped.parseManyAsync = (sources, isDocument2, threads, options, plans) => invoke(() => raw.parseManyAsync(sources.map(encodeString), isDocument2, threads, encodeOptions(options), plans === void 0 ? void 0 : encodeJson(plans)));
248
248
  if (raw.extractPlanManyAsync) wrapped.extractPlanManyAsync = async (sources, plan, threads, options) => decodeJson(await invoke(() => raw.extractPlanManyAsync(sources.map(encodeString), encodeJson(plan), threads, encodeOptions(options))));
249
249
  if (raw.extractManyAsync) wrapped.extractManyAsync = async (sources, queries, threads, isDocument2, options) => decodeJson(await invoke(() => raw.extractManyAsync(sources.map(encodeString), queries.map((query) => ({ ...query, key: encodeString(query.key), selector: encodeString(query.selector), ...query.attr === void 0 ? {} : { attr: encodeString(query.attr) } })), threads, isDocument2, encodeOptions(options))));
250
250
  return wrapped;
@@ -288,320 +288,6 @@ function requireNative() {
288
288
  throw new Error('Rusteerio could not load its Rust native binding. Reinstall rusteerio for this platform or run "npm run build:native" from a source checkout.', { cause: bindingError });
289
289
  }
290
290
 
291
- // src/hooks.ts
292
- var tokenCallbacks = ["onattribdata", "onattribentity", "onattribend", "onattribname", "oncdata", "onclosetag", "oncomment", "ondeclaration", "onend", "onopentagend", "onopentagname", "onprocessinginstruction", "onselfclosingtag", "ontext", "ontextentity"];
293
- function createTokenizerSession(options) {
294
- const events = [];
295
- const callbacks = Object.fromEntries(tokenCallbacks.map((name) => [name, (...args) => {
296
- if (name === "ontextentity" || name === "onattribentity") {
297
- const codePoint = +args[0];
298
- String.fromCodePoint(codePoint);
299
- args[0] = codePoint;
300
- }
301
- events.push([name, ...args]);
302
- }]));
303
- const Constructor = options.Tokenizer;
304
- const tokenizer = new Constructor(options, callbacks);
305
- return {
306
- write(chunk) {
307
- if (tokenizer.running) tokenizer.write(chunk);
308
- },
309
- end() {
310
- tokenizer.end();
311
- const result = cleanParserOptions(options);
312
- result.__tokenEvents = events;
313
- return result;
314
- }
315
- };
316
- }
317
- function cleanParserOptions(options) {
318
- const result = { ...options };
319
- delete result.Tokenizer;
320
- delete result.treeAdapter;
321
- delete result.onParseError;
322
- delete result.__tokenEvents;
323
- if (!options._useHtmlParser2 && options.onParseError) {
324
- result.__collectParseErrors = true;
325
- result.sourceCodeLocationInfo = true;
326
- }
327
- return result;
328
- }
329
- function parserOptions(source, options) {
330
- if (options._useHtmlParser2 && options.Tokenizer) {
331
- const session = createTokenizerSession(options);
332
- session.write(source);
333
- return session.end();
334
- }
335
- return cleanParserOptions(options);
336
- }
337
- function deliverParseErrors(document, options) {
338
- if (options._useHtmlParser2 || !options.onParseError) return;
339
- const diagnostics = document.parserDiagnostics?.();
340
- const errors = typeof diagnostics === "string" ? JSON.parse(diagnostics) : diagnostics ?? [];
341
- for (const error of errors) options.onParseError(error);
342
- }
343
- function hasParserHooks(options) {
344
- return !!(options._useHtmlParser2 ? options.Tokenizer : options.treeAdapter || options.onParseError);
345
- }
346
- function treeAdapterBridge(adapter, options) {
347
- const deliveredErrors = /* @__PURE__ */ new Set();
348
- const nodes = [];
349
- const ids = /* @__PURE__ */ new WeakMap();
350
- const identify = (node) => {
351
- if (node == null) return null;
352
- if (typeof node !== "object") throw new TypeError("treeAdapter methods must return DOM node objects");
353
- let id = ids.get(node);
354
- if (id === void 0) {
355
- id = nodes.length;
356
- ids.set(node, id);
357
- nodes.push(node);
358
- }
359
- return id;
360
- };
361
- const nodeArgs = {
362
- appendChild: [0, 1],
363
- insertBefore: [0, 1, 2],
364
- setTemplateContent: [0, 1],
365
- getTemplateContent: [0],
366
- setDocumentType: [0],
367
- setDocumentMode: [0],
368
- getDocumentMode: [0],
369
- detachNode: [0],
370
- insertText: [0],
371
- insertTextBefore: [0, 2],
372
- adoptAttributes: [0],
373
- getFirstChild: [0],
374
- getChildNodes: [0],
375
- getParentNode: [0],
376
- getAttrList: [0],
377
- getTagName: [0],
378
- getNamespaceURI: [0],
379
- getTextNodeContent: [0],
380
- getCommentNodeContent: [0],
381
- getDocumentTypeNodeName: [0],
382
- getDocumentTypeNodePublicId: [0],
383
- getDocumentTypeNodeSystemId: [0],
384
- isTextNode: [0],
385
- isCommentNode: [0],
386
- isElementNode: [0],
387
- isDocumentTypeNode: [0],
388
- setNodeSourceCodeLocation: [0],
389
- getNodeSourceCodeLocation: [0],
390
- updateNodeSourceCodeLocation: [0],
391
- onItemPop: [0, 1],
392
- onItemPush: [0]
393
- };
394
- const nodeReturns = /* @__PURE__ */ new Set(["createDocument", "createDocumentFragment", "createElement", "createCommentNode", "createTextNode", "getTemplateContent", "getFirstChild", "getParentNode"]);
395
- const invoke = (method, args) => {
396
- const fn = adapter[method];
397
- if (typeof fn !== "function") {
398
- if (method === "onItemPop" || method === "onItemPush") return void 0;
399
- throw new TypeError(`treeAdapter.${method} must be a function`);
400
- }
401
- return Reflect.apply(fn, adapter, args);
402
- };
403
- const snapshot = () => {
404
- const records = [];
405
- for (let id = 0; id < nodes.length; id++) {
406
- const node = nodes[id];
407
- const element = invoke("isElementNode", [node]);
408
- const text = invoke("isTextNode", [node]);
409
- const comment = invoke("isCommentNode", [node]);
410
- const doctype = invoke("isDocumentTypeNode", [node]);
411
- const name = element ? invoke("getTagName", [node]) : doctype ? "!doctype" : node.name;
412
- const type = element ? name === "script" || name === "style" ? name : "tag" : text ? "text" : comment ? "comment" : doctype ? "directive" : node.type ?? "root";
413
- const children = element || type === "root" || type === "cdata" ? invoke("getChildNodes", [node]) ?? [] : [];
414
- const record = { id, type, children: Array.from(children, identify), parent: identify(invoke("getParentNode", [node])) };
415
- if (name !== void 0) record.name = name;
416
- if (element) {
417
- record.namespace = invoke("getNamespaceURI", [node]);
418
- record.attribs = /* @__PURE__ */ Object.create(null);
419
- record.attribsNamespace = /* @__PURE__ */ Object.create(null);
420
- record.attribsPrefix = /* @__PURE__ */ Object.create(null);
421
- for (const attr2 of invoke("getAttrList", [node])) {
422
- Object.defineProperty(record.attribs, attr2.name, { value: attr2.value, enumerable: true, configurable: true, writable: true });
423
- record.attribsNamespace[attr2.name] = attr2.namespace;
424
- record.attribsPrefix[attr2.name] = attr2.prefix;
425
- }
426
- } else if (text || comment) record.data = invoke(text ? "getTextNodeContent" : "getCommentNodeContent", [node]);
427
- else if (doctype) {
428
- record.data = node.data;
429
- for (const [field, method] of [["x-name", "getDocumentTypeNodeName"], ["x-publicId", "getDocumentTypeNodePublicId"], ["x-systemId", "getDocumentTypeNodeSystemId"]]) record[field] = invoke(method, [node]);
430
- }
431
- if (type === "root" && node["x-mode"] !== void 0) record["x-mode"] = invoke("getDocumentMode", [node]);
432
- for (const field of ["startIndex", "endIndex", "sourceCodeLocation"]) if (node[field] !== void 0) record[field] = node[field];
433
- records.push(record);
434
- }
435
- return records;
436
- };
437
- const bridge = (request) => {
438
- const { method, args = [] } = JSON.parse(request);
439
- if (method === "__parseError") {
440
- deliveredErrors.add(JSON.stringify(args[0]));
441
- options.onParseError?.(args[0]);
442
- return "null";
443
- }
444
- if (method === "__snapshot") return JSON.stringify(snapshot());
445
- if (method === "__locations") {
446
- for (const entry of args[0]) invoke("setNodeSourceCodeLocation", [nodes[entry.id], hydrateLocation(entry.location)]);
447
- return "null";
448
- }
449
- const values = args.slice();
450
- if (method === "setNodeSourceCodeLocation" || method === "updateNodeSourceCodeLocation") hydrateLocation(values[1]);
451
- for (const index of nodeArgs[method] ?? []) values[index] = values[index] == null ? null : nodes[values[index]];
452
- const result = invoke(method, values);
453
- return JSON.stringify(method === "getChildNodes" ? Array.from(result ?? [], identify) : nodeReturns.has(method) ? identify(result) : result ?? null);
454
- };
455
- return { bridge, nodes, deliveredErrors };
456
- }
457
- function bindAdapterDocument(document, options, adapter) {
458
- if (options.onParseError) {
459
- const errors = document.parserDiagnostics?.();
460
- for (const error of errors ? JSON.parse(errors) : []) if (!adapter.deliveredErrors.has(JSON.stringify(error))) options.onParseError(error);
461
- }
462
- const routed = routeDocument(document, options);
463
- const globalId = routed.localNodeId.bind(routed);
464
- for (let id = 0; id < adapter.nodes.length; id++) getNode(routed, globalId(id), adapter.nodes[id]);
465
- return routed;
466
- }
467
- function parseNativeDocument(source, options, isDocument2) {
468
- const binding = requireNative();
469
- const nativeOptions = parserOptions(source, options);
470
- if (!options._useHtmlParser2 && options.treeAdapter && canReplay(options)) {
471
- const document2 = routeDocument(new binding.NativeDocument(source, isDocument2, JSON.stringify(nativeOptions)), options);
472
- const nodes = replayIntoAdapter(document2, options.treeAdapter, isDocument2);
473
- if (nodes) {
474
- attachParsedNodes(document2.state, nodes);
475
- return document2;
476
- }
477
- }
478
- if (!options._useHtmlParser2 && options.treeAdapter) {
479
- const parse3 = binding.parseWithTreeAdapter;
480
- if (!parse3) throw new Error("This native addon does not support treeAdapter hooks; rebuild the Rust addon.");
481
- const adapter = treeAdapterBridge(options.treeAdapter, options);
482
- return bindAdapterDocument(parse3(source, isDocument2, JSON.stringify(nativeOptions), adapter.bridge), options, adapter);
483
- }
484
- const document = new binding.NativeDocument(source, isDocument2, JSON.stringify(nativeOptions));
485
- deliverParseErrors(document, options);
486
- return routeDocument(document, options);
487
- }
488
- function canReplay(options) {
489
- const adapter = options.treeAdapter;
490
- return !options.onParseError && !options.sourceCodeLocationInfo && adapter.onItemPush === void 0 && adapter.onItemPop === void 0;
491
- }
492
- function replayIntoAdapter(document, adapter, isDocument2) {
493
- const state = document.state;
494
- const root = document.rootId() - state.base;
495
- const size = state.baseline;
496
- const nodes = new Array(size);
497
- for (let local = 0; local < Math.min(size, 65); local++) recordAt(state, local);
498
- for (let local = 0; local < size; local++) {
499
- const snapshot = plainSnapshot(state, local);
500
- const record = snapshot ? void 0 : recordAt(state, local);
501
- if (!snapshot && !record) continue;
502
- const type = snapshot ? snapshot.typeOf(local) : record.type;
503
- if (local === root) {
504
- nodes[local] = isDocument2 ? adapter.createDocument() : adapter.createDocumentFragment();
505
- const mode = record?.["x-mode"];
506
- if (isDocument2 && mode !== void 0) adapter.setDocumentMode(nodes[local], mode);
507
- continue;
508
- }
509
- const parentLocal = snapshot ? snapshot.linkOf(local, 1) : record.parent;
510
- const parent = parentLocal == null ? void 0 : nodes[parentLocal];
511
- if (parent === void 0) return void 0;
512
- switch (type) {
513
- case "tag":
514
- case "script":
515
- case "style": {
516
- const element = snapshot ? adapter.createElement(snapshot.nameOf(local), snapshot.namespaceOf(local), snapshot.attributeObjects(local)) : adapter.createElement(record.name, record.namespace, attributeList(record));
517
- adapter.appendChild(parent, element);
518
- nodes[local] = element;
519
- break;
520
- }
521
- case "root": {
522
- const content = adapter.createDocumentFragment();
523
- adapter.setTemplateContent(parent, content);
524
- nodes[local] = content;
525
- break;
526
- }
527
- case "text": {
528
- adapter.insertText(parent, (snapshot ? snapshot.dataOf(local) : record.data) ?? "");
529
- const children = adapter.getChildNodes(parent);
530
- nodes[local] = children[children.length - 1];
531
- break;
532
- }
533
- case "comment": {
534
- const comment = adapter.createCommentNode((snapshot ? snapshot.dataOf(local) : record.data) ?? "");
535
- adapter.appendChild(parent, comment);
536
- nodes[local] = comment;
537
- break;
538
- }
539
- case "directive": {
540
- if (!record || record.name !== "!doctype" || !isDocument2) return void 0;
541
- adapter.setDocumentType(parent, record["x-name"] ?? "", record["x-publicId"] ?? "", record["x-systemId"] ?? "");
542
- nodes[local] = Array.from(adapter.getChildNodes(parent) ?? []).find((child) => adapter.isDocumentTypeNode(child));
543
- break;
544
- }
545
- default:
546
- return void 0;
547
- }
548
- }
549
- const changes = differencesFromParse(adapter, nodes, state, root);
550
- if (!changes) return void 0;
551
- for (const change of changes) operation(document, "setNode", { id: state.base + change.local, field: change.field, value: change.value });
552
- return nodes;
553
- }
554
- function attributeList(record) {
555
- const attributes = record.attribs ?? {};
556
- return Object.keys(attributes).map((name) => {
557
- const namespace = record.attribsNamespace?.[name];
558
- const prefix = record.attribsPrefix?.[name];
559
- return { name, value: attributes[name], ...namespace === void 0 ? {} : { namespace }, ...prefix === void 0 ? {} : { prefix } };
560
- });
561
- }
562
- function differencesFromParse(adapter, nodes, state, root) {
563
- const changes = [];
564
- for (let local = 0; local < nodes.length; local++) {
565
- const node = nodes[local];
566
- const snapshot = plainSnapshot(state, local);
567
- const record = snapshot ? void 0 : recordAt(state, local);
568
- if (!snapshot && !record) continue;
569
- if (!node) return void 0;
570
- const type = snapshot ? snapshot.typeOf(local) : record.type;
571
- if (local !== root && adapter.getParentNode(node) !== nodes[snapshot ? snapshot.linkOf(local, 1) : record.parent]) return void 0;
572
- if (type !== "text" && type !== "comment" && type !== "directive") {
573
- const children = adapter.getChildNodes(node) ?? [];
574
- const count = snapshot ? snapshot.childCount(local) : record.children.length;
575
- if (children.length !== count) return void 0;
576
- for (let index = 0; index < count; index++) if (children[index] !== nodes[snapshot ? snapshot.childAt(local, index) : record.children[index]]) return void 0;
577
- }
578
- if (type === "tag" || type === "script" || type === "style") {
579
- const name = snapshot ? snapshot.nameOf(local) : record.name;
580
- const namespace = snapshot ? snapshot.namespaceOf(local) : record.namespace;
581
- if (!adapter.isElementNode(node) || adapter.getTagName(node) !== name || adapter.getNamespaceURI(node) !== namespace) return void 0;
582
- const attributes = adapter.getAttrList(node) ?? [];
583
- const expected = snapshot ? snapshot.attributeObjects(local) : attributeList(record);
584
- const isSame = attributes.length === expected.length && attributes.every((attribute, index) => attribute.name === expected[index].name && attribute.value === expected[index].value);
585
- if (!isSame) {
586
- const values = /* @__PURE__ */ Object.create(null);
587
- const namespaces = /* @__PURE__ */ Object.create(null);
588
- const prefixes = /* @__PURE__ */ Object.create(null);
589
- for (const attribute of attributes) {
590
- values[attribute.name] = attribute.value;
591
- if (attribute.namespace !== void 0) namespaces[attribute.name] = attribute.namespace;
592
- if (attribute.prefix !== void 0) prefixes[attribute.name] = attribute.prefix;
593
- }
594
- changes.push({ local, field: "attribs", value: values }, { local, field: "attribsNamespace", value: namespaces }, { local, field: "attribsPrefix", value: prefixes });
595
- }
596
- } else if (type === "text" || type === "comment") {
597
- if (type === "text" ? !adapter.isTextNode(node) : !adapter.isCommentNode(node)) return void 0;
598
- const data2 = type === "text" ? adapter.getTextNodeContent(node) : adapter.getCommentNodeContent(node);
599
- if (data2 !== (snapshot ? snapshot.dataOf(local) : record.data)) changes.push({ local, field: "data", value: data2 });
600
- }
601
- }
602
- return changes;
603
- }
604
-
605
291
  // src/snapshot.ts
606
292
  var HEADER = 5;
607
293
  var STRIDE = 15;
@@ -702,8 +388,12 @@ var NodeSnapshot = class {
702
388
  text;
703
389
  isEncoded;
704
390
  names = [];
391
+ /** Per interned type: 1 for element types, 2 for root, 3 for text, 0 otherwise. */
392
+ typeKinds = [];
705
393
  /** Node object constructors by interned type, resolved once per snapshot. */
706
394
  viewConstructors = void 0;
395
+ /** Text nodes too large for the arrays, read individually while this snapshot is current. */
396
+ largeTexts = /* @__PURE__ */ new Map();
707
397
  constructor(raw) {
708
398
  const { data: data2 } = raw;
709
399
  const childrenStart = HEADER + data2[0];
@@ -792,6 +482,33 @@ var NodeSnapshot = class {
792
482
  childStart(local) {
793
483
  return this.nodes[local * STRIDE + 4];
794
484
  }
485
+ /** Whether a node is an element: a tag, script or style. */
486
+ isElementAt(local) {
487
+ return this.typeKind(local) === 1;
488
+ }
489
+ isRootAt(local) {
490
+ return this.typeKind(local) === 2;
491
+ }
492
+ isTextAt(local) {
493
+ return this.typeKind(local) === 3;
494
+ }
495
+ /** Whether a node's record adds fields stored as JSON, such as source locations. */
496
+ hasExtraFields(local) {
497
+ return this.nodes[local * STRIDE + 14] !== 0;
498
+ }
499
+ /** Whether the slot holds a node, rather than one a collection released. */
500
+ isPresent(local) {
501
+ return local < this.size && (this.nodes[local * STRIDE + 10] & RELEASED) === 0;
502
+ }
503
+ typeKind(local) {
504
+ const reference = this.nodes[local * STRIDE];
505
+ let kind = this.typeKinds[reference];
506
+ if (kind === void 0) {
507
+ const type = this.name(reference);
508
+ kind = this.typeKinds[reference] = type === "tag" || type === "script" || type === "style" ? 1 : type === "root" ? 2 : type === "text" ? 3 : 0;
509
+ }
510
+ return kind;
511
+ }
795
512
  /** The interned reference of a node's type, comparable across nodes of this snapshot. */
796
513
  typeReference(local) {
797
514
  return this.nodes[local * STRIDE];
@@ -800,54 +517,368 @@ var NodeSnapshot = class {
800
517
  childCount(local) {
801
518
  return this.nodes[local * STRIDE + 5];
802
519
  }
803
- childAt(local, index) {
804
- return this.children[this.nodes[local * STRIDE + 4] + index];
520
+ childAt(local, index) {
521
+ return this.children[this.nodes[local * STRIDE + 4] + index];
522
+ }
523
+ childrenOf(local) {
524
+ const base = local * STRIDE;
525
+ const count = this.nodes[base + 5];
526
+ if (count === 0) return noChildren;
527
+ const first = this.nodes[base + 4];
528
+ return Array.from(this.children.subarray(first, first + count));
529
+ }
530
+ attributesOf(local) {
531
+ const base = local * STRIDE;
532
+ const count = this.nodes[base + 9];
533
+ if (count === 0) return noAttributes;
534
+ const first = this.nodes[base + 8];
535
+ const attributes = this.attributes;
536
+ const result = {};
537
+ for (let index = 0; index < count; index++) {
538
+ const pair = (first + index) * 2;
539
+ const name = this.name(attributes[pair]);
540
+ const value = this.string(attributes[pair + 1]);
541
+ if (name === "__proto__") Object.defineProperty(result, name, { value, writable: true, enumerable: true, configurable: true });
542
+ else result[name] = value;
543
+ }
544
+ return result;
545
+ }
546
+ /** One attribute's value, without decoding the rest. */
547
+ attributeValue(local, name) {
548
+ const base = local * STRIDE;
549
+ const count = this.nodes[base + 9];
550
+ const first = this.nodes[base + 8];
551
+ const attributes = this.attributes;
552
+ for (let index = count - 1; index >= 0; index--) {
553
+ const pair = (first + index) * 2;
554
+ if (this.name(attributes[pair]) === name) return this.string(attributes[pair + 1]);
555
+ }
556
+ return void 0;
557
+ }
558
+ string(reference) {
559
+ const strings = this.strings;
560
+ const value = this.text.slice(strings[reference * 2], strings[reference * 2 + 1]);
561
+ return this.isEncoded ? decodeString(value) : value;
562
+ }
563
+ /** Names repeat across nodes, so each decodes once. */
564
+ name(reference) {
565
+ return this.names[reference] ??= this.string(reference);
566
+ }
567
+ };
568
+
569
+ // src/hooks.ts
570
+ var tokenCallbacks = ["onattribdata", "onattribentity", "onattribend", "onattribname", "oncdata", "onclosetag", "oncomment", "ondeclaration", "onend", "onopentagend", "onopentagname", "onprocessinginstruction", "onselfclosingtag", "ontext", "ontextentity"];
571
+ function createTokenizerSession(options) {
572
+ const events = [];
573
+ const callbacks = Object.fromEntries(tokenCallbacks.map((name) => [name, (...args) => {
574
+ if (name === "ontextentity" || name === "onattribentity") {
575
+ const codePoint = +args[0];
576
+ String.fromCodePoint(codePoint);
577
+ args[0] = codePoint;
578
+ }
579
+ events.push([name, ...args]);
580
+ }]));
581
+ const Constructor = options.Tokenizer;
582
+ const tokenizer = new Constructor(options, callbacks);
583
+ return {
584
+ write(chunk) {
585
+ if (tokenizer.running) tokenizer.write(chunk);
586
+ },
587
+ end() {
588
+ tokenizer.end();
589
+ const result = cleanParserOptions(options);
590
+ result.__tokenEvents = events;
591
+ return result;
592
+ }
593
+ };
594
+ }
595
+ function cleanParserOptions(options) {
596
+ const result = { ...options };
597
+ delete result.Tokenizer;
598
+ delete result.treeAdapter;
599
+ delete result.onParseError;
600
+ delete result.__tokenEvents;
601
+ if (!options._useHtmlParser2 && options.onParseError) {
602
+ result.__collectParseErrors = true;
603
+ result.sourceCodeLocationInfo = true;
604
+ }
605
+ return result;
606
+ }
607
+ function parserOptions(source, options) {
608
+ if (options._useHtmlParser2 && options.Tokenizer) {
609
+ const session = createTokenizerSession(options);
610
+ session.write(source);
611
+ return session.end();
612
+ }
613
+ return cleanParserOptions(options);
614
+ }
615
+ function deliverParseErrors(document, options) {
616
+ if (options._useHtmlParser2 || !options.onParseError) return;
617
+ const diagnostics = document.parserDiagnostics?.();
618
+ const errors = typeof diagnostics === "string" ? JSON.parse(diagnostics) : diagnostics ?? [];
619
+ for (const error of errors) options.onParseError(error);
620
+ }
621
+ function hasParserHooks(options) {
622
+ return !!(options._useHtmlParser2 ? options.Tokenizer : options.treeAdapter || options.onParseError);
623
+ }
624
+ function treeAdapterBridge(adapter, options) {
625
+ const deliveredErrors = /* @__PURE__ */ new Set();
626
+ const nodes = [];
627
+ const ids = /* @__PURE__ */ new WeakMap();
628
+ const identify = (node) => {
629
+ if (node == null) return null;
630
+ if (typeof node !== "object") throw new TypeError("treeAdapter methods must return DOM node objects");
631
+ let id = ids.get(node);
632
+ if (id === void 0) {
633
+ id = nodes.length;
634
+ ids.set(node, id);
635
+ nodes.push(node);
636
+ }
637
+ return id;
638
+ };
639
+ const nodeArgs = {
640
+ appendChild: [0, 1],
641
+ insertBefore: [0, 1, 2],
642
+ setTemplateContent: [0, 1],
643
+ getTemplateContent: [0],
644
+ setDocumentType: [0],
645
+ setDocumentMode: [0],
646
+ getDocumentMode: [0],
647
+ detachNode: [0],
648
+ insertText: [0],
649
+ insertTextBefore: [0, 2],
650
+ adoptAttributes: [0],
651
+ getFirstChild: [0],
652
+ getChildNodes: [0],
653
+ getParentNode: [0],
654
+ getAttrList: [0],
655
+ getTagName: [0],
656
+ getNamespaceURI: [0],
657
+ getTextNodeContent: [0],
658
+ getCommentNodeContent: [0],
659
+ getDocumentTypeNodeName: [0],
660
+ getDocumentTypeNodePublicId: [0],
661
+ getDocumentTypeNodeSystemId: [0],
662
+ isTextNode: [0],
663
+ isCommentNode: [0],
664
+ isElementNode: [0],
665
+ isDocumentTypeNode: [0],
666
+ setNodeSourceCodeLocation: [0],
667
+ getNodeSourceCodeLocation: [0],
668
+ updateNodeSourceCodeLocation: [0],
669
+ onItemPop: [0, 1],
670
+ onItemPush: [0]
671
+ };
672
+ const nodeReturns = /* @__PURE__ */ new Set(["createDocument", "createDocumentFragment", "createElement", "createCommentNode", "createTextNode", "getTemplateContent", "getFirstChild", "getParentNode"]);
673
+ const invoke = (method, args) => {
674
+ const fn = adapter[method];
675
+ if (typeof fn !== "function") {
676
+ if (method === "onItemPop" || method === "onItemPush") return void 0;
677
+ throw new TypeError(`treeAdapter.${method} must be a function`);
678
+ }
679
+ return Reflect.apply(fn, adapter, args);
680
+ };
681
+ const snapshot = () => {
682
+ const records = [];
683
+ for (let id = 0; id < nodes.length; id++) {
684
+ const node = nodes[id];
685
+ const element = invoke("isElementNode", [node]);
686
+ const text = invoke("isTextNode", [node]);
687
+ const comment = invoke("isCommentNode", [node]);
688
+ const doctype = invoke("isDocumentTypeNode", [node]);
689
+ const name = element ? invoke("getTagName", [node]) : doctype ? "!doctype" : node.name;
690
+ const type = element ? name === "script" || name === "style" ? name : "tag" : text ? "text" : comment ? "comment" : doctype ? "directive" : node.type ?? "root";
691
+ const children = element || type === "root" || type === "cdata" ? invoke("getChildNodes", [node]) ?? [] : [];
692
+ const record = { id, type, children: Array.from(children, identify), parent: identify(invoke("getParentNode", [node])) };
693
+ if (name !== void 0) record.name = name;
694
+ if (element) {
695
+ record.namespace = invoke("getNamespaceURI", [node]);
696
+ record.attribs = /* @__PURE__ */ Object.create(null);
697
+ record.attribsNamespace = /* @__PURE__ */ Object.create(null);
698
+ record.attribsPrefix = /* @__PURE__ */ Object.create(null);
699
+ for (const attr2 of invoke("getAttrList", [node])) {
700
+ Object.defineProperty(record.attribs, attr2.name, { value: attr2.value, enumerable: true, configurable: true, writable: true });
701
+ record.attribsNamespace[attr2.name] = attr2.namespace;
702
+ record.attribsPrefix[attr2.name] = attr2.prefix;
703
+ }
704
+ } else if (text || comment) record.data = invoke(text ? "getTextNodeContent" : "getCommentNodeContent", [node]);
705
+ else if (doctype) {
706
+ record.data = node.data;
707
+ for (const [field, method] of [["x-name", "getDocumentTypeNodeName"], ["x-publicId", "getDocumentTypeNodePublicId"], ["x-systemId", "getDocumentTypeNodeSystemId"]]) record[field] = invoke(method, [node]);
708
+ }
709
+ if (type === "root" && node["x-mode"] !== void 0) record["x-mode"] = invoke("getDocumentMode", [node]);
710
+ for (const field of ["startIndex", "endIndex", "sourceCodeLocation"]) if (node[field] !== void 0) record[field] = node[field];
711
+ records.push(record);
712
+ }
713
+ return records;
714
+ };
715
+ const bridge = (request) => {
716
+ const { method, args = [] } = JSON.parse(request);
717
+ if (method === "__parseError") {
718
+ deliveredErrors.add(JSON.stringify(args[0]));
719
+ options.onParseError?.(args[0]);
720
+ return "null";
721
+ }
722
+ if (method === "__snapshot") return JSON.stringify(snapshot());
723
+ if (method === "__locations") {
724
+ for (const entry of args[0]) invoke("setNodeSourceCodeLocation", [nodes[entry.id], hydrateLocation(entry.location)]);
725
+ return "null";
726
+ }
727
+ const values = args.slice();
728
+ if (method === "setNodeSourceCodeLocation" || method === "updateNodeSourceCodeLocation") hydrateLocation(values[1]);
729
+ for (const index of nodeArgs[method] ?? []) values[index] = values[index] == null ? null : nodes[values[index]];
730
+ const result = invoke(method, values);
731
+ return JSON.stringify(method === "getChildNodes" ? Array.from(result ?? [], identify) : nodeReturns.has(method) ? identify(result) : result ?? null);
732
+ };
733
+ return { bridge, nodes, deliveredErrors };
734
+ }
735
+ function bindAdapterDocument(document, options, adapter) {
736
+ if (options.onParseError) {
737
+ const errors = document.parserDiagnostics?.();
738
+ for (const error of errors ? JSON.parse(errors) : []) if (!adapter.deliveredErrors.has(JSON.stringify(error))) options.onParseError(error);
739
+ }
740
+ const routed = routeDocument(document, options);
741
+ const globalId = routed.localNodeId.bind(routed);
742
+ for (let id = 0; id < adapter.nodes.length; id++) getNode(routed, globalId(id), adapter.nodes[id]);
743
+ return routed;
744
+ }
745
+ function parseNativeDocument(source, options, isDocument2) {
746
+ const binding = requireNative();
747
+ const nativeOptions = parserOptions(source, options);
748
+ if (!options._useHtmlParser2 && options.treeAdapter && canReplay(options)) {
749
+ const document2 = routeDocument(new binding.NativeDocument(source, isDocument2, JSON.stringify(nativeOptions)), options);
750
+ const nodes = replayIntoAdapter(document2, options.treeAdapter, isDocument2);
751
+ if (nodes) {
752
+ attachParsedNodes(document2.state, nodes);
753
+ return document2;
754
+ }
805
755
  }
806
- childrenOf(local) {
807
- const base = local * STRIDE;
808
- const count = this.nodes[base + 5];
809
- if (count === 0) return noChildren;
810
- const first = this.nodes[base + 4];
811
- return Array.from(this.children.subarray(first, first + count));
756
+ if (!options._useHtmlParser2 && options.treeAdapter) {
757
+ const parse3 = binding.parseWithTreeAdapter;
758
+ if (!parse3) throw new Error("This native addon does not support treeAdapter hooks; rebuild the Rust addon.");
759
+ const adapter = treeAdapterBridge(options.treeAdapter, options);
760
+ return bindAdapterDocument(parse3(source, isDocument2, JSON.stringify(nativeOptions), adapter.bridge), options, adapter);
812
761
  }
813
- attributesOf(local) {
814
- const base = local * STRIDE;
815
- const count = this.nodes[base + 9];
816
- if (count === 0) return noAttributes;
817
- const first = this.nodes[base + 8];
818
- const attributes = this.attributes;
819
- const result = {};
820
- for (let index = 0; index < count; index++) {
821
- const pair = (first + index) * 2;
822
- const name = this.name(attributes[pair]);
823
- const value = this.string(attributes[pair + 1]);
824
- if (name === "__proto__") Object.defineProperty(result, name, { value, writable: true, enumerable: true, configurable: true });
825
- else result[name] = value;
762
+ const document = new binding.NativeDocument(source, isDocument2, JSON.stringify(nativeOptions));
763
+ deliverParseErrors(document, options);
764
+ return routeDocument(document, options);
765
+ }
766
+ function canReplay(options) {
767
+ const adapter = options.treeAdapter;
768
+ return !options.onParseError && !options.sourceCodeLocationInfo && adapter.onItemPush === void 0 && adapter.onItemPop === void 0;
769
+ }
770
+ function replayIntoAdapter(document, adapter, isDocument2) {
771
+ const state = document.state;
772
+ const root = document.rootId() - state.base;
773
+ const size = state.baseline;
774
+ const nodes = new Array(size);
775
+ for (let local = 0; local < Math.min(size, 65); local++) recordAt(state, local);
776
+ for (let local = 0; local < size; local++) {
777
+ const snapshot = plainSnapshot(state, local);
778
+ const record = snapshot ? void 0 : recordAt(state, local);
779
+ if (!snapshot && !record) continue;
780
+ const type = snapshot ? snapshot.typeOf(local) : record.type;
781
+ if (local === root) {
782
+ nodes[local] = isDocument2 ? adapter.createDocument() : adapter.createDocumentFragment();
783
+ const mode = record?.["x-mode"];
784
+ if (isDocument2 && mode !== void 0) adapter.setDocumentMode(nodes[local], mode);
785
+ continue;
826
786
  }
827
- return result;
828
- }
829
- /** One attribute's value, without decoding the rest. */
830
- attributeValue(local, name) {
831
- const base = local * STRIDE;
832
- const count = this.nodes[base + 9];
833
- const first = this.nodes[base + 8];
834
- const attributes = this.attributes;
835
- for (let index = count - 1; index >= 0; index--) {
836
- const pair = (first + index) * 2;
837
- if (this.name(attributes[pair]) === name) return this.string(attributes[pair + 1]);
787
+ const parentLocal = snapshot ? snapshot.linkOf(local, 1) : record.parent;
788
+ const parent = parentLocal == null ? void 0 : nodes[parentLocal];
789
+ if (parent === void 0) return void 0;
790
+ switch (type) {
791
+ case "tag":
792
+ case "script":
793
+ case "style": {
794
+ const element = snapshot ? adapter.createElement(snapshot.nameOf(local), snapshot.namespaceOf(local), snapshot.attributeObjects(local)) : adapter.createElement(record.name, record.namespace, attributeList(record));
795
+ adapter.appendChild(parent, element);
796
+ nodes[local] = element;
797
+ break;
798
+ }
799
+ case "root": {
800
+ const content = adapter.createDocumentFragment();
801
+ adapter.setTemplateContent(parent, content);
802
+ nodes[local] = content;
803
+ break;
804
+ }
805
+ case "text": {
806
+ adapter.insertText(parent, (snapshot ? snapshot.dataOf(local) : record.data) ?? "");
807
+ const children = adapter.getChildNodes(parent);
808
+ nodes[local] = children[children.length - 1];
809
+ break;
810
+ }
811
+ case "comment": {
812
+ const comment = adapter.createCommentNode((snapshot ? snapshot.dataOf(local) : record.data) ?? "");
813
+ adapter.appendChild(parent, comment);
814
+ nodes[local] = comment;
815
+ break;
816
+ }
817
+ case "directive": {
818
+ if (!record || record.name !== "!doctype" || !isDocument2) return void 0;
819
+ adapter.setDocumentType(parent, record["x-name"] ?? "", record["x-publicId"] ?? "", record["x-systemId"] ?? "");
820
+ nodes[local] = Array.from(adapter.getChildNodes(parent) ?? []).find((child) => adapter.isDocumentTypeNode(child));
821
+ break;
822
+ }
823
+ default:
824
+ return void 0;
838
825
  }
839
- return void 0;
840
826
  }
841
- string(reference) {
842
- const strings = this.strings;
843
- const value = this.text.slice(strings[reference * 2], strings[reference * 2 + 1]);
844
- return this.isEncoded ? decodeString(value) : value;
845
- }
846
- /** Names repeat across nodes, so each decodes once. */
847
- name(reference) {
848
- return this.names[reference] ??= this.string(reference);
827
+ const changes = differencesFromParse(adapter, nodes, state, root);
828
+ if (!changes) return void 0;
829
+ for (const change of changes) operation(document, "setNode", { id: state.base + change.local, field: change.field, value: change.value });
830
+ return nodes;
831
+ }
832
+ function attributeList(record) {
833
+ const attributes = record.attribs ?? {};
834
+ return Object.keys(attributes).map((name) => {
835
+ const namespace = record.attribsNamespace?.[name];
836
+ const prefix = record.attribsPrefix?.[name];
837
+ return { name, value: attributes[name], ...namespace === void 0 ? {} : { namespace }, ...prefix === void 0 ? {} : { prefix } };
838
+ });
839
+ }
840
+ function differencesFromParse(adapter, nodes, state, root) {
841
+ const changes = [];
842
+ for (let local = 0; local < nodes.length; local++) {
843
+ const node = nodes[local];
844
+ const snapshot = plainSnapshot(state, local);
845
+ const record = snapshot ? void 0 : recordAt(state, local);
846
+ if (!snapshot && !record) continue;
847
+ if (!node) return void 0;
848
+ const type = snapshot ? snapshot.typeOf(local) : record.type;
849
+ if (local !== root && adapter.getParentNode(node) !== nodes[snapshot ? snapshot.linkOf(local, 1) : record.parent]) return void 0;
850
+ if (type !== "text" && type !== "comment" && type !== "directive") {
851
+ const children = adapter.getChildNodes(node) ?? [];
852
+ const count = snapshot ? snapshot.childCount(local) : record.children.length;
853
+ if (children.length !== count) return void 0;
854
+ for (let index = 0; index < count; index++) if (children[index] !== nodes[snapshot ? snapshot.childAt(local, index) : record.children[index]]) return void 0;
855
+ }
856
+ if (type === "tag" || type === "script" || type === "style") {
857
+ const name = snapshot ? snapshot.nameOf(local) : record.name;
858
+ const namespace = snapshot ? snapshot.namespaceOf(local) : record.namespace;
859
+ if (!adapter.isElementNode(node) || adapter.getTagName(node) !== name || adapter.getNamespaceURI(node) !== namespace) return void 0;
860
+ const attributes = adapter.getAttrList(node) ?? [];
861
+ const expected = snapshot ? snapshot.attributeObjects(local) : attributeList(record);
862
+ const isSame = attributes.length === expected.length && attributes.every((attribute, index) => attribute.name === expected[index].name && attribute.value === expected[index].value);
863
+ if (!isSame) {
864
+ const values = /* @__PURE__ */ Object.create(null);
865
+ const namespaces = /* @__PURE__ */ Object.create(null);
866
+ const prefixes = /* @__PURE__ */ Object.create(null);
867
+ for (const attribute of attributes) {
868
+ values[attribute.name] = attribute.value;
869
+ if (attribute.namespace !== void 0) namespaces[attribute.name] = attribute.namespace;
870
+ if (attribute.prefix !== void 0) prefixes[attribute.name] = attribute.prefix;
871
+ }
872
+ changes.push({ local, field: "attribs", value: values }, { local, field: "attribsNamespace", value: namespaces }, { local, field: "attribsPrefix", value: prefixes });
873
+ }
874
+ } else if (type === "text" || type === "comment") {
875
+ if (type === "text" ? !adapter.isTextNode(node) : !adapter.isCommentNode(node)) return void 0;
876
+ const data2 = type === "text" ? adapter.getTextNodeContent(node) : adapter.getCommentNodeContent(node);
877
+ if (data2 !== (snapshot ? snapshot.dataOf(local) : record.data)) changes.push({ local, field: "data", value: data2 });
878
+ }
849
879
  }
850
- };
880
+ return changes;
881
+ }
851
882
 
852
883
  // src/router.ts
853
884
  var STRIDE2 = 2 ** 32;
@@ -946,7 +977,8 @@ function stateFor(raw, options = {}) {
946
977
  isExposed: false,
947
978
  hasChildLists: false,
948
979
  collectionRetries: 0,
949
- isCollectionEvicted: false
980
+ isCollectionEvicted: false,
981
+ hasPreparedSnapshot: false
950
982
  };
951
983
  holder[stateKey] = state;
952
984
  return state;
@@ -1071,7 +1103,7 @@ function readRecord(state, local) {
1071
1103
  const size = Math.max(state.snapshotSize, state.baseline);
1072
1104
  const firstSnapshotRead = state.generation === 0 ? size / 100 : 1 + size / 100;
1073
1105
  if (reads >= Math.min(minimumReadsBeforeSnapshot, firstSnapshotRead)) {
1074
- const snapshot = new NodeSnapshot(state.raw.snapshot());
1106
+ const snapshot = new NodeSnapshot(takePreparedSnapshot(state, true) ?? state.raw.snapshot());
1075
1107
  state.snapshotSize = snapshot.size;
1076
1108
  state.bulk = snapshot;
1077
1109
  const record = snapshot.record(local);
@@ -1080,10 +1112,40 @@ function readRecord(state, local) {
1080
1112
  }
1081
1113
  return rawOperation(state.raw, "node", { id: local });
1082
1114
  }
1115
+ function takePreparedSnapshot(state, isWanted) {
1116
+ if (!state.hasPreparedSnapshot) return void 0;
1117
+ state.hasPreparedSnapshot = false;
1118
+ return state.raw.takePreparedSnapshot?.(isWanted) ?? void 0;
1119
+ }
1083
1120
  function plainSnapshot(state, local) {
1084
1121
  const bulk = state.bulk;
1085
1122
  return bulk instanceof NodeSnapshot && !(state.stale.size && state.stale.has(local)) && bulk.isPlain(local) ? bulk : void 0;
1086
1123
  }
1124
+ function structureSnapshot(state, local) {
1125
+ const bulk = state.bulk;
1126
+ return bulk instanceof NodeSnapshot && !(state.stale.size && state.stale.has(local)) && bulk.isPresent(local) ? bulk : void 0;
1127
+ }
1128
+ var snapshotTextBudget = 256;
1129
+ function textFromSnapshot(state, local) {
1130
+ let text = "";
1131
+ const stack = [local];
1132
+ for (let visited = 0; stack.length; visited++) {
1133
+ const node = stack.pop();
1134
+ const snapshot = structureSnapshot(state, node);
1135
+ if (!snapshot || visited === snapshotTextBudget) return void 0;
1136
+ if (snapshot.isTextAt(node)) {
1137
+ text += snapshot.dataOf(node) ?? largeText(state, snapshot, node);
1138
+ continue;
1139
+ }
1140
+ for (let index = snapshot.childCount(node) - 1; index >= 0; index--) stack.push(snapshot.childAt(node, index));
1141
+ }
1142
+ return text;
1143
+ }
1144
+ function largeText(state, snapshot, local) {
1145
+ let text = snapshot.largeTexts.get(local);
1146
+ if (text === void 0) snapshot.largeTexts.set(local, text = state.raw.text([local]));
1147
+ return text;
1148
+ }
1087
1149
  function attributeAt(state, local, name) {
1088
1150
  const snapshot = plainSnapshot(state, local);
1089
1151
  if (snapshot) return snapshot.attributeValue(local, name);
@@ -1092,15 +1154,18 @@ function attributeAt(state, local, name) {
1092
1154
  return typeof value === "string" ? value : void 0;
1093
1155
  }
1094
1156
  function invalidate(state) {
1157
+ takePreparedSnapshot(state, false);
1095
1158
  state.generation++;
1096
1159
  state.records = [];
1097
1160
  state.bulk = 0;
1098
1161
  state.stale.clear();
1099
1162
  }
1100
1163
  function invalidateNodes(state, locals) {
1164
+ takePreparedSnapshot(state, false);
1101
1165
  const isSnapshot = state.bulk instanceof NodeSnapshot;
1166
+ const { records } = state;
1102
1167
  for (const local of locals) {
1103
- state.records[local] = void 0;
1168
+ if (records[local] !== void 0) records[local] = void 0;
1104
1169
  if (isSnapshot) state.stale.add(local);
1105
1170
  }
1106
1171
  }
@@ -1335,6 +1400,11 @@ var RoutedDocument = class {
1335
1400
  return this.globals(group.state, found);
1336
1401
  }
1337
1402
  text(ids) {
1403
+ if (ids.length === 1) {
1404
+ const state = this.owner(ids[0]);
1405
+ const text = textFromSnapshot(state, ids[0] - state.base);
1406
+ if (text !== void 0) return text;
1407
+ }
1338
1408
  return this.groups(ids).map((group) => group.state.raw.text(group.locals)).join("");
1339
1409
  }
1340
1410
  innerHtml(id) {
@@ -1419,9 +1489,12 @@ var RoutedDocument = class {
1419
1489
  return this.globals(state, inserted);
1420
1490
  }
1421
1491
  const result2 = [];
1492
+ const isBesideTarget = args.position === "before" || args.position === "after";
1422
1493
  for (let index = 0; index < targets.length; index++) {
1423
1494
  const target = this.owner(targets[index]);
1424
1495
  const targetLocal = targets[index] - target.base;
1496
+ const record = recordAt(target, targetLocal);
1497
+ if (!record || !parentTypes.has(record.type) || isBesideTarget && record.parent == null) continue;
1425
1498
  const local = this.moveInto(target, nodes, index + 1 < targets.length);
1426
1499
  const inserted = this.call(target, command, { ...args, targetIds: [targetLocal], nodeIds: local });
1427
1500
  result2.push(...this.globals(target, inserted));
@@ -1644,6 +1717,8 @@ function traverseRecords(document, ids, kind) {
1644
1717
  for (const id of ids) {
1645
1718
  const state = stateIn(document, id);
1646
1719
  const { base } = state;
1720
+ const snapshot = state.bulk instanceof NodeSnapshot ? state.bulk : void 0;
1721
+ if (snapshot?.isPresent(id - base) && traverseSnapshot(snapshot, base, id - base, kind, result)) continue;
1647
1722
  const node = recordAt(state, id - base);
1648
1723
  if (!node) continue;
1649
1724
  const isElement2 = (local) => isElementType(recordAt(state, local)?.type);
@@ -1684,6 +1759,60 @@ function traverseRecords(document, ids, kind) {
1684
1759
  }
1685
1760
  return result;
1686
1761
  }
1762
+ function traverseSnapshot(snapshot, base, local, kind, result) {
1763
+ const pushChildren2 = (parent, isElementsOnly, except) => {
1764
+ const count = snapshot.childCount(parent);
1765
+ for (let index = 0; index < count; index++) {
1766
+ const child = snapshot.childAt(parent, index);
1767
+ if (child !== except && (!isElementsOnly || snapshot.isElementAt(child))) result.push(base + child);
1768
+ }
1769
+ };
1770
+ switch (kind) {
1771
+ case "children":
1772
+ pushChildren2(local, true, -1);
1773
+ return true;
1774
+ case "contents":
1775
+ pushChildren2(local, false, -1);
1776
+ return true;
1777
+ case "siblings": {
1778
+ const parent = snapshot.linkOf(local, 1);
1779
+ if (parent !== null && snapshot.isPresent(parent)) pushChildren2(parent, true, local);
1780
+ return true;
1781
+ }
1782
+ case "parent": {
1783
+ const parent = snapshot.linkOf(local, 1);
1784
+ if (parent !== null && snapshot.isPresent(parent) && snapshot.isElementAt(parent)) result.push(base + parent);
1785
+ return true;
1786
+ }
1787
+ case "parents":
1788
+ case "ancestors": {
1789
+ let parent = snapshot.linkOf(local, 1);
1790
+ for (let steps = 0; parent !== null && steps < snapshot.size && snapshot.isPresent(parent) && !snapshot.isRootAt(parent); steps++) {
1791
+ if (snapshot.isElementAt(parent)) result.push(base + parent);
1792
+ parent = snapshot.linkOf(parent, 1);
1793
+ }
1794
+ return true;
1795
+ }
1796
+ case "next":
1797
+ case "prev":
1798
+ case "nextAll":
1799
+ case "prevAll": {
1800
+ const offset = kind === "next" || kind === "nextAll" ? 3 : 2;
1801
+ const isSingle = kind === "next" || kind === "prev";
1802
+ let sibling = snapshot.linkOf(local, offset);
1803
+ for (let steps = 0; sibling !== null && steps < snapshot.size && snapshot.isPresent(sibling); steps++) {
1804
+ if (snapshot.isElementAt(sibling)) {
1805
+ result.push(base + sibling);
1806
+ if (isSingle) break;
1807
+ }
1808
+ sibling = snapshot.linkOf(sibling, offset);
1809
+ }
1810
+ return true;
1811
+ }
1812
+ default:
1813
+ return false;
1814
+ }
1815
+ }
1687
1816
  function viewAt(state, local) {
1688
1817
  const existing = state.views[local] ?? cachedView(state, local);
1689
1818
  if (existing) return existing;
@@ -1692,9 +1821,9 @@ function viewAt(state, local) {
1692
1821
  state.adapterNodes[local] = void 0;
1693
1822
  return adoptObject(state, local, parsed);
1694
1823
  }
1695
- const snapshot = plainSnapshot(state, local);
1824
+ const snapshot = structureSnapshot(state, local);
1696
1825
  let constructor;
1697
- if (snapshot) constructor = viewConstructor(snapshot.typeOf(local), false);
1826
+ if (snapshot && !snapshot.hasExtraFields(local)) constructor = viewConstructor(snapshot.typeOf(local), false);
1698
1827
  else {
1699
1828
  const record = recordAt(state, local);
1700
1829
  if (!record) return void 0;
@@ -1893,14 +2022,17 @@ function fillChildren(view) {
1893
2022
  if (!target) return;
1894
2023
  const state = view[VIEW_STATE];
1895
2024
  const local = view[VIEW_LOCAL];
1896
- const snapshot = plainSnapshot(state, local);
2025
+ const snapshot = structureSnapshot(state, local);
1897
2026
  if (snapshot && state.stale.size === 0) {
1898
2027
  fillFromSnapshot(state, snapshot, local, target);
1899
2028
  return;
1900
2029
  }
1901
2030
  const locals = viewRecord(view).children ?? [];
1902
- target.length = locals.length;
1903
2031
  for (let index = 0; index < locals.length; index++) target[index] = viewAt(state, locals[index]);
2032
+ truncate(target, locals.length);
2033
+ }
2034
+ function truncate(target, length) {
2035
+ if (target.length !== length) target.length = length;
1904
2036
  }
1905
2037
  function fillFromSnapshot(state, snapshot, local, target) {
1906
2038
  const count = snapshot.childCount(local);
@@ -1909,17 +2041,17 @@ function fillFromSnapshot(state, snapshot, local, target) {
1909
2041
  const views = state.views;
1910
2042
  const isStrong = !state.adapterNodes;
1911
2043
  const constructors = snapshotConstructors(snapshot);
1912
- target.length = count;
1913
2044
  for (let index = 0; index < count; index++) {
1914
2045
  const child = childIds2[first + index];
1915
2046
  let node = views[child] ?? (state.weakViews.size ? cachedView(state, child) : void 0);
1916
2047
  if (node === void 0) {
1917
- const constructor = isStrong && child < state.baseline && snapshot.isPlain(child) ? constructors(snapshot.typeReference(child)) : void 0;
2048
+ const constructor = isStrong && child < state.baseline && snapshot.isPresent(child) && !snapshot.hasExtraFields(child) ? constructors(snapshot.typeReference(child)) : void 0;
1918
2049
  if (constructor) views[child] = node = new constructor(state, child);
1919
2050
  else node = viewAt(state, child);
1920
2051
  }
1921
2052
  target[index] = node;
1922
2053
  }
2054
+ truncate(target, count);
1923
2055
  }
1924
2056
  function snapshotConstructors(snapshot) {
1925
2057
  let lookup = snapshot.viewConstructors;
@@ -1942,7 +2074,7 @@ var snapshotField = (field) => ({
1942
2074
  get() {
1943
2075
  const state = this[VIEW_STATE];
1944
2076
  const local = this[VIEW_LOCAL];
1945
- const snapshot = plainSnapshot(state, local);
2077
+ const snapshot = field === "data" ? plainSnapshot(state, local) : structureSnapshot(state, local);
1946
2078
  if (snapshot) return field === "type" ? snapshot.typeOf(local) : field === "name" ? snapshot.nameOf(local) : snapshot.dataOf(local);
1947
2079
  return viewRecord(this)[field];
1948
2080
  },
@@ -1955,7 +2087,7 @@ var pointerField = (field) => ({
1955
2087
  get() {
1956
2088
  const state = this[VIEW_STATE];
1957
2089
  const local = this[VIEW_LOCAL];
1958
- const snapshot = plainSnapshot(state, local);
2090
+ const snapshot = structureSnapshot(state, local);
1959
2091
  return linkedView(this, snapshot ? snapshot.linkOf(local, linkOffsets[field]) : viewRecord(this)[field]);
1960
2092
  },
1961
2093
  set(value) {
@@ -2808,11 +2940,11 @@ function selectSimpleWithin(document, contexts, selector, options) {
2808
2940
  }
2809
2941
  var isElementType2 = (type) => type === "tag" || type === "script" || type === "style";
2810
2942
  function typeAt(state, local) {
2811
- const snapshot = plainSnapshot(state, local);
2943
+ const snapshot = structureSnapshot(state, local);
2812
2944
  return snapshot ? snapshot.typeOf(local) : recordAt(state, local)?.type;
2813
2945
  }
2814
2946
  function pushChildren(state, local, stack) {
2815
- const snapshot = plainSnapshot(state, local);
2947
+ const snapshot = structureSnapshot(state, local);
2816
2948
  if (snapshot) {
2817
2949
  for (let index = snapshot.childCount(local) - 1; index >= 0; index--) stack.push(snapshot.childAt(local, index));
2818
2950
  return;
@@ -2828,7 +2960,7 @@ function sameMatchOptions(documentOptions, isXml, isLowerCaseTags, isLowerCaseAt
2828
2960
  return isDocumentXml === isXml && (documentOptions.lowerCaseTags ?? !isDocumentXml) === isLowerCaseTags && (documentOptions.lowerCaseAttributeNames ?? !isDocumentXml) === isLowerCaseAttributes;
2829
2961
  }
2830
2962
  function matchesCompound(state, local, compound, isLowerCaseTags, isLowerCaseAttributes) {
2831
- const snapshot = plainSnapshot(state, local);
2963
+ const snapshot = structureSnapshot(state, local);
2832
2964
  const record = snapshot ? void 0 : recordAt(state, local);
2833
2965
  if (!snapshot && !record) return false;
2834
2966
  if (!isElementType2(snapshot ? snapshot.typeOf(local) : record.type)) return false;
@@ -3408,26 +3540,25 @@ var Cheerio = class {
3408
3540
  }
3409
3541
  /** @internal */
3410
3542
  _insert(position, content) {
3543
+ if (typeof content[0] !== "function") {
3544
+ const ids = this.ids;
3545
+ if (ids.length) this.op("insert", { targetIds: ids, nodeIds: this._domIds(content), position });
3546
+ return this;
3547
+ }
3411
3548
  const targets = this.ids.filter((id) => ["root", "tag", "script", "style", "cdata"].includes(nodeRecord(this[DOCUMENT], id).type));
3412
3549
  if (!targets.length)
3413
3550
  return this;
3414
- if (typeof content[0] === "function")
3415
- return this.each((index, node) => {
3416
- if (!targets.includes(this.ids[index]))
3417
- return;
3418
- if ((position === "before" || position === "after") && nodeRecord(this[DOCUMENT], this.ids[index]).parent == null)
3419
- return;
3420
- const value = content[0].call(node, index, this[DOCUMENT].innerHtml(this.ids[index]));
3421
- let nodes2 = this._domIds(value);
3422
- if (index < this.length - 1)
3423
- nodes2 = this.op("clone", { ids: nodes2, deep: true });
3424
- this.op("insert", { targetIds: [this.ids[index]], nodeIds: nodes2, position });
3425
- });
3426
- let nodes = this._domIds(content);
3427
- if (targets[targets.length - 1] !== this.ids[this.ids.length - 1])
3428
- nodes = this.op("clone", { ids: nodes, deep: true });
3429
- this.op("insert", { targetIds: targets, nodeIds: nodes, position });
3430
- return this;
3551
+ return this.each((index, node) => {
3552
+ if (!targets.includes(this.ids[index]))
3553
+ return;
3554
+ if ((position === "before" || position === "after") && nodeRecord(this[DOCUMENT], this.ids[index]).parent == null)
3555
+ return;
3556
+ const value = content[0].call(node, index, this[DOCUMENT].innerHtml(this.ids[index]));
3557
+ let nodes = this._domIds(value);
3558
+ if (index < this.length - 1)
3559
+ nodes = this.op("clone", { ids: nodes, deep: true });
3560
+ this.op("insert", { targetIds: [this.ids[index]], nodeIds: nodes, position });
3561
+ });
3431
3562
  }
3432
3563
  append(...content) {
3433
3564
  return this._insert("append", content);
@@ -3607,28 +3738,30 @@ function plainKeys(map) {
3607
3738
  return !import_node_util2.types.isProxy(map) && (Object.getPrototypeOf(map) === Object.prototype || Object.getPrototypeOf(map) === null) ? Object.keys(map) : void 0;
3608
3739
  }
3609
3740
  function nativePrefix(scope, map, keys, key) {
3610
- if (!keys || scope._make !== nativeExtractionMethods.make || scope._findBySelector !== nativeExtractionMethods.find || scope.prop !== nativeExtractionMethods.prop || scope.extract !== nativeExtractionMethods.extract) return void 0;
3741
+ if (!keys || !hasNativeExtraction(scope)) return void 0;
3742
+ const prefix = prefixPlan(map, keys, key, scope.options);
3743
+ if (!prefix) return void 0;
3744
+ return { keys: prefix.names, values: runPlan(scope, prefix.plan) };
3745
+ }
3746
+ function prefixPlan(map, keys, key, options) {
3611
3747
  const prefix = /* @__PURE__ */ Object.create(null);
3612
- let count = 0;
3613
3748
  let plan;
3614
- const index = keys.indexOf(key);
3615
- for (let position = index; position < keys.length; position++) {
3749
+ for (let position = keys.indexOf(key); position < keys.length; position++) {
3616
3750
  const name = keys[position];
3617
3751
  const slot = Object.getOwnPropertyDescriptor(map, name);
3618
3752
  if (!slot || !slot.enumerable || !("value" in slot)) break;
3619
3753
  Object.defineProperty(prefix, name, { value: slot.value, enumerable: true, configurable: true });
3620
- const compiled2 = compileExtractPlan(prefix, scope.options);
3754
+ const compiled2 = compileExtractPlan(prefix, options);
3621
3755
  if (!compiled2) {
3622
3756
  delete prefix[name];
3623
3757
  break;
3624
3758
  }
3625
3759
  plan = compiled2;
3626
- count++;
3627
3760
  }
3628
- if (!plan || !count) return void 0;
3629
- const values = scope.op("extractPlan", { ids: scope.ids, plan });
3630
- hydrateExtractResult(values, plan.entries);
3631
- return { keys: Object.keys(prefix), values };
3761
+ return plan ? { names: Object.keys(prefix), plan } : void 0;
3762
+ }
3763
+ function hasNativeExtraction(scope) {
3764
+ return scope._make === nativeExtractionMethods.make && scope._findBySelector === nativeExtractionMethods.find && scope.prop === nativeExtractionMethods.prop && scope.extract === nativeExtractionMethods.extract;
3632
3765
  }
3633
3766
  function extract(map) {
3634
3767
  const result = {};
@@ -3673,8 +3806,17 @@ function extract(map) {
3673
3806
  return result;
3674
3807
  }
3675
3808
  function nativeRows(scope, key, all, selector, map) {
3809
+ if (!hasNativeExtraction(scope)) return void 0;
3810
+ const planned = rowsPlan(key, all, selector, map, scope.options);
3811
+ if (!planned) return void 0;
3812
+ const writes = writeCount();
3813
+ const extracted = runPlan(scope, planned.plan)[key];
3814
+ const rows = all ? extracted : extracted === void 0 ? [] : [extracted];
3815
+ return Array.isArray(rows) ? { keys: planned.nativeKeys, rows, writes } : void 0;
3816
+ }
3817
+ function rowsPlan(key, all, selector, map, options) {
3676
3818
  const keys = plainKeys(map);
3677
- if (!keys || scope._make !== nativeExtractionMethods.make || scope._findBySelector !== nativeExtractionMethods.find || scope.prop !== nativeExtractionMethods.prop || scope.extract !== nativeExtractionMethods.extract) return void 0;
3819
+ if (!keys) return void 0;
3678
3820
  const subset = /* @__PURE__ */ Object.create(null);
3679
3821
  const nativeKeys = /* @__PURE__ */ new Set();
3680
3822
  for (const name of keys) {
@@ -3682,21 +3824,25 @@ function nativeRows(scope, key, all, selector, map) {
3682
3824
  if (!slot || !slot.enumerable || !("value" in slot)) continue;
3683
3825
  const single = /* @__PURE__ */ Object.create(null);
3684
3826
  Object.defineProperty(single, name, { value: slot.value, enumerable: true, configurable: true });
3685
- if (!compileExtractPlan(single, scope.options)) continue;
3827
+ if (!compileExtractPlan(single, options)) continue;
3686
3828
  Object.defineProperty(subset, name, { value: slot.value, enumerable: true, configurable: true });
3687
3829
  nativeKeys.add(name);
3688
3830
  }
3689
3831
  if (!nativeKeys.size || nativeKeys.size === keys.length) return void 0;
3690
3832
  const outer = /* @__PURE__ */ Object.create(null);
3691
3833
  Object.defineProperty(outer, key, { value: all ? [{ selector, value: subset }] : { selector, value: subset }, enumerable: true, configurable: true });
3692
- const plan = compileExtractPlan(outer, scope.options);
3693
- if (!plan) return void 0;
3694
- const writes = writeCount();
3695
- const values = scope.op("extractPlan", { ids: scope.ids, plan });
3834
+ const plan = compileExtractPlan(outer, options);
3835
+ return plan ? { nativeKeys, plan } : void 0;
3836
+ }
3837
+ var preparedPlans = /* @__PURE__ */ new WeakMap();
3838
+ function runPlan(scope, plan) {
3839
+ const document = scope[DOCUMENT];
3840
+ const prepared = preparedPlans.get(document);
3841
+ const isRoot = prepared !== void 0 && prepared.writes === writeCount() && scope.ids.length === 1 && scope.ids[0] === document.rootId();
3842
+ const result = isRoot ? prepared.results.get(JSON.stringify(plan)) : void 0;
3843
+ const values = result !== void 0 ? JSON.parse(result) : scope.op("extractPlan", { ids: scope.ids, plan });
3696
3844
  hydrateExtractResult(values, plan.entries);
3697
- const extracted = values[key];
3698
- const rows = all ? extracted : extracted === void 0 ? [] : [extracted];
3699
- return Array.isArray(rows) ? { keys: nativeKeys, rows, writes } : void 0;
3845
+ return values;
3700
3846
  }
3701
3847
  function completeRow(scope, map, rows, index) {
3702
3848
  const row = {};
@@ -3774,7 +3920,9 @@ var dom = {
3774
3920
  isCDATA,
3775
3921
  hasChildren
3776
3922
  };
3923
+ var defaultOptions = { _useHtmlParser2: false };
3777
3924
  function flattenOptions(options, base) {
3925
+ if (!options) return base ?? defaultOptions;
3778
3926
  const result = { _useHtmlParser2: false, ...base, ...options };
3779
3927
  if (options?.xml) {
3780
3928
  result._useHtmlParser2 = true;