Batch checking with checkAll
2 minutes read
checkAll(items) checks a batch of components synchronously on top of check and returns both positional outcomes and batch counts:
import { checkAll, isCheckAllError } from "@velella/ebb/check-all";
By design, checkAll never reads SBOM or lockfile files and never touches the network; only the first evaluation loads Ebb's active local rule index (later calls reuse the warm index). Map your SBOM components or lockfile entries into { purl, version } objects before calling it.
CycloneDX SBOM example
The runnable example below builds a CycloneDX 1.5 component list modeled on tests/check-all/fixtures/sbom.json, adding two invalid entries ("pkg:" and a component with no version) and keeping a duplicated dependency (request@2.88.2).
import { checkAll, isCheckAllError } from "@velella/ebb/check-all";
const sbom = {
bomFormat: "CycloneDX",
specVersion: "1.5",
components: [
{ name: "express", version: "4.21.0", purl: "pkg:npm/express@4.21.0" },
{ name: "request", version: "2.88.2", purl: "pkg:npm/request@2.88.2" },
{ name: "node-sass", version: "9.0.0", purl: "pkg:npm/node-sass@9.0.0" },
{ name: "dotnet", version: "9.0.0", purl: "pkg:generic/dotnet@9.0.0" },
{ name: "nodejs", version: "22.12.0", purl: "pkg:generic/nodejs@22.12.0" },
{ name: "request", version: "2.88.2", purl: "pkg:npm/request@2.88.2" },
{ name: "broken-purl", version: "1.0.0", purl: "pkg:" },
{ name: "express", purl: "pkg:npm/express" },
{ name: "@types/node", version: "22.10.0", purl: "pkg:npm/%40types/node@22.10.0" },
],
};
const { results, summary } = checkAll(
sbom.components.map((component) => ({
purl: component.purl ?? component.name,
version: component.version ?? "",
})),
);
const findings = results.filter((entry) => !isCheckAllError(entry) && entry.status !== "ok");
const errors = results.filter(isCheckAllError);
console.log(JSON.stringify({ summary, findingCount: findings.length, errors }, null, 2));
Output:
{
"summary": {
"total": 9,
"byStatus": {
"ok": 2,
"deprecated": 2,
"unsupported": 1,
"end-of-life": 1,
"removed": 0,
"scheduled": 1
},
"byEvent": {
"deprecated": 5,
"unsupported": 1,
"end-of-life": 1,
"removed": 0,
"scheduled": 1
},
"errors": 2
},
"findingCount": 5,
"errors": [
{
"status": "error",
"index": 6,
"code": "invalid-purl",
"message": "PURL is missing a type: pkg:"
},
{
"status": "error",
"index": 7,
"code": "invalid-version",
"message": "Invalid version \"\"."
}
]
}
Notice what happens in that run:
results.length === 9andresults[i]stays aligned withsbom.components[i]— nothing is sorted or dropped.request@2.88.2appears at indexes1and5.(purl, version)is judged once, both positions receive the cachedCheckResult, and both occurrences contribute tosummary.- Index
6("pkg:") and index7(missingversion) produce positionalCheckAllErrorobjects without aborting index8(@types/node@22.10.0). byStatuscounts each valid item once by its primary verdict (ok: 2,deprecated: 2,unsupported: 1,end-of-life: 1,scheduled: 1, total valid7, pluserrors: 2equalstotal: 9).byEventcounts every matched rule event across valid items (request@2.88.2matches twodeprecatedrules twice, andnode-sass@9.0.0matches both anend-of-liferule and adeprecatedrule, givingbyEvent.deprecated === 5).
Every valid entry in results has the schema documented in Result model:
{
"schemaVersion": 1,
"status": "end-of-life",
"matches": [
{
"ruleId": "npm.package.node-sass.eol",
"event": "end-of-life",
"date": "2024-07-24",
"daysLeft": null,
"replacement": "sass",
"sources": [
{
"title": "Node Sass is end-of-life",
"url": "https://sass-lang.com/blog/node-sass-is-end-of-life/",
"accessedAt": "2026-10-16"
}
],
"reviewedAt": "2026-10-14"
},
{
"ruleId": "npm.package.node-sass.deprecated",
"event": "deprecated",
"date": null,
"daysLeft": null,
"replacement": "sass",
"sources": [
{
"title": "node-sass package deprecation notice",
"url": "https://www.npmjs.com/package/node-sass",
"accessedAt": "2026-10-30"
},
{
"title": "node-sass registry metadata",
"url": "https://registry.npmjs.org/node-sass",
"accessedAt": "2026-10-30"
}
],
"reviewedAt": "2026-10-30"
}
],
"version": "9.0.0"
}
Streaming iterables
checkAll accepts any Iterable<CheckAllItem>. Arrays use an indexed fast path with a pre-sized output array; generators, Sets, and other iterables stream in a single pass without building a second intermediate list first:
import { checkAll, isCheckAllError } from "@velella/ebb/check-all";
function* itemsFromLockfile() {
yield { purl: "pkg:npm/express", version: "4.21.0" };
yield { purl: "pkg:npm/node-sass", version: "9.0.0" };
yield { purl: "pkg:npm/request", version: "2.88.2" };
}
const { results, summary } = checkAll(itemsFromLockfile());
console.log(
JSON.stringify({
statuses: results.map((entry) => (isCheckAllError(entry) ? entry.code : entry.status)),
total: summary.total,
}),
);
Output:
{"statuses":["ok","end-of-life","deprecated"],"total":3}
API and types
src/check-all.ts exports:
| Symbol | Role |
|---|---|
checkAll(items) |
Evaluates Iterable<CheckAllItem> synchronously and returns CheckAllResult. |
CheckAllItem |
{ readonly purl: string; readonly version: string } — PURL or product name, plus required version string. |
CheckAllResult |
{ results: CheckAllEntry[]; summary: CheckAllSummary } |
CheckAllEntry |
CheckResult | CheckAllError |
isCheckAllError(entry) |
Type guard narrowing a CheckAllEntry to CheckAllError (entry.status === "error"). |
CheckAllSummary |
{ total, byStatus, byEvent, errors } |
CheckAllError |
{ readonly status: "error"; readonly index: number; readonly code: CheckAllErrorCode; readonly message: string } |
CheckAllErrorCode |
Reason code recorded when judging an individual item fails. |
CheckAllItem requirements
Every item needs both purl and version. Even when a PURL from CycloneDX or SPDX already includes @version (pkg:npm/request@2.88.2), checkAll still evaluates against item.version ("2.88.2"). If a component has no version, item.version is empty and produces an "invalid-version" error entry at that index rather than guessing from the PURL.
CheckAllErrorCode values
| Code | When emitted |
|---|---|
"invalid-purl" |
purl starts with pkg: but is malformed (PurlError / EbbError). |
"invalid-version" |
version is empty/whitespace or invalid for the matched rule's version scheme (VersError). |
"unknown-product" |
Bare product name (without pkg:) does not match any product in the active rules (CheckError). |
"ambiguous-product" |
Bare product name ties across multiple ecosystems or packages (CheckError); pass a full pkg: PURL instead. |
"invalid-data-version" |
Active data configuration pins an invalid data version (EbbError). |
"invalid-date" |
Query supplied an invalid ISO date (EbbError). |
"package-file-not-found" |
Required bundled package file could not be located (EbbError). |
"internal" |
Unexpected internal error encountered while processing the item (EbbError). |
"data-unavailable" |
Configured data bundle could not be loaded (EbbError). |
"data-unavailable" |
Configured data bundle could not be loaded (EbbError). |
"data-update-failed" |
Active data source failed verification or loading (EbbError). |
"check-failed" |
Fallback when checking an item throws any other error. |