circuitjson-toolkit
v1.4.0
Published
Canonical CircuitJSON parsing, project, rendering, query, manufacturing, simulation, and scene contracts
Maintainers
Readme
CircuitJSON Toolkit
CircuitJSON Toolkit is the dependency-free common runtime for the ECAD toolkit
family. It provides the same parser, project, rendering, interaction, query,
manufacturing, simulation, 3D scene, capability, error, and worker contracts
used by gerber-toolkit, altium-toolkit, and kicad-toolkit.
CircuitJSON is the shared immutable model. Source-format packages keep their
native decoders and fidelity data in explicit extension namespaces while
common services operate on DocumentResult.model or a reused
CircuitJsonDocumentContext.
The package is browser- and Node-compatible, local-first, and has no runtime dependencies. It does not import Three.js, the DOM, filesystem APIs, or network clients.
Breaking API convergence
Version 1.1.0 intentionally changes names, parameters, return shapes, and
package subpaths to match the Gerber, Altium, and KiCad toolkits. The root is an
exact 17-class surface: 14 common classes plus three deprecated viewer
compatibility classes. Previous CircuitJSON-specific utilities were not
deleted: 37 source-neutral compatibility exports moved to
circuitjson-toolkit/extensions.
See the migration guide and its generated root, parser, renderer, and behavior appendices for all 1,207 preserved 1.0.17 features. The 1.1.0 release notes provide the concise change summary.
Version 1.1.1 makes queued worker-request ownership synchronous. Parser and project requests waiting behind active work can no longer observe later caller-buffer mutation; explicit transfers detach exact buffers immediately after admission. See the 1.1.1 release notes.
Version 1.1.2 makes schematic SVG paint deterministic. Open primitives always
emit fill="none", filled primitives retain authored paint or use the shared
theme fallback, and generic component and symbol bodies explicitly use the
ECAD Forge schematic palette. See the
1.1.2 release notes.
Version 1.2.0 extends the canonical PCB contract with validated source text fidelity, rotated drilled-pad bounds, and rounded-pad corner radii. Worker and metadata ownership traversals now support deeply nested, valid ECAD documents up to 256 levels. See the 1.2.0 release notes.
Version 1.2.1 keeps large canonical documents and multi-document projects inside explicit worker budgets. Selected extensions may contain up to four million structured items, while exact project envelopes receive independent per-document, project-metadata, aggregate-byte, and repeated-work limits. See the 1.2.1 release notes.
Version 1.3.0 adds an explicit fast preparation path for documents received through platform structured clone. Large native extension graphs can be owned without exception-driven binary probing of every ordinary record, while the general preparation path remains prototype-independent for arbitrary caller input. Cross-realm and altered-prototype buffers, typed views, byte limits, defensive copies, and all existing return shapes remain supported. See the 1.3.0 release notes.
Version 1.4.0 adds cooperative structured-clone preparation and a documented
owned-document construction path for source toolkits. Hosts can yield
throughout large extension traversal, binary protection, and property locking,
and format parsers can retain the identity of graphs they just created instead
of defensively copying them again. The resulting model, extensions,
parameters, and DocumentResult shape are unchanged. See the
1.4.0 release notes.
Before 1.1.0:
import { CircuitJsonParser } from 'circuitjson-toolkit'
const model = CircuitJsonParser.parseText(text, {
fileName: 'board.json'
})After 1.1.0:
import { Parser } from 'circuitjson-toolkit'
const document = Parser.parse({
fileName: 'board.json',
data: text
})
const model = document.modelFeatures
- Exact
ParserandProjectLoadercontracts with typed envelopes and errors - Immutable CircuitJSON validation proofs and request-scoped reusable indexes
- Copy-on-write normalization of supported legacy CircuitJSON aliases through
CircuitJsonDocument.normalizeModel() - Deterministic PCB, schematic, and BOM renderers
- Explicit schematic SVG stroke/fill paint that does not depend on browser defaults
- Asset-backed
schematic_imagerows and hierarchicalschematic_sheet_symbolrows with shared bounds and SVG behavior - Validated PCB text source-fidelity fields for independent font dimensions, exact nine-position anchors, visibility, layer provenance, and native type
- Reusable exact PCB interaction and spatial indexes
- Query, manufacturing, and injected simulation services
- Data-only, millimeter-based, right-handed Z-up PCB 3D scenes
- Descriptor-safe
ToolkitAssetmeasurement and preparation with zero-copy metadata mode, one-copy full mode, and shared ECAD model/image media-type inference - Explicit asynchronous scene-asset preparation without implicit I/O
- Strict
ecad-toolkit.worker.v1parsing/project protocol with progress, cancellation, and controlled buffer transfer - One-pass ownership for selected source extensions, with a separate 128 MiB / 4,000,000-item bound and exact direct/worker result parity
- Cooperative structured-clone preparation with in-place ordinary records and clean alias/cycle-preserving dense-array normalization
- Machine-readable capability inventory and packed downstream conformance harness
- Explicit
/extensionssurface retaining every previous specialized API - Local-only behavior and no runtime package dependencies
Install
npm install circuitjson-toolkitNode.js 20 or newer is required.
Parse and reuse a context
import {
CircuitJsonDocumentContext,
Parser,
PcbInteractionIndex,
PcbSvgRenderer,
QueryService
} from 'circuitjson-toolkit'
const document = Parser.parse({
fileName: 'board.json',
data: fileText
})
const context = CircuitJsonDocumentContext.prepare(document, {
indexes: ['elements', 'relations', 'connectivity', 'spatial']
})
const svg = PcbSvgRenderer.render(context, { side: 'top' })
const hits = PcbInteractionIndex.create(context).hitTest({ x: 10, y: 5 })
const components = QueryService.create(context).query({
select: 'components'
})
console.log(document.model, svg, hits, components.items)Browser hosts that receive the exact result of a platform structured clone can prepare it cooperatively:
const context = await CircuitJsonDocumentContext.prepareStructuredCloneAsync(
message.data,
{
indexes: ['elements', 'relations'],
ownership: 'exclusive',
yield: () => scheduler.yield()
}
)ownership: 'exclusive' is required and transfers the graph destructively.
The caller must relinquish every alias, including shared-memory writers, until
the promise settles. A mutation to a node that has not been acquired yet cannot
be reconstructed or detected later, and rejection may leave part of the graph
locked. Use prepareStructuredClone() for uninterrupted same-thread adoption,
or prepare() for arbitrary caller-owned values.
An already prepared CircuitJsonDocumentContext is reused immediately and
does not require an ownership declaration because no graph is transferred.
Ordinary extension records are adopted in place. Dense arrays are normalized
into clean arrays while preserving aliases and cycles, preventing unsupported
hidden properties from carrying mutable state into the context.
The model is validated and frozen before extension adoption begins. Dense
arrays, Map/Set normalization, immutable text accounting, binary copying,
binary installation, and property locking are divided into bounded slices.
Individual plain extension records are limited to 16,384 properties on this
cooperative path. The method returns the same
CircuitJsonDocumentContext shape as the synchronous preparation methods.
Omit yield to use scheduler.yield() when available and a zero-delay host
task otherwise. This entry point accepts only exact platform structured-clone
results with ordinary enumerable string properties.
Parser.parse() returns the exact clone-safe ecad-toolkit.document.v1
envelope:
{
schema: 'ecad-toolkit.document.v1',
id: 'document-...',
modelSchema: { name: 'circuit-json', version: '0.0.446' },
model: [],
source: { format: 'circuitjson', fileName: 'board.json', fileType: 'circuitjson' },
extensions: {},
assets: [],
diagnostics: [],
statistics: {}
}Project and asynchronous parsing
import { Parser, ProjectLoader } from 'circuitjson-toolkit'
const project = ProjectLoader.load(
[
{
name: 'board.json',
data: boardText,
assets: [{ name: 'body.step', data: bodyBytes }]
},
{ name: 'schematic.json', data: schematicText }
],
{ decodeAssets: 'metadata' }
)
const controller = new AbortController()
const document = await Parser.parseAsync(
{ fileName: 'board.json', data: boardBytes },
{
worker: 'auto',
transferInput: false,
signal: controller.signal,
onProgress: ({ stage, detail }) => console.log(stage, detail)
}
)
console.log(project.documents, document.model)Attached asset bytes count toward both maxEntryBytes and maxTotalBytes in
direct and worker project loading, even when decodeAssets: 'none' omits them
from the result.
Direct and worker calls return equivalent serialized results. Caller input is
never detached unless transferInput: true is explicit. worker: 'auto'
falls back to direct execution only when worker construction is unavailable;
explicit worker and runtime failures remain visible.
Selected source-native extensions are captured once into an immutable owned
snapshot. Their separate 128 MiB payload and 4,000,000-item ceilings permit
realistic renderer/model graphs. Canonical standalone documents remain within
a 250 MB result ceiling, while exact multi-document project envelopes use a
256 MiB aggregate ceiling plus independent 250 MB per-document and metadata
ceilings. Item and repeated-alias work budgets remain separately bounded; an
over-limit extension or result fails visibly instead of being silently
truncated. Binary extension values remain byte-backed and return defensive
copies instead of expanding into JavaScript number arrays.
When extensions are disabled, native documents and projects return the exact
empty map {}. Bounded ZIP consumers can preflight local/central filenames and
CRC32/size metadata with
ZipArchiveInspector.inspect() and validate inflated bytes with
verifyExtractedBytes(). Compression limits use member payload sizes, so ZIP
comments or other container padding cannot dilute the measured expansion ratio.
The shared PCB hole primitive preserves circular, pill/oval, rectangular, and square legal variants. Square holes normalize to equal-width rectangular apertures instead of circular fallbacks.
retainSource: 'reference' is available for direct parser calls that need the
exact caller input identity. It adds a non-enumerable sourceReference, does
not freeze the caller object, and is omitted from serialized results. Explicit
worker execution rejects this identity-only mode; automatic execution stays
direct.
Render, inspect, export, simulate, and build scenes
import {
BomTableRenderer,
ManufacturingService,
PcbScene3dBuilder,
PcbScene3dPreparator,
SchematicSvgRenderer,
SimulationService
} from 'circuitjson-toolkit'
const schematicSvg = SchematicSvgRenderer.render(document)
const bomHtml = BomTableRenderer.render(document)
const manufacturing = ManufacturingService.inspect(document)
const simulation = SimulationService.build(document)
const canonicalScene = PcbScene3dBuilder.build(document)
const preparedScene = await PcbScene3dPreparator.prepare(document, {
fidelity: 'native',
resolveAsset: async (request, { signal }) =>
await hostAssetStore.resolve(request, { signal })
})3D results use the ecad-toolkit.scene3d.v1 data contract. Runtime rendering
belongs to packages such as
pcb-scene3d-viewer.
Retained extension APIs
Previous specialized exports remain available explicitly:
import {
CircuitJsonParser,
CircuitJsonPcbSvgRenderer,
SpiceSimulationService
} from 'circuitjson-toolkit/extensions'New integrations should use canonical classes. /extensions exists for
deliberate migrations, not as a second common API. Its exact 37 exports are
source-neutral and can be shared or derived by all four toolkits.
The temporary root compatibility class also exposes
CircuitJsonDocument.normalizeModel(model, { owned }). It projects supported
pre-union table, PCB path, pad-clearance, courtyard, and artwork aliases onto
the pinned CircuitJSON union. The default copy-on-write mode returns the exact
input when it is already canonical; { owned: true } is reserved for
toolkit-owned mutable projections and may update them in place. Renderers and
viewers can therefore share the same normalization boundary instead of keeping
application adapters.
The compatibility renderer also provides
CircuitJsonPcbSvgRenderer.renderSides(model, sides) so callers migrating
multi-side output can prepare legacy primitives once.
CircuitJsonPcbHolePrimitiveModel is the shared geometry boundary for drilled
PCB elements. Polygon-plated pads derive rotation-local width and height from
pad_outline, while pill drill width, height, diameter, and rotation remain
available to viewers and manufacturing consumers without source adapters.
Direct asynchronous parsing and project loading own exact binary view windows and selected assets before the first progress callback. Worker-received inputs reuse their structured-clone ownership marker, avoiding a redundant receiver copy while keeping sync, direct async, and worker results mutation-isolated.
Package entrypoints
circuitjson-toolkitcircuitjson-toolkit/parsercircuitjson-toolkit/projectcircuitjson-toolkit/rendererscircuitjson-toolkit/interactioncircuitjson-toolkit/querycircuitjson-toolkit/manufacturingcircuitjson-toolkit/simulationcircuitjson-toolkit/scene3dcircuitjson-toolkit/capabilitiescircuitjson-toolkit/extensionscircuitjson-toolkit/testingcircuitjson-toolkit/workers/parser.worker.mjscircuitjson-toolkit/styles/renderers.css
Documentation
- API reference
- Capability inventory
- Migration from 1.0.17
- Model and envelope format
- Testing and downstream conformance
- 1.1.0 release notes
- 1.1.1 release notes
- 1.1.2 release notes
- 1.2.0 release notes
- 1.2.1 release notes
- 1.3.0 release notes
- 1.4.0 release notes
- Library scope
Package scope
This package owns common, data-only operations derived from CircuitJSON. Native
Gerber, Altium, and KiCad decoding remains in the source toolkit. Native-only
facts remain in document.extensions[format]; common services do not duplicate
or rename native renderer graphs.
The package does not own Three.js runtime rendering, browser UI controls, implicit filesystem access, implicit network access, or source-format-specific decoders.
Test
npm test
npm run check:format
npm run sync:schema -- --check
npm run check:features -- --strict
npm run benchmark -- --compare benchmarks/baseline-v1.0.17.json
npm run check:packed-entrypoints
npm run check:browser-dependenciesTests use small synthetic CircuitJSON and source-format fixtures only. Do not add customer, vendor, or source project documents.
License
This project is available under two licensing options.
1. Open-source software license
GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later).
You may use, modify, and distribute this project under the AGPL. If you distribute modified versions, run modified versions as a network service, or create larger works based on this project, they must comply with the AGPL, including source-code availability requirements.
The full AGPL license text is included in LICENSES/AGPL-3.0-or-later.txt. The root LICENSE file summarizes the package's public software license.
2. Commercial/proprietary license
For use in closed-source, proprietary, or otherwise AGPL-incompatible products, a separate paid commercial license is required.
Commercial licensing contact: https://github.com/SunboX
See COMMERCIAL-LICENSE.md. That file is a licensing notice, not a commercial license grant.
Downstream toolkit dependency
Gerber Toolkit, Altium Toolkit, and KiCad Toolkit use this package as their shared runtime. Their package or commercial terms do not replace this package's AGPL or separately granted commercial terms. A closed-source product using a source toolkit must also have an AGPL-compatible use or a separate commercial license for CircuitJSON Toolkit.
Documentation and notices
Documentation and non-code text are licensed under Creative Commons
Attribution-ShareAlike 4.0 (CC-BY-SA-4.0) unless otherwise marked.
The full CC-BY-SA license text is included in LICENSES/CC-BY-SA-4.0.txt.
Copyright (C) 2026 André Fiedler.
Copyright, license, attribution, and source-origin notices must be preserved as required by the AGPL, CC-BY-SA-4.0, and the notice files in this repository. See LICENSE, COMMERCIAL-LICENSE.md, and NOTICE.md.
Package-manager dependencies retain their own license terms and notices.
