Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 143 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,47 @@
# @cldmv/wisp

A Node.js module for version-agnostic JSON importing, providing transparent support for modern and legacy import syntaxes with automatic fallbacks.
**@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.

## Overview
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.

`@cldmv/wisp` allows you to load JSON files in Node.js without worrying about version-specific import syntax. It automatically tries the most modern import methods first and falls back to reliable file system operations.
> _Load JSON the same way on every Node.js version β€” quietly, like a wisp._

## Node.js Version Support
[![npm version]][npm_version_url] [![npm downloads]][npm_downloads_url] [![GitHub downloads]][github_downloads_url] [![Last commit]][last_commit_url] [![npm last update]][npm_last_update_url]

[![Contributors]][contributors_url] [![Sponsor shinrai]][sponsor_url]

---

## ✨ What's New

### Latest: v1.0.7 (October 2026)

- **`require()` works in bundles and fails clearly on older Node.js** β€” `index.cjs` now loads the ESM entry with a plain `require("./index.mjs")` instead of `createRequire(__filename)`, so `require("@cldmv/wisp")` survives esbuild and webpack bundling. On Node.js versions without synchronous `require(esm)`, where `require()` never worked, it now throws an `ERR_REQUIRE_ESM` error that names the supported versions (`^20.19.0` or `>=22.12.0`) and points to `import()`. The ESM entry and the library code are unchanged (#30).
- **A failed validation throws instead of loading the fallback** β€” `fallback` is now used only when the primary file cannot be read or parsed; a `validate` rejection is reported as an error, and a fallback that also fails no longer loops forever ([#35](https://github.com/CLDMV/wisp/pull/35)). The package is also relicensed under Apache-2.0 ([#34](https://github.com/CLDMV/wisp/pull/34)).
- [View full v1.0.7 Changelog](https://github.com/CLDMV/wisp/blob/master/docs/changelog/v1/v1.0.7.md)

### Recent Releases

- **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](https://github.com/CLDMV/wisp/blob/master/docs/changelog/v1/v1.0.6.md))
- **v1.0.5** (October 2026) β€” First npm release since v1.0.1; TypeScript 6, chai 6 and `@types/node` 26 for development, v4 workflow syncs; no runtime change ([Changelog](https://github.com/CLDMV/wisp/blob/master/docs/changelog/v1/v1.0.5.md))
- **v1.0.4** (September 2026) β€” Thrown errors now carry the original error as `cause`; ESLint wired up; mocha 12 ([Changelog](https://github.com/CLDMV/wisp/blob/master/docs/changelog/v1/v1.0.4.md))
- **v1.0.3** (August 2026) β€” Development-dependency security update (`picomatch`); no runtime change ([Changelog](https://github.com/CLDMV/wisp/blob/master/docs/changelog/v1/v1.0.3.md))

πŸ“š **For complete version history and detailed release notes, see the [docs/changelog/](https://github.com/CLDMV/wisp/tree/master/docs/changelog/) folder.**

---

## πŸš€ Key Features

- **Version-agnostic JSON imports** β€” `with`, then `assert`, then a file-system read; whichever the running Node.js supports.
- **Caller-aware paths** β€” relative paths resolve from the calling file; `base` overrides it.
- **Async and sync** β€” `wisp()` returns a promise, `wispSync()` returns the value directly.
- **Validation and revivers** β€” `validate` rejects bad data, `reviver` is passed to `JSON.parse`.
- **Fallback files** β€” `fallback` names a second file to try when the first cannot be loaded.
- **ESM and CommonJS** β€” `import` and `require()` entry points, with TypeScript declarations included.
- **Zero runtime dependencies.**

### Node.js Version Support

| Node Version | `import ... with { type: 'json' }` | `import ... assert { type: 'json' }` | Fallback |
| ------------ | ---------------------------------- | ------------------------------------ | -------- |
Expand All @@ -16,15 +51,26 @@ A Node.js module for version-agnostic JSON importing, providing transparent supp
| β‰₯ 16.14 | ❌ | βœ… | βœ… |
| < 16.14 | ❌ | ❌ | βœ… |

## Installation
---

## πŸ“¦ Installation

### Requirements

- **Node.js 16 or higher** for `import` (ESM).
- **`require()` needs Node.js ^20.19.0 or >=22.12.0** (synchronous `require(esm)`). On older Node.js versions, load the package with `import()` instead.

### Install

```bash
npm install @cldmv/wisp
```

## Usage
---

## πŸš€ Quick Start

### ESM (Modern)
### ESM

```javascript
import { wisp, wispSync } from "@cldmv/wisp";
Expand All @@ -36,7 +82,7 @@ const config = await wisp("./config.json");
const data = wispSync("./data.json");
```

### CJS (CommonJS)
### CommonJS

```javascript
const { wisp, wispSync } = require("@cldmv/wisp");
Expand All @@ -50,7 +96,9 @@ wisp("./config.json").then((config) => {
const data = wispSync("./data.json");
```

## API Reference
---

## πŸ“– API Reference

### `wisp(input, options?)`

Expand All @@ -63,6 +111,8 @@ Asynchronously loads JSON from a file.
- `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 to `JSON.parse`.
- `type` (string, optional): Import attribute type used for the `import()` attempts. Defaults to `"json"`; the file-system fallback only runs for `"json"`.
- `fallback` (string | URL, optional): A second file to load when `input` cannot be read or parsed. A `validate` failure on `input` throws rather than falling back.

#### Returns

Expand All @@ -88,11 +138,11 @@ Synchronously loads JSON from a file.
#### Parameters

- `input` (string | URL): Path or URL to the JSON file
- `options` (object, optional): Same as `wisp` options.
- `options` (object, optional): Same as `wisp` options, except `type` (the file is always read and parsed as JSON).

#### Returns

`Promise<*>`: The parsed JSON value.
`*`: The parsed JSON value.

#### Example

Expand All @@ -106,35 +156,45 @@ const data = wispSync("./config.json", {
});
```

## Options
---

| 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. |
## βš™οΈ Options

## Path Resolution
| 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. |

---

## 🧭 Path Resolution

`@cldmv/wisp` uses caller-aware path resolution:

- Relative paths are resolved relative to the file that calls `wisp` or `wispSync`
- Absolute paths and URLs are used as-is
- The `base` option overrides the default caller-based resolution

## Fallback Order
---

## πŸ” Fallback Order

The module attempts to load JSON in this order:

1. `import(url, { with: { type: 'json' } })` (Node β‰₯ 18.20/20.10/22)
2. `import(url, { assert: { type: 'json' } })` (Node β‰₯ 16.14)
3. `fs.readFile` / `fs.readFileSync` (all supported Node versions)

This ensures maximum compatibility across Node.js versions.
This ensures maximum compatibility across Node.js versions. `wispSync` always uses `fs.readFileSync`.

## Error Handling
---

Validation errors are prefixed with `@cldmv/wisp:` for easy identification:
## πŸ›‘ Error Handling

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:

```javascript
try {
Expand All @@ -144,10 +204,69 @@ try {
}
});
} catch (error) {
console.log(error.message); // "@cldmv/wisp: Custom validation failed"
console.log(error.message); // "@cldmv/wisp: Failed to load JSON file at file:///…/invalid.json: @cldmv/wisp: Custom validation failed"
}
```

## License
---

## πŸ“š Documentation

- **[Changelog](https://github.com/CLDMV/wisp/tree/master/docs/changelog/)** β€” release notes for every version
- **[Bug reports and fixes](https://github.com/CLDMV/wisp/blob/master/BUGS.md)** β€” write-ups of notable bugs and how they were fixed

[![CodeFactor]][codefactor_url] [![OpenSSF Scorecard]][ossf_scorecard_url] [![npms.io score]][npms_url] [![npm unpacked size]][npm_size_url] [![Repo size]][repo_size_url]

---

## 🀝 Contributing

Contributions are welcome β€” open an [issue](https://github.com/CLDMV/wisp/issues) or a pull request.

[![Contributors]][contributors_url] [![Sponsor shinrai]][sponsor_url]

---

## πŸ”— Links

- **npm**: [@cldmv/wisp](https://www.npmjs.com/package/@cldmv/wisp)
- **GitHub**: [CLDMV/wisp](https://github.com/CLDMV/wisp)
- **Issues**: [GitHub Issues](https://github.com/CLDMV/wisp/issues)
- **Changelog**: [docs/changelog/](https://github.com/CLDMV/wisp/tree/master/docs/changelog/)

---

## πŸ“„ License

[![GitHub license]][github_license_url] [![npm license]][npm_license_url]

Apache-2.0 Β© CLDMV Inc. See [LICENSE](https://github.com/CLDMV/wisp/blob/master/LICENSE) for the full text.

[npm version]: https://img.shields.io/npm/v/%40cldmv%2Fwisp.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
[npm_version_url]: https://www.npmjs.com/package/@cldmv/wisp
[last commit]: https://img.shields.io/github/last-commit/CLDMV/wisp?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
[last_commit_url]: https://github.com/CLDMV/wisp/commits
[npm last update]: https://img.shields.io/npm/last-update/%40cldmv%2Fwisp?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
[npm_last_update_url]: https://www.npmjs.com/package/@cldmv/wisp
[codefactor]: https://img.shields.io/codefactor/grade/github/CLDMV/wisp?style=for-the-badge&logo=codefactor&logoColor=white&labelColor=F44A6A
[codefactor_url]: https://www.codefactor.io/repository/github/cldmv/wisp
[openssf scorecard]: https://img.shields.io/ossf-scorecard/github.com/CLDMV/wisp?style=for-the-badge&label=OpenSSF%20Scorecard
[ossf_scorecard_url]: https://scorecard.dev/viewer/?uri=github.com/CLDMV/wisp
[npms.io score]: https://img.shields.io/npms-io/final-score/%40cldmv%2Fwisp?style=for-the-badge&logo=npms&logoColor=white&labelColor=0B5D57
[npms_url]: https://npms.io/search?q=%40cldmv%2Fwisp
[npm downloads]: https://img.shields.io/npm/dm/%40cldmv%2Fwisp.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
[npm_downloads_url]: https://www.npmjs.com/package/@cldmv/wisp
[github downloads]: https://img.shields.io/github/downloads/CLDMV/wisp/total?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
[github_downloads_url]: https://github.com/CLDMV/wisp/releases
[npm unpacked size]: https://img.shields.io/npm/unpacked-size/%40cldmv%2Fwisp.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
[npm_size_url]: https://www.npmjs.com/package/@cldmv/wisp
[repo size]: https://img.shields.io/github/repo-size/CLDMV/wisp?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
[repo_size_url]: https://github.com/CLDMV/wisp
[github license]: https://img.shields.io/github/license/CLDMV/wisp.svg?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
[github_license_url]: https://github.com/CLDMV/wisp/blob/HEAD/LICENSE
[npm license]: https://img.shields.io/npm/l/%40cldmv%2Fwisp.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
[npm_license_url]: https://www.npmjs.com/package/@cldmv/wisp
[contributors]: https://img.shields.io/github/contributors/CLDMV/wisp.svg?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
[contributors_url]: https://github.com/CLDMV/wisp/graphs/contributors
[sponsor shinrai]: https://img.shields.io/github/sponsors/shinrai?style=for-the-badge&logo=githubsponsors&logoColor=white&labelColor=EA4AAA&label=Sponsor
[sponsor_url]: https://github.com/sponsors/shinrai
34 changes: 34 additions & 0 deletions docs/changelog/v1/v1.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Wisp v1.0.0 Changelog

**Release Date**: November 2025
**Release Type**: Major (initial release)

---

## Overview

First public release of `@cldmv/wisp`, a small library for loading JSON files in Node.js without caring which JSON import syntax the running Node.js version supports.

---

## ✨ Features

### Version-agnostic JSON loading

- `wisp(input, options)` loads a JSON file asynchronously. It tries `import(url, { with: { type: "json" } })` first, then the legacy `import(url, { assert: { type: "json" } })`, and finally reads and parses the file with `fs.readFile`.
- `wispSync(input, options)` loads a JSON file synchronously with `fs.readFileSync`.
- `input` accepts a relative path, an absolute path, a `file://` string or a `URL`. Relative paths resolve from the file that called `wisp` / `wispSync`, unless `options.base` is given.
- Options: `base` (base path or URL for relative inputs), `validate` (called with the parsed value; a throw rejects the load), `reviver` (passed to `JSON.parse`), `type` (import attribute type, `"json"` by default, async only) and `fallback` (a second path or URL to try when the first one cannot be loaded).
- Values returned with a `reviver` or `validate` are deep-cloned (with `structuredClone` when available), so the module cache is never mutated.

### Packaging

- Dual entry points: `index.mjs` for `import` and `index.cjs` for `require()`, with `wisp` as the default export.
- Declared `engines.node` of `>=16.14`.

---

## Upgrade notes

- Initial release; nothing to upgrade from.
- Relative-path resolution from the caller's location does not work correctly in this version when the package is installed as a dependency (paths resolve from inside the package). Upgrade to [v1.0.1](./v1.0.1.md).
31 changes: 31 additions & 0 deletions docs/changelog/v1/v1.0.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Wisp v1.0.1 Changelog

**Release Date**: November 2025
**Release Type**: Patch

---

## Overview

Fixes caller path resolution, which made relative paths unusable from any package that depended on `@cldmv/wisp`, and starts shipping the type declarations.

---

## πŸ› Bug Fixes

### Relative paths resolve from the caller again

In v1.0.0, `wispSync("../examples/data.json")` called from a consuming module resolved the path relative to `node_modules/@cldmv/wisp/src/wisp.mjs` instead of the caller, and failed with `ENOENT`. The resolver in `src/lib/resolve-from-caller.mjs` was rewritten: it finds the package root by walking up to the nearest `package.json`, walks the call stack, and picks the first frame after the stack leaves the package's `src/` or `dist/` directory. It no longer depends on hard-coded file names or on an `index.mjs` frame being present (Node.js does not create one for `export *` re-exports). The bug and the fix are described in [BUGS.md](https://github.com/CLDMV/wisp/blob/master/BUGS.md).

---

## πŸ“¦ Packaging

- The `types/` folder (generated `.d.mts` declarations) is now included in the published package.
- `engines.node` lowered from `>=16.14` to `>=16.0.0`; on Node.js before 16.14 the file-system fallback is used.

---

## Upgrade notes

- No API changes β€” drop-in for v1.0.0. Code that worked around the path bug by passing an absolute path or `base` keeps working.
25 changes: 25 additions & 0 deletions docs/changelog/v1/v1.0.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Wisp v1.0.2 Changelog

**Release Date**: July 2026
**Release Type**: Patch

---

## Overview

Moves the repository onto the CLDMV v4 staging-branch release flow. No runtime code changed.

This version was tagged on GitHub but was not published to npm; npm went from v1.0.1 to v1.0.5.

---

## πŸ”§ CI & tooling

- Replaced the old release workflow with the CLDMV v4 workflow set: work merges into `next`, a persistent release PR carries it to `master`, and `hotfixes` handles urgent fixes ([#2](https://github.com/CLDMV/wisp/pull/2)).
- Added CodeQL, OpenSSF Scorecard, dependency review, CLA, labeler, PR-title normalization, stale, branch-retention, tag-health and release-notification workflows.

---

## Upgrade notes

- No runtime change β€” drop-in for v1.0.1.
24 changes: 24 additions & 0 deletions docs/changelog/v1/v1.0.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Wisp v1.0.3 Changelog

**Release Date**: August 2026
**Release Type**: Patch

---

## Overview

A development-dependency security update. No runtime code changed.

This version was tagged on GitHub but was not published to npm; npm went from v1.0.1 to v1.0.5.

---

## πŸ”§ Dependencies

- `picomatch` 2.3.1 β†’ 2.3.2, a development-only transitive dependency of `mocha` ([#3](https://github.com/CLDMV/wisp/pull/3), [#4](https://github.com/CLDMV/wisp/pull/4)).

---

## Upgrade notes

- No runtime change β€” drop-in for v1.0.2.
Loading
Loading