npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@entryscape/inspec

v0.6.0

Published

Validation, Conversion, and Enrichment per the INSPEC specification

Readme

@entryscape/inspec

JavaScript/Node.js library implementing Validation, Conversion, and Enrichment per INSPEC specification v1.1.1.

INSPEC describes how application profiles and vocabularies are packaged as PROF, SHACL, RDFS, and SKOS artifacts. This library provides tools to:

  • Validate those artifacts against the INSPEC rule sets (PROF-INSPEC, SHACL-INSPEC, RDFS-INSPEC, SKOS-INSPEC).
  • Convert between related representations: UML (OSLO JSON) → SHACL, RDForms ↔ SHACL.
  • Enrich a PROF specification by deriving dcterms:hasPart, inspec:introduces, inspec:reuses, and prof:isProfileOf triples from its SHACL and RDFS artifacts.

The library is not a full SHACL implementation — it focuses on the INSPEC-relevant subset.

Install

pnpm add @entryscape/inspec
# or
npm install @entryscape/inspec

Requires Node.js 20+.

CLI

inspec validate <file> [--type prof|rdfs|skos|shacl] [--strict] [--indexation]
                       [--json] [--quiet]

inspec convert <converter> <file> [-o <output>]
                                  [--format turtle|ntriples|rdfjson]
                                  [--base-uri <uri>] [--profile-uri <uri>]

inspec enrich <prof-file> [--shacl <file>] [--rdfs <file>...] [--rdfs-owned]
                          [--overwrite] [-o <output>] [--format ...]

Converters: uml2shacl, rdforms2shacl, shacl2rdforms.

enrich's --shacl is required for a profile specification (it drives hasPart and the introduces/reuses split) but rejected for a foundational specification, which has no application profile and is enriched from its --rdfs vocabularies alone.

RDF graph inputs are parsed by file extension: .ttl (Turtle), .rdf/.xml (RDF/XML), and .json/.rdfjson (RDF/JSON — the native @entryscape/rdfjson form). This applies wherever a graph is loaded (validate, enrich's <prof-file>/--shacl/--rdfs, and convert shacl2rdforms). The rdforms2shacl and uml2shacl converters instead take their RDForms/OSLO JSON bundle directly. For these two, --base-uri sets the base for minted URIs — with an idMapper bundle it applies to the ids the mapper does not cover. When no base is configured, URIs are minted under the placeholder http://example.com/ and a warning is emitted.

For validate, the resource type is auto-detected from the graph when --type is omitted. --indexation switches missing-dependency reports (DEP-1) from warnings to errors — meant for when the dependency index is complete enough to definitively confirm a dependency does not exist. It currently has no effect from the CLI (see Known gaps).

Exit codes: 0 success, 1 validation failures or command misuse (including running inspec without arguments), 2 runtime error. Errors report a message only; set INSPEC_DEBUG=1 to include stack traces (internal errors such as a TypeError always include one).

Examples

# Validate a PROF specification, emit JSON
inspec validate spec.ttl --type prof --json

# Convert an RDForms template bundle to SHACL
inspec convert rdforms2shacl template.json -o shapes.ttl

# Round-trip SHACL back into an RDForms bundle
inspec convert shacl2rdforms shapes.ttl -o template.json

# Enrich a PROF graph with hasPart / introduces / reuses
inspec enrich spec.ttl --shacl shapes.ttl --rdfs vocab.ttl -o enriched.ttl

introduces vs reuses is normally decided from the PROF resource descriptors (a non-inherited prof:ResourceDescriptor with dcterms:conformsTo inspec:RDFS and a dcterms:subject naming the owned ontology). During local development a PROF file may list its resources as bare URIs, thus ontology ownership cannot be established — pass --rdfs-owned to treat every ontology declared in the --rdfs files as owned by the spec, classifying their terms as introduces. Do not combine it with reused vocabularies passed as --rdfs, or their terms will be misclassified as introduced.

Library

import {
  validatePROF,
  validateRDFS,
  validateSKOS,
  validateSHACL,
  convertUMLToSHACL,
  convertRDFormsToSHACL,
  convertSHACLToRDForms,
  enrich,
  collectPartSubjects,
  findSpecificationResource,
  isFoundationalSpecification,
  isInspecPartType,
  isProfileSpecification,
  listResourceDescriptors,
  loadGraph,
  parseTurtle,
  parseRdfXml,
  parseRdfJson,
  URIManager,
  IdMapperURIManager,
  ValidationResult,
  buildPrefixMap,
} from '@entryscape/inspec';

Validation

Each validator returns a ValidationResult with a uniform shape:

const graph = await loadGraph('spec.ttl');
const result = validatePROF(graph, { strict: false, indexation: false });

result.valid; // boolean — true if no errors
result.violations; // [{ rule, severity, message, subject?, hint? }]
result.summary; // { errors, warnings }

Rule coverage (rule identifiers as emitted in violations):

| Validator | Rules | | --------------- | -------------------------------------------------------- | | validatePROF | PROF-1, PROF-2, PROF-7–PROF-10, ENRICH-1–ENRICH-4, DEP-1 | | validateSHACL | AP-2–AP-4, AP-6–AP-15 | | validateRDFS | DV-2–DV-6, DEP-1 | | validateSKOS | TE-2–TE-5 |

inspec:refines and inspec:variant targets must be URIs, reported as an error under the rule whose precondition they break — AP-7/AP-8 (a public property shape, which AP-3 requires to have a URI), AP-9/AP-10 (a public node shape, AP-4) and AP-11/AP-12 (an application profile resource, AP-2). A literal or blank-node target can be none of those.

Two limits: a relation carried by a shape that is not a typed sh:NodeShape or sh:PropertyShape with a URI is not reached, and only the first target per shape is inspected. In both cases the bad value is still kept out of the enrichment and coverage sets — only the diagnostic is missing.

AP-1, AP-5, DV-1, and TE-1 are not checked. PROF-3/4/5 are realized by delegating each part's graph to the matching sub-validator rather than as separately emitted rules; PROF-6 is not implemented (see Known gaps).

DEP-1 is a repo-local rule identifier, not a rule of the INSPEC specification.

PROF-2 also covers a resource descriptor that declares more than one INSPEC part type — dcterms:conformsTo inspec:RDFS, inspec:SHACL — since a part "MAY be a data vocabulary, a terminology, an application profile or a diagram", one of them, and declaring two asserts two of PROF-3/4/5/6 over a single artifact. enrich() throws on the same input rather than guess which applies.

AP-3's blank-node rule exempts any property shape whose sh:path is rdf:type: it constrains the class of the enclosing node shape's instances rather than describing a property of the resource, so it may stay anonymous. sh:hasValue <C> and sh:in ( … ) are the two ways such a shape pins the class down. The AP-3 target-class hygiene warnings read the same construct, so a node shape re-using its class's URI as its own shape URI is reported as a warning. rdforms2shacl emits the idiom and shacl2rdforms reads it back.

AP-3 likewise exempts the blank property shapes of a node shape that is itself private, since a private shape's constraint scaffolding is not part of the published specification. The exemption reaches that node shape's own sh:property children and no further: a nested node shape, or a named property shape, is judged on its own severity whatever links it — see Known gaps. A blank property shape that a public node shape also links is still reported, against that public node shape.

In the library, strict: true affects only validatePROF: it promotes the ENRICH-1/2/4 advisories and PROF-10 to errors. The CLI's --strict is a broader policy applied after validation — it promotes every remaining warning to an error. indexation: true reports missing dependencies (DEP-1) as errors instead of warnings; it only matters when the dependency inputs are supplied (artifactGraphs for validatePROF, providedOntologyURIs for validateRDFS).

PROF introspection

validatePROF sub-validates a specification's parts only for the artifacts it is handed, and resolving those is the caller's job (see Known gaps). These functions expose the traversal that finds them:

const specificationURI = findSpecificationResource(profGraph);
const parts = listResourceDescriptors(profGraph, specificationURI);

// [{ descriptor, artifactURI, conformsTo, kind, subject, format, title,
//    isInheritedFrom }]

// Diagram parts are listed too, but an SVG is not a graph — fetch only the
// kinds that carry RDF, and only once per artifact.
const RDF_PART_KINDS = new Set(['rdfs', 'skos', 'shacl']);
const artifactURIs = [
  ...new Set(
    parts
      .filter((part) => RDF_PART_KINDS.has(part.kind) && part.artifactURI)
      .map((part) => part.artifactURI)
  ),
];

const artifactGraphs = Object.fromEntries(
  await Promise.all(
    artifactURIs.map(async (uri) => [uri, await fetchGraph(uri)])
  )
);

const result = validatePROF(profGraph, { artifactGraphs });

findSpecificationResource returns the first non-blank-node subject of dcterms:conformsTo inspec:PROF, or undefined. Blank-node candidates are ignored, since PROF-1 requires the specification resource to have a URI.

listResourceDescriptors returns one record per prof:hasResource statement, in graph order:

  • kind is 'rdfs', 'skos', 'shacl' or 'svg' — the part types PROF-3 to PROF-6 prescribe — and undefined for anything else, so if (part.kind) tests whether a part is INSPEC-controlled. The key is always present; only its value is undefined.
  • conformsTo is an array of every declared value, in graph order — a part may conform to something outside INSPEC as well — while kind resolves the INSPEC one among them. Every other predicate except dcterms:title is read single-valued. A descriptor declaring two INSPEC part types is contradictory and reported as PROF-2.
  • descriptor is a URI or, where a specification inlines its parts, a blank node id.
  • Every field except descriptor and conformsTo may be undefined; conformsTo is [] when the descriptor declares none. title carries every language variant keyed by lower-cased tag ('' for an untagged literal), one value per tag — a repeated tag keeps the last statement.
  • Passing no specification URI returns [], so a graph with no specification resource yields nothing rather than every descriptor it happens to contain.

The remaining helpers answer the questions that follow from a specification URI and its parts:

  • isProfileSpecification(profGraph, specificationURI) — whether the specification is typed prof:Profile.
  • isFoundationalSpecification(profGraph, specificationURI) — whether it is typed dcterms:Standard; further types do not affect the answer.
  • isInspecPartType(conformance) — whether a dcterms:conformsTo value names an INSPEC part type. Filter part.conformsTo with it rather than counting its length.
  • collectPartSubjects(parts) — the Set of dcterms:subject values of the given parts, i.e. the data vocabulary and terminology resources they describe (DV-6, TE-5). Parts without one are skipped.

The two type predicates are separate on purpose: PROF-1 types a specification as prof:Profile or dcterms:Standard, so true to both — or neither — is itself a PROF-1 violation for the caller to report, which a single kind-returning helper would hide.

Conversion

import rdforms from '@entryscape/rdforms';

// RDForms → SHACL
const itemstore = new rdforms.ItemStore();
itemstore.registerBundle({ source: rdformsTemplate });
const shapes = convertRDFormsToSHACL({
  itemstore,
  uriManager: new URIManager(baseURI, { profileURI }),
});

// SHACL → RDForms
const target = new rdforms.ItemStore();
convertSHACLToRDForms({ itemstore: target, graph: shaclGraph });
const bundle = target.getBundles()[0].getSource();

// UML (OSLO JSON) → SHACL
const umlShapes = convertUMLToSHACL({
  osloJSON: umlJson,
  uriManager: new URIManager(baseURI, { profileURI }),
});

All three converters accept a logger option ({ warn }, default console). Malformed or unconvertible input is never silently dropped: every skipped or degraded construct produces a warning through that logger — e.g. unsupported RDForms item types and anonymous (id-less) items in convertRDFormsToSHACL, implicitly-typed named shapes and unmappable sh:nodeKind values in convertSHACLToRDForms, and OSLO entries without an assignedURI in convertUMLToSHACL. Pass a custom logger to capture or silence them.

For RDForms bundles whose ids are minted through an idMapper, use IdMapperURIManager in place of URIManager: new IdMapperURIManager(idMapper, { baseURI, profileURI }) — the longest matching mapper prefix wins, and baseURI covers ids no prefix matches.

Enrichment

Single entrypoint, derives PROF structure triples from the supplied SHACL (and optionally RDFS) graphs:

const { graph, added, removed } = enrich(profGraph, {
  shaclGraph,
  rdfsGraphs: [rdfsGraph1, rdfsGraph2],
  specificationURI,
});

enrich also accepts a logger ({ warn }, default console). It reports input that could not be used — a non-URI inspec:refines target is skipped rather than coerced into a URI — and never comments on the result itself.

A caller that already knows which terms the specification introduces hands that answer over as introducedTerms, skipping the derivation from the RDFS graphs:

const { graph } = enrich(profGraph, {
  shaclGraph,
  specificationURI,
  introducedTerms: new Set(knownIntroducedTerms),
});

Members must be absolute http, https or urn URIs carrying no character a URI cannot hold; a relative id or a CURIE is refused. The same goes for specificationURI, which is the subject of every derived triple. ENRICH-3 emits every member, in insertion order. ENRICH-2 instead tests the set against the terms its shapes refer to, so for a profile the URIs must be written as the shapes write them — a term the set omits is reused, and a member no shape refers to is not emitted but reported through logger. An empty set is an answer, a profile that reuses everything, not an omission.

The set replaces the RDFS derivation rather than adding to it, so a non-empty rdfsGraphs, or rdfsOwned: true, alongside it throws.

introduces and reuses cover what ENRICH-2 asks for: the classes and properties referred to via the specification's public shapes. Public is AP-3/AP-4's sense, but reached through private scaffolding rather than applied shape by shape. The specification's worked example settles that: ex:ns-person declares no sh:targetClass, so foaf:Person is named only by a blank rdf:type constraint shape AP-3 counts as private — and the published enrichment block still reuses it.

Reachability is settled per shape, by whether the specification publishes it. A shape explicitly below sh:Violation publishes nothing. An own named shape — one declaring rdfs:isDefinedBy <specURI> — otherwise always does, whatever encloses it, being addressable and reusable in its own right. A blank node publishes only while a shape of this specification that uses it does, where uses means the object of sh:property, sh:node, sh:not or sh:qualifiedValueShape, or a member of an sh:and/sh:or/sh:xone list. So a terminology binding written as sh:node [ sh:severity sh:Info ; sh:property [ … ] ] does not announce its class as reused, while a named shape nested in the same place still does.

A term named only in a negative constraint is reused like any other: sh:not [ sh:class ex:Draft ] announces ex:Draft, even though instances must not carry it. The specification depends on the term either way, and a consumer resolving inspec:reuses needs to find it.

Only this specification's own shapes vouch for a blank node: a blank node has no URI and cannot be reused across specifications, so a foreign shape sharing a file with it must not publish it. The ENRICH-2 advisory in validatePROF reads the same set from each SHACL artifact, so the two agree for a specification whose shapes all live in the single SHACL artifact enrich() is given. They can differ on a split one: a shape whose rdfs:isDefinedBy sits in a second artifact is not own in the first, so the advisory judges it — and anything it holds — unpublished where enrich() on the merged graph does not.

graph is a copy of profGraph with the derived triples added — the specification's original statements (titles, descriptions, resource descriptors, …) are preserved. added holds the derived triples on their own, and removed those a preceding overwrite cleared; all three are Graph instances, so the delta can be merged elsewhere or exported without a conversion step.

added is legitimately empty when no rule matches — a foundational specification with no data vocabularies, or a profile whose SHACL carries no inspec:refines statement and no shape the specification owns. An empty added is a result, not a failure. removed is empty unless overwrite actually cleared something.

The two are not a net diff: they report what this run derived and what it cleared. Re-enriching an already-enriched graph with overwrite from the same SHACL and RDFS inputs is idempotent, so every triple then appears in both — after the inputs change, the two differ and the difference is the point. A caller wanting a true diff subtracts them itself.

removed is for reporting, not replay. Clearing an arc that pointed at a blank node leaves that node's own statements behind in graph, orphaned but still reachable by that id; removed captures only the arc, with nothing describing the node. Merging removed back would therefore mint a fresh, unconnected node rather than restore the original link.

For a profile (prof:Profile), shapes whose sh:targetClass / sh:path refer to terms defined by an ontology the spec declares as its own (via a non-inherited RDFS descriptor) are classified as introduces; the rest as reuses. For a foundational specification (dcterms:Standard), everything defined in the spec's own ontology is treated as introduces. Either way, introducedTerms replaces that derivation when the caller supplies it.

If the specification already carries enrichment triples (dcterms:hasPart, inspec:introduces, inspec:reuses, prof:isProfileOf), enrich throws (the CLI prints the error and exits non-zero) rather than merging stale and fresh values. Pass overwrite: true (CLI --overwrite) to clear those four predicates on the specification before adding the freshly derived ones.

Status & roadmap

The current release covers the MVP scope. Known gaps:

  • UML2RDFS — not implemented (UML2SHACL is). Reuse-vs-introduce handling for OSLO JSON is still an open question.

  • CSVW converters (CSVW2SHACL, CSVW2PROF) — not implemented.

  • Implicit ResourceDescriptor detection — not implemented.

  • Specification / RDFS index integration (e.g. dataportal.se lookups) — not implemented; the ArtifactCache currently has no population mechanism.

  • CLI dependency checking — validator-level dependency checking (DEP-1, DV-5) is implemented and usable via the library API by passing artifactGraphs; listResourceDescriptors provides the part list to build it from, so what remains is fetching and caching the artifacts. The CLI's validate --indexation flag is forwarded to the validators but has no effect from the CLI, because nothing there builds artifactGraphs (no --cache-dir wiring to read an ArtifactCache), and DEP-1 is only emitted when artifactGraphs is supplied. Complementary to the index-integration gap above — both are needed for end-to-end dependency checking from the command line.

  • PROF-6 (SVG diagram parts) — sub-validation is not implemented; SVG parts are reported as kind: 'svg' by listResourceDescriptors but otherwise accepted without being checked.

  • URI inputs in the CLI — commands accept local file paths only; resolving remote URIs (via cache or direct fetch) is deferred.

  • RDForms propertygroup items — not convertible to SHACL: a propertygroup's predicate is itself variable (its first child is a choice of predicates), which has no sh:path equivalent. rdforms2shacl skips such items with a warning.

  • RDForms items without an id — anonymous inline items in a group's items array cannot become INSPEC shapes (no stable URI to mint); rdforms2shacl skips them with a warning nudging toward assigning an id.

  • Foreign shapes in merged graphs — both the ENRICH-1/2 validation advisories and enrich()'s output are scoped to the spec's own shapes: a named shape counts as own only if it declares rdfs:isDefinedBy <specURI> (blank-node shapes always count). In a merged multi-AP graph the shapes of a refined/variant parent AP are neither flagged nor enriched; shapes missing rdfs:isDefinedBy are likewise skipped — AP-13 reports those.

  • URI-bearing private shapes — public/private classification follows the AP-3/AP-4 severity rule: a shape is public only at severity sh:Violation (SHACL's default when the severity is absent), and a property shape must additionally carry at least one validating constraint predicate. A shape meant as private that carries a URI but no below-sh:Violation severity is treated as public and is therefore subject to AP-3/AP-6/AP-13. Publicness here marks a shape as meant for publication, which is why AP-3/AP-4 then require a URI for it — a blank node shape carrying no below-sh:Violation severity is an error to fix, not a published shape.

    Privacy is mostly not inherited: a nested node shape that omits a below-sh:Violation severity is public whatever links it, so AP-4 reports it when it is a blank node and AP-6/AP-13 apply when it has a URI, and a named property shape is public even when the only node shape linking it is private, so AP-13 and ENRICH-1 still expect it to be covered. AP-3 is the exception: it skips a private node shape's property children outright rather than demanding URIs for them. enrich()'s introduces/reuses reach through a public shape into the private scaffolding it holds, judging a blank node by what uses it, as ENRICH-2 requires (see Enrichment); dcterms:hasPart reads only URIs and so follows the severity rule above.

Development

pnpm install
pnpm test
pnpm lint
pnpm build

License

LGPL-3.0-only