Releases: idfkit/idfkit-js
Release list
v0.2.0-rc.1
The first release candidate for 0.2.0, the release that unifies the JavaScript and Python APIs under one vocabulary and brings the Python library's validation, introspection and documentation helpers to JavaScript for the first time.
This is a prerelease, published under the next dist-tag. npm install @idfkit/core still gives you 0.1.0. Opt in explicitly:
npm install @idfkit/core@next @idfkit/schemas@nextIt is breaking. Five renames, all mechanical, all named exports, so a bundler tells you exactly which one you missed rather than failing at run time.
Validation, introspection and documentation URLs
The largest addition, and the reason to try this RC. @idfkit/core now carries the checks the Python library has always had.
validateDocument(doc) and validateObject(obj, schema) check a model against its schema. Nothing throws: a finding is a record with a severity, the object it was found on, the field it concerns, a message, and a code. Match on code and never on message — the codes are shared verbatim with Python (E001 required field missing, E003 wrong type, E004 value not among the permitted values, E005 to E008 the four bounds, E009 dangling reference, E010 singleton violation, W002/W003 unknown type and unknown field), while the messages differ between the runtimes because number formatting and type names do.
Validation is on demand. Parsing never triggers it, so reading a file costs exactly what it did before.
validateObject takes its schema as an argument rather than reading it off a document, which means you can check an object while it is still detached — before it goes anywhere near your model:
import { validateObject } from '@idfkit/core';
const candidate = zone.clone();
candidate.set('ceiling_height', -1);
const findings = validateObject(candidate, doc.schema);
// [{ code: 'E005', field: 'ceiling_height',
// message: 'Value -1 is below minimum 0', severity: 'error', ... }]describeObjectType(schema, typeName) reports what a type declares: every field with its type, whether it is required, its default, its units, its permitted values and its bounds, plus whether the type carries a name and whether it has an extensible group. It is the schema read as a description rather than as storage, which is what a form generator or a field editor wants.
docsUrlForObject, ioReferenceUrl, engineeringReferenceUrl and searchUrl return links into the EnergyPlus documentation, so a tool can point at the reference instead of embedding it.
⚠️ Breaking changes
1. IDFDocument is now IdfDocument
The last exported type spelling the acronym in full caps, out of step with IdfObject, IdfCollection and IdfParseError.
// Before
import type { IDFDocument } from '@idfkit/core';
// After
import type { IdfDocument } from '@idfkit/core';2. detectVersion() and detectEpJsonVersion() are now getIdfVersion() and getEpJsonVersion()
Python spells the same operation get_idf_version, and the shared naming register maps a Python get_* accessor to a TypeScript get* one. No alias is kept: a second public name for one concept is what the register exists to prevent.
// Before
const v = detectVersion(text);
// After
const v = getIdfVersion(text);3. doc.collection(type) is no longer exported
Use all(), which returns the same collection and is the name the register carries.
// Before
const zones = doc.collection('Zone');
// After
const zones = doc.all('Zone');4. The @idfkit/core/types subpath is gone
The generated per-version interfaces now ship as opt-in packages. This is why @idfkit/core fell from 6.7 MB unpacked to 286 KB: the two type maps were 5.3 MB of it and every reader paid for both whether or not they parameterised a document. They are types only, erased at build time, so runtime behaviour is identical either way.
// Before
import type { TypeMap } from '@idfkit/core/types';
// After (npm install @idfkit/types-v26-1@next)
import type { TypeMap } from '@idfkit/types-v26-1';5. SlimField.xmin, xmax and e were typed wrongly and are now honest
Only affects code reading SlimField directly. xmin and xmax were typed number but hold a boolean on EnergyPlus 8.9.0 through 9.5.0, where draft-04 makes the keyword a flag qualifying its sibling bound. Code trusting the old type computed value <= true and rejected everything at or below 1 in a positive-bounded field. e was typed string[] but holds numbers for the 68 fields carrying a numeric enum.
If you were evaluating bounds yourself, this is a good moment to stop and call validateObject instead. That case is exactly what it gets right and hand-rolled checks get wrong.
IdfCollection.insert(), delete() and rekey() are also no longer importable. All three were already tagged @internal, but no tsconfig set stripInternal, so they shipped as public by accident.
Fixes
-
Validation reads the constraints inside an
anyOffield instead of discarding them. The bundle used to collapse ananyOfto its numeric branch and throw the string branch away, so nothing recorded which sentinel a field accepts — and 1,781 fields takeAutocalculaterather thanAutosize, while 646 accept any string at all.SlimFieldgainsse, the string branch's enum verbatim. The bundle grows 5,416 bytes, 0.52%.The same change removes reference-check false positives from lists nothing can populate,
ZoneList-expanded names, and implicit remainder spaces. Across the 760 EnergyPlus example files that is 19,793 spurious findings gone, and files that validate cleanly rise from 149 to 470. -
Reading an unknown object type no longer modifies the document.
doc.all('Zoen')used to store the new empty collection, permanently adding a junk key as a side effect of a read. Unknown names still return empty rather than throwing, matching Python. -
A blank
Nameis no longer given an invented name. A type with an optional Name left blank keeps the blank as the epJSON key""; the synthetic"<Type> N"key is reserved for types with no Name field at all. Previously a document round-tripped through epJSON came back with an object nobody had named. (#7)
Also new
-
CONFORMANCE_LEVEL, exported from@idfkit/core, names the cross-language conformance corpus level this release is checked against. It is not a version number and is not comparable to one: two installed libraries agree about the formats when they report the same level, whatever their own versions say. -
The
idfkitfacade package is built and gated but not published in this RC. npm's similarity filter rejected the name and an appeal is pending, sonpm install idfkitdoes not work yet. Keep using the scoped packages.
Upgrade notes
Nothing upgrades transparently — this RC is behind the next tag by design, so you have to ask for it.
Work through the five renames in order; they are all named exports, so tsc, rollup, esbuild and Vite each report the ones you missed:
"IDFDocument" is not exported by "node_modules/@idfkit/core/dist/index.js"
The one change that can pass a build and still be wrong is number 5, SlimField's corrected types. If you read xmin, xmax or e directly, review those call sites by hand.
Feedback on the validation API in particular is what this candidate is for. Report anything at idfkit-js/issues; the full change list is in CHANGELOG.md, and the work landed in #29.
Documentation artifacts docs-2026.1
TypeScript examples and TypeDoc reference for the unified documentation site, from 57e3bcc.
v0.1.0
This release adds a third package, @idfkit/weather, and fixes two httpSource
bugs that made @idfkit/schemas fail to load on common static hosts. It is also
the first release cut through the tagged release workflow rather than published by
hand, so all three packages now carry the same real version number.
The API is still not stable. This is a 0.x line.
Features
-
@idfkit/weather(#5), a new package for finding an EnergyPlus weather file
and downloading it.StationIndexsearches the climate.onebuilding.org TMYx
index of 69,638 stations by name, WMO number, filename, or distance from a
coordinate.fetchWeatherFilesandfetchEpwfetch a station's archive and
return its EPW, DDY, and STAT files as text, ready to hand to
@idfkit/engine.Search is synchronous and pure; retrieval is the only async part. Nothing outside
the platform is required, because the ZIP reader is built on
DecompressionStream, so the package runs unchanged in a browser, a worker, an
edge runtime, or Node.import { loadBundledIndex } from '@idfkit/weather/node'; import { fetchEpw } from '@idfkit/weather'; const index = await loadBundledIndex(); const [nearest] = index.nearest(41.98, -87.9, { maxDistanceKm: 50 }); const epw = await fetchEpw(nearest.station);
climate.onebuilding.org sends no CORS header, so calls from a page need a proxy.
TherewriteUrl,baseUrl, andfetchoptions are there to route through one. -
geocodeanddetectLocation(#5) turn a place name or the caller's IP
address into the[latitude, longitude]pair you spread straight into
StationIndex.nearest. They share one rate limiter that holds to the upstream geocoder's
one-request-per-second policy, so you cannot accidentally get yourself blocked by
looping over a list of cities. -
@idfkit/weather/node(#5) reads the station index bundled with the package
straight off disk withloadBundledIndex, and writes downloaded files out, so a
script can search without touching the network at all. In the browser,
loadStationIndexfetches the same index over HTTP.
Fixes
-
Schemas load from hosts that serve
.gzwithContent-Encoding: gzip(#4),
including the Vite dev server, nginx withgzip_static on, and several static
hosts.httpSourceinflated every response unconditionally, so a body the HTTP
client had already inflated failed withincorrect header check. In the browser
that surfaced as a bareTypeError: Failed to fetchpointing nowhere near the
cause. The payload is now checked for the gzip magic bytes and inflated only when
it is actually compressed. -
httpSourceaccepts a same-origin path such ashttpSource('/schemas/'),
the form the README documents (#3). It previously threwTypeError: Invalid URL
before making any request, because the base was required to be absolute. Relative
bases now resolve against the document base, the wayfetch('/schemas/...')
does. In Node, where there is no document base, a relative base is still an error,
but it is raised when a schema is read rather than at construction, so building a
SchemaBundleat module scope is safe in server-rendered apps.
Packaging
@idfkit/weathernow carries a real version number. Its first upload to npm went
out as0.0.0, the in-repo placeholder that the release tooling treats as "not a
release".- The release workflow publishes all three packages. It previously published only
@idfkit/schemasand@idfkit/core, so a tagged release would have left weather
behind.
Upgrade notes
@idfkit/coreand@idfkit/schemasupgrade transparently from0.0.1. Both
fixes are internal to schema loading, and no API changed.- If you installed
@idfkit/weather@0.0.0, upgrade to0.1.0. The code is the
same; the version is not. - The three packages are versioned and released together. Install them at matching
versions:@idfkit/coredepends on@idfkit/schemasat an exact version, so a
mismatched pair will not resolve.
npm install @idfkit/core@0.1.0 @idfkit/schemas@0.1.0 @idfkit/weather@0.1.0