Skip to content

Releases: idfkit/idfkit-js

v0.2.0-rc.1

v0.2.0-rc.1 Pre-release
Pre-release

Choose a tag to compare

@samuelduchesne samuelduchesne released this 04 Sep 02:19
13fb45c

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@next

It 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 anyOf field instead of discarding them. The bundle used to collapse an anyOf to its numeric branch and throw the string branch away, so nothing recorded which sentinel a field accepts — and 1,781 fields take Autocalculate rather than Autosize, while 646 accept any string at all. SlimField gains se, 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 Name is 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 idfkit facade package is built and gated but not published in this RC. npm's similarity filter rejected the name and an appeal is pending, so npm install idfkit does 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

Choose a tag to compare

@github-actions github-actions released this 04 Sep 00:09

TypeScript examples and TypeDoc reference for the unified documentation site, from 57e3bcc.

v0.1.0

Choose a tag to compare

@samuelduchesne samuelduchesne released this 13 Aug 21:44

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. StationIndex searches the climate.onebuilding.org TMYx
    index of 69,638 stations by name, WMO number, filename, or distance from a
    coordinate. fetchWeatherFiles and fetchEpw fetch 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.
    The rewriteUrl, baseUrl, and fetch options are there to route through one.

  • geocode and detectLocation (#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 with loadBundledIndex, and writes downloaded files out, so a
    script can search without touching the network at all. In the browser,
    loadStationIndex fetches the same index over HTTP.

Fixes

  • Schemas load from hosts that serve .gz with Content-Encoding: gzip (#4),
    including the Vite dev server, nginx with gzip_static on, and several static
    hosts. httpSource inflated every response unconditionally, so a body the HTTP
    client had already inflated failed with incorrect header check. In the browser
    that surfaced as a bare TypeError: Failed to fetch pointing nowhere near the
    cause. The payload is now checked for the gzip magic bytes and inflated only when
    it is actually compressed.

  • httpSource accepts a same-origin path such as httpSource('/schemas/'),
    the form the README documents (#3). It previously threw TypeError: Invalid URL
    before making any request, because the base was required to be absolute. Relative
    bases now resolve against the document base, the way fetch('/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
    SchemaBundle at module scope is safe in server-rendered apps.

Packaging

  • @idfkit/weather now carries a real version number. Its first upload to npm went
    out as 0.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/schemas and @idfkit/core, so a tagged release would have left weather
    behind.

Upgrade notes

  • @idfkit/core and @idfkit/schemas upgrade transparently from 0.0.1. Both
    fixes are internal to schema loading, and no API changed.
  • If you installed @idfkit/weather@0.0.0, upgrade to 0.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/core depends on @idfkit/schemas at 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