css-select-base-adapter
css-select-base-adapter is a tiny CommonJS factory for people adapting a custom tree structure to the css-select selector engine. You supply six operations that identify elements, read names and attributes, walk parents and children, and collect text. It returns those operations plus default implementations of sibling lookup, attribute checks, subtree searches, and duplicate or descendant removal. It does not parse HTML, implement a DOM, or select anything by itself.
Keep it if an existing adapter already depends on its exact behavior. For new code, the eight-year maintenance gap, absent types, and stale contract documentation make a small in-house typed adapter or an existing current adapter easier to trust.
Use it if
- You have a non-DOM tree model and need to supply the repetitive traversal helpers expected by a css-select adapter
- Your nodes have stable object identity, parent links, child arrays, tag names, attributes, and recursive text access
- You are maintaining older CommonJS code that already uses the package and want to preserve its adapter behavior
- You want a dependency-free implementation small enough to audit completely before adopting it
- You only need to query ordinary HTML: css-select already defaults to domutils, while Cheerio packages parsing and selection behind a higher-level API
- You expect current TypeScript support: version 0.1.1 ships no declarations, and its README documents the older adapter surface rather than the generic Adapter type published by css-select 7
- You need active compatibility work: the last package release and repository push were both in October 2018, before multiple css-select major versions
- Your tree uses logical equality instead of object identity: removeSubsets relies on Array#indexOf and never consults css-select's optional equals hook
- Your roots or document nodes need defensive traversal: getSiblings can return null for a parentless node, and existsOne refuses to descend through any node for which isTag returns false
Setup reality
Installation is just npm install css-select-base-adapter, with no dependencies, peer dependencies, native build, credentials, or configuration files. That simplicity hides the actual integration work. The package does not include css-select, a parser, DOM nodes, or TypeScript declarations, so you install and configure those separately. Your implementation must provide exactly six functions before the factory will return: isTag, getAttributeValue, getChildren, getName, getParent, and getText. Miss one and construction throws an Error listing the absent functions. getText must recursively produce text for a whole node, not merely read an element's direct text field. getParent must use null or another falsy value at the root, and child and parent links must remain consistent for sibling and ancestor operations. The included defaults assume object identity, ordinary arrays, synchronous traversal, and an acyclic tree. removeSubsets mutates the array you pass. getSiblings includes the node itself and returns the parent's complete child list, matching css-select's contract but not jQuery's meaning of siblings. The package is CommonJS and uses Object.assign; it has no engines field and no transpiled fallback for very old JavaScript runtimes. With current css-select 7, import interop and the adapter type are your responsibility, and the upstream README is not a compatibility promise because this package's tests do not install or exercise css-select itself.
Patterns
Complete a minimal custom adaptercreate-adapter
const makeAdapter = require('css-select-base-adapter');
const adapter = makeAdapter({
isTag: (node) => node.type === 'element',
getAttributeValue: (node, name) => node.attributes[name],
getChildren: (node) => node.children || [],
getName: (node) => node.name,
getParent: (node) => node.parent || null,
getText: (node) => node.type === 'text'
? node.value
: (node.children || []).map(getText).join(''),
});
function getText(node) {
return adapter.getText(node);
}All six named functions are mandatory. The factory validates them immediately and reports every missing function in one error.
Pass the adapter to current css-selectuse-with-css-select
import makeAdapter from 'css-select-base-adapter';
import { selectAll } from 'css-select';
const adapter = makeAdapter(treeImplementation);
const matches = selectAll('article.featured > h2', roots, {
adapter,
xmlMode: false,
});css-select 7 is ESM while this package is CommonJS. Node can provide the default-import interop shown here, but TypeScript needs your own adapter typing because this package has no declarations.
Replace a generated helperoverride-default
const adapter = makeAdapter({
...treeImplementation,
hasAttrib(node, name) {
return Object.prototype.hasOwnProperty.call(node.attributes, name);
},
});Provided methods win over defaults. Override hasAttrib when a present attribute can legitimately map to undefined in your data model.
Find the first matching nodefind-first
const firstMain = adapter.findOne(
(node) => adapter.isTag(node) && adapter.getAttributeValue(node, 'role') === 'main',
roots
);findOne runs the predicate on every array entry, including non-tag nodes, so include an isTag guard if element-only fields are not safe on text nodes.
Collect every matching elementfind-all
const headings = adapter.findAll(
(node) => /^h[1-6]$/.test(adapter.getName(node)),
roots
);findAll only tests nodes accepted by isTag, then recursively walks their children in depth-first document order.
Check whether any matching element existscheck-descendant
const hasLink = adapter.existsOne(
(node) => adapter.getName(node) === 'a',
elementRoots
);Every starting node must satisfy isTag for traversal to continue. Passing a document wrapper that is not a tag can incorrectly produce false without visiting its children.
Read an element's sibling groupread-siblings
const siblingGroup = adapter.getSiblings(node) || [node];
const position = siblingGroup.indexOf(node);The returned list includes node itself. The default returns a falsy value when node has no parent, despite current css-select types describing an array return.
Test attribute presencecheck-attribute
if (adapter.hasAttrib(node, 'disabled')) {
console.log('attribute exists even when its value is empty');
}The default considers any value other than undefined present. Empty strings and null therefore count as present.
Remove duplicate and nested selectionsremove-subsets
const candidates = [section, paragraph, section];
const topLevelOnly = adapter.removeSubsets(candidates);removeSubsets mutates candidates in place, removes duplicates, and drops a node when one of its ancestors is also present. Copy first if the original array matters.
Remove subsets without changing caller datapreserve-input-array
const topLevelOnly = adapter.removeSubsets([...candidates]);
console.log(candidates.length); // unchangedA shallow array copy is enough to protect the list, but the nodes themselves are still shared and compared with strict object identity.
Override traversal for a document wrappersupport-root-node
const adapter = makeAdapter({
...treeImplementation,
existsOne(test, nodes) {
return nodes.some((node) =>
(this.isTag(node) && test(node)) ||
this.existsOne(test, this.getChildren(node) || [])
);
},
});The stock existsOne does not descend through non-tag document nodes. This override handles wrappers, but avoid this-dependent methods if your callers detach functions from the adapter.
Fail clearly when a primitive is missingvalidate-implementation
try {
makeAdapter({
isTag() {},
getChildren() {},
});
} catch (error) {
console.error(error.message);
// Expected functions (...) to be implemented
}Validation checks only that each required property is a function. It cannot verify return types, parent-child consistency, or cycle safety.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| css-select | npm | Use its built-in domutils adapter directly when your nodes come from htmlparser2 rather than maintaining a custom adapter |
| css-select-browser-adapter | npm | Use a ready-made adapter when the backing nodes are browser DOM elements |
| domutils | npm | Use the adapter css-select expects by default when you already parse with domhandler or htmlparser2 |
| cheerio | npm | Use a higher-level HTML parsing and querying API when you do not actually have a custom tree model |