New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@async/json

Package Overview
Dependencies
Maintainers
2
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@async/json

Use a JSON file or folder like a small database, with sidecar state, queries, history, and Redis-backed scale paths.

Source
npmnpm
Version
0.3.0
Version published
Weekly downloads
37
105.56%
Maintainers
2
Weekly downloads
 
Created
Source

@async/json

Use a JSON file or a folder of JSON files like a small database.

pnpm add @async/json

The package is ESM-only and supports Node.js 24 and newer.

import json from '@async/json';

const users = await json('./users.json');
await users.create({ name: 'Ada Lovelace', role: 'admin' });
await users.find({ where: { role: 'admin' } });

const db = await json('./db');
await db.collection('users').find({ where: { role: 'admin' } });
await db.document('settings').get();

By default the visible JSON file is seed data and writes go to sidecar state under .async-json/state. Use writes: 'source' only when the JSON file itself should be rewritten.

@async/json owns standalone JSON database semantics: collection/document runtime APIs, scalar and compound identity, append-only collections, encoded payload validation, sidecar state, local indexes, RedisJSON storage, stable stringify, and JSON5-compatible parsing. Use @async/db when you also need readers, schemas, generated types, REST/GraphQL, operations, lifecycle, and store graduation.

Stable JSON Helpers

stableStringify() produces deterministic JSON by sorting object keys lexically. Array order is preserved.

import { parseJson, registerJson, stableJson, stableStringify } from '@async/json';

stableStringify({ b: 1, a: 2 });
// {"a":2,"b":1}

stableJson.stringify({ b: 1, a: 2 }, true);
// {
//   "a": 2,
//   "b": 1
// }

Pass pretty: true for two-space output, or use space with the same number clamping and string truncation rules as JSON.stringify():

stableStringify({ b: 1, a: 2 }, { pretty: true });
stableStringify({ b: 1, a: 2 }, { space: '\t' });

Sorting defaults to true. Disable it to preserve insertion order, or provide a custom comparator:

stableStringify({ b: 1, a: 2 }, { sort: false });

stableStringify({ id: 1, metadata: {}, name: 'Ada' }, {
  sort: (left, right) => left.length - right.length || left.localeCompare(right),
});

The second argument can be a replacer function. Replacers receive path and cycle context, so recursive structures can be rendered as JSON references:

const root = { id: 'root' };
root.self = root;

stableStringify(root, (key, value, context) =>
  context.circular
    ? { $ref: `#/${context.refPath?.join('/') ?? ''}` }
    : value,
);

parseJson() accepts JSON5-compatible input, including comments, trailing commas, unquoted keys, and single-quoted strings. $ref objects are not resolved automatically; revivers can resolve them in one place:

const graph = parseJson(`{
  users: { u_1: { name: 'Ada' } },
  owner: { $ref: '#/users/u_1' },
}`, (key, value, context) =>
  context.isRef ? context.resolvePointer(context.ref ?? '#') : value,
);

registerJson() installs an opt-in JSON shim on globalThis or a provided target. The shim keeps native JSON.stringify(value, replacer, space) and JSON.parse(text, reviver) argument shapes while using @async/json parsing and stable stringify behavior. The returned function restores the previous JSON object.

const restore = registerJson();
try {
  JSON.stringify({ b: 1, a: 2 });
  JSON.parse('{a: 1}');
} finally {
  restore();
}

Identity, Logs, And Encoded Payloads

Single-field resources keep using id by default:

const users = await json('./users.json');
await users.get('u_1');

Compound identity uses object keys instead of delimiter-encoded ids:

const memberships = await json('./memberships.json', {
  identity: { fields: ['orgId', 'userId'] },
  indexes: ['role'],
});

await memberships.get({ orgId: 'o_1', userId: 'u_1' });
await memberships.patch({ orgId: 'o_1', userId: 'u_1' }, { role: 'admin' });

Append-only collections allow append() and block create/update/delete/replace APIs:

const events = await json('./events.json', {
  writePolicy: 'append-only',
});

await events.append({ id: 'e_1', type: 'created' });

Bytes fields validate JSON-safe encoded strings while keeping payloads opaque:

const updates = await json('./updates.json', {
  fields: {
    id: { type: 'string', required: true },
    update: { type: 'bytes', encoding: 'base64url', required: true },
  },
});

await updates.append({ id: 'u_1', update: 'YWJjZA' });

Redis JSON

import json from '@async/json';
import { redisJson } from '@async/json/redis';

const db = await json('./db', {
  store: redisJson({ client, prefix: 'app:' }),
  indexes: {
    users: [{ fields: ['email'] }],
  },
});

Redis mode stores collection records as per-record Redis JSON keys. Declared indexes can be mapped to Redis Search; indexes are not created implicitly from observed queries.

Local Development

pnpm install
pnpm run build
pnpm run test
pnpm run release:check

release:check builds the package, runs tests, verifies the API surface ledger, and runs a package dry-run.

Generated files:

  • dist/ is build output and is not committed.
  • .async/pages/ is generated GitHub Pages output and is not committed.
  • .async/, .async-json/, node_modules/, and *.tgz are local runtime, cache, dependency, and package output.
  • .github/workflows/async-pipeline.yml is generated from pipeline.ts.

Website

The project site is published through GitHub Pages at https://async.github.io/json/.

Release Status

@async/json publishes as a public npm package. Public API changes should update api-contract.json, regenerate API_SURFACE.md, update this README, and add tests in the same change.

FAQs

Package last updated on 15 Jun 2026

Related posts