@cldmv/wisp loads JSON files in Node.js without caring which JSON import syntax the running Node.js version understands. It tries the modern import ... with { type: "json" } form first, falls back to the legacy assert form, and finally reads and parses the file itself, so the same call works from Node.js 16 through the current release.
Relative paths resolve from the file that calls wisp, not from wisp's own location, so wispSync("./config.json") means what it looks like it means from anywhere in your project — including from packages that depend on wisp.
Load JSON the same way on every Node.js version — quietly, like a wisp.
require()works in bundles and fails clearly on older Node.js —index.cjsnow loads the ESM entry with a plainrequire("./index.mjs")instead ofcreateRequire(__filename), sorequire("@cldmv/wisp")survives esbuild and webpack bundling. On Node.js versions without synchronousrequire(esm), whererequire()never worked, it now throws anERR_REQUIRE_ESMerror that names the supported versions (^20.19.0or>=22.12.0) and points toimport()(#30).- A failed validation throws instead of loading the fallback —
fallbackis now used only when the primary file cannot be read or parsed; avalidaterejection is reported as an error, and a fallback that also fails no longer loops forever (#35). On theimport()paths, avalidaterejection now throws the validation error once instead ofUnsupported type, andreviver/validateon a module without a default export receive a plain-object copy of its exports instead of failing (#41). The package is also relicensed under Apache-2.0 (#34). - Built package in
dist/— the published package is now bundled with tsup intodist/index.mjs, withdist/index.cjsas a thinrequire()wrapper, and ships onlydist/,types/,README.mdandLICENSE.importandrequire()of@cldmv/wispwork exactly as before; code that loaded the old rootindex.mjs/index.cjsorsrc/files by path must use the package specifier (#40). The test suite now runs on@cldmv/vitest-runnerwith 100% coverage (#37). - View full v1.0.7 Changelog
- v1.0.6 (October 2026) — Uniform file headers via
@cldmv/fix-headers, a CI fix and a development-dependency security update; no runtime change (Changelog) - v1.0.5 (October 2026) — First npm release since v1.0.1; TypeScript 6, chai 6 and
@types/node26 for development, v4 workflow syncs; no runtime change (Changelog) - v1.0.4 (September 2026) — Thrown errors now carry the original error as
cause; ESLint wired up; mocha 12 (Changelog) - v1.0.3 (August 2026) — Development-dependency security update (
picomatch); no runtime change (Changelog)
📚 For complete version history and detailed release notes, see the docs/changelog/ folder.
- Version-agnostic JSON imports —
with, thenassert, then a file-system read; whichever the running Node.js supports. - Caller-aware paths — relative paths resolve from the calling file;
baseoverrides it. - Async and sync —
wisp()returns a promise,wispSync()returns the value directly. - Validation and revivers —
validaterejects bad data,reviveris passed toJSON.parse. - Fallback files —
fallbacknames a second file to try when the first cannot be loaded. - ESM and CommonJS —
importandrequire()entry points, with TypeScript declarations included. - Zero runtime dependencies.
| Node Version | import ... with { type: 'json' } |
import ... assert { type: 'json' } |
Fallback |
|---|---|---|---|
| ≥ 22.10 | ✅ | ✅ | ✅ |
| ≥ 20.10 | ✅ | ✅ | ✅ |
| ≥ 18.20 | ✅ | ✅ | ✅ |
| ≥ 16.14 | ❌ | ✅ | ✅ |
| < 16.14 | ❌ | ❌ | ✅ |
- Node.js 16 or higher for
import(ESM). require()needs Node.js ^20.19.0 or >=22.12.0 (synchronousrequire(esm)). On older Node.js versions, load the package withimport()instead.
npm install @cldmv/wispimport { wisp, wispSync } from "@cldmv/wisp";
// Asynchronous loading
const config = await wisp("./config.json");
// Synchronous loading
const data = wispSync("./data.json");const { wisp, wispSync } = require("@cldmv/wisp");
// Asynchronous loading
wisp("./config.json").then((config) => {
console.log(config);
});
// Synchronous loading
const data = wispSync("./data.json");Asynchronously loads JSON from a file.
input(string | URL): Path or URL to the JSON fileoptions(object, optional):base(string | URL, optional): Base URL for resolving relative paths. Defaults to the caller's file URL.validate(function, optional): Validation function called with the parsed JSON. Throws if validation fails.reviver(function, optional): Reviver function passed toJSON.parse. For a module loaded throughimport()that has no default export,reviverandvalidatereceive a plain-object copy of its exports.type(string, optional): Import attribute type used for theimport()attempts. Defaults to"json"; the file-system fallback only runs for"json".fallback(string | URL, optional): A second file to load wheninputcannot be read or parsed. Avalidatefailure oninputthrows rather than falling back.
Promise<*>: The parsed JSON value.
import { wisp } from "@cldmv/wisp";
const data = await wisp("./config.json", {
validate: (json) => {
if (!json.requiredField) throw new Error("Missing required field");
},
reviver: (key, value) => (key === "date" ? new Date(value) : value)
});Synchronously loads JSON from a file.
input(string | URL): Path or URL to the JSON fileoptions(object, optional): Same aswispoptions, excepttype(the file is always read and parsed as JSON).
*: The parsed JSON value.
import { wispSync } from "@cldmv/wisp";
const data = wispSync("./config.json", {
validate: (json) => {
if (!json.version) throw new Error("Version required");
}
});| Option | Type | Description |
|---|---|---|
base |
string/URL | Base URL for relative path resolution. Defaults to caller's file URL. |
validate |
function | Validation function. Receives parsed JSON, should throw on invalid data. |
reviver |
function | JSON.parse reviver function for custom parsing. |
type |
string | Import attribute type for wisp()'s import() attempts. Defaults to "json". |
fallback |
string/URL | File to load instead when input cannot be read or parsed (missing, unreadable, or not valid JSON). A validate failure throws instead. |
@cldmv/wisp uses caller-aware path resolution:
- Relative paths are resolved relative to the file that calls
wisporwispSync - Absolute paths and URLs are used as-is
- The
baseoption overrides the default caller-based resolution
The module attempts to load JSON in this order:
import(url, { with: { type: 'json' } })(Node ≥ 18.20/20.10/22)import(url, { assert: { type: 'json' } })(Node ≥ 16.14)fs.readFile/fs.readFileSync(all supported Node versions)
This ensures maximum compatibility across Node.js versions. wispSync always uses fs.readFileSync.
Errors thrown by wisp are prefixed with @cldmv/wisp: for easy identification, and carry the underlying error as error.cause. A validation failure is reported as part of the load error:
try {
await wisp("./invalid.json", {
validate: () => {
throw new Error("Custom validation failed");
}
});
} catch (error) {
console.log(error.message); // "@cldmv/wisp: Failed to load JSON file at file:///…/invalid.json: @cldmv/wisp: Custom validation failed"
}- Changelog — release notes for every version
- Bug reports and fixes — write-ups of notable bugs and how they were fixed
Contributions are welcome — open an issue or a pull request.
- npm: @cldmv/wisp
- GitHub: CLDMV/wisp
- Issues: GitHub Issues
- Changelog: docs/changelog/
Apache-2.0 © CLDMV Inc. See LICENSE for the full text.