Skip to content

feat(json): add JSON generators - #1079

Open
avivkeller wants to merge 2 commits into
mainfrom
json-generators
Open

avivkeller wants to merge 2 commits into
mainfrom
json-generators

Conversation

@avivkeller

Copy link
Copy Markdown
Member

This PR is meant to supersede #287 and fix #214.

The majority of the added lines are test fixtures and schemas, so do not be alarmed by the size of this PR.

This new generator writes one JSON document per source file. Each document
is a tree of the file's headings, in document order, with the metadata,
signature or type, Markdown body, and code examples of every one of them.

The output is described by a JSON schema, published at the URL every document
carries in $schema, and shipped with the package as
@doc-kit/core/generators/json/schema.json.

An output looks roughly like:

{
  "$schema": "https://doc-kit.nodejs.org/schemas/api-doc/1.0.0.json",
  "id": "fs",
  "path": "/fs",
  "type": "module",
  "module": "fs",
  "title": "File system",
  "introducedIn": "v0.10.0",
  "sourceLink": {
    "path": "lib/fs.js",
    "url": "https://github.com/nodejs/node/blob/HEAD/lib/fs.js"
  },
  "stability": { "index": "2","description": "Stable" },
  "added": [],
  "deprecated": [],
  "removed": [],
  "napiVersion": [],
  "changes": [],
  "description": "The `node:fs` module enables interacting with the file system in a\nway modeled on standard POSIX functions.\n\n",
  "summary": "The `node:fs` module enables interacting with the file system in a way modeled on standard POSIX functions.",
  "examples": [],
  "children": []
}

Co-Authored-By: flakey5 <73616808+flakey5@users.noreply.github.com>
Signed-off-by: avivkeller <me@aviv.sh>
@avivkeller
avivkeller requested a review from a team as a code owner September 2, 2026 22:36
@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
api-docs-tooling Ready Ready Preview Sep 2, 2026 10:39pm UTC

Request Review

@codecov

codecov Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.42563% with 419 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.74%. Comparing base (da5d8e6) to head (7df595e).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...re/src/generators/json/__tests__/generate.test.mjs 15.88% 233 Missing ⚠️
packages/core/src/generators/json/utils/node.mjs 54.90% 69 Missing ⚠️
packages/core/src/generators/json/generate.mjs 38.46% 40 Missing ⚠️
...ckages/core/src/generators/json/utils/document.mjs 32.75% 39 Missing ⚠️
www/doc-kit.config.mjs 0.00% 14 Missing ⚠️
packages/react/src/jsx-ast/utils/buildContent.mjs 33.33% 6 Missing ⚠️
scripts/generate-json-types.mjs 90.19% 5 Missing ⚠️
packages/core/src/generators/json-all/generate.mjs 91.42% 3 Missing ⚠️
...kages/core/src/generators/json/utils/lifecycle.mjs 96.72% 2 Missing ⚠️
...kages/core/src/generators/json/utils/signature.mjs 98.91% 1 Missing and 1 partial ⚠️
... and 6 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1079      +/-   ##
==========================================
- Coverage   90.57%   89.74%   -0.83%     
==========================================
  Files         217      245      +28     
  Lines       20755    22791    +2036     
  Branches     1969     2160     +191     
==========================================
+ Hits        18799    20454    +1655     
- Misses       1949     2328     +379     
- Partials        7        9       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

api-links Generator

Output: 1 file differs

apilinks.json
Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines

  {
    'Agent.defaultMaxSockets': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L300',
    'Buffer.alloc': 'https://github.com/{repository}/blob/HEAD/lib/buffer.js#L450',
    'Buffer.allocUnsafe': 'https://github.com/{repository}/blob/HEAD/lib/buffer.js#L469',
    'Buffer.allocUnsafeSlow': 'https://github.com/{repository}/blob/HEAD/lib/buffer.js#L492',
...
    'agent.addRequest': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L365',
+   'agent.createConnection': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L304',
-   'agent.createConnection': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L332',
    'agent.createSocket': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L446',
    'agent.destroy': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L679',
+   'agent.getName': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L334',
+   'agent.keepSocketAlive': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L635',
-   'agent.getName': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L515',
-   'agent.keepSocketAlive': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L484',
    'agent.removeSocket': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L574',
    'agent.reuseSocket': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L671',
    'assert.assert': 'https://github.com/{repository}/blob/HEAD/lib/assert.js#L185',
    'asyncResource.asyncId': 'https://github.com/{repository}/blob/HEAD/lib/async_hooks.js#L243',
    'asyncResource.bind': 'https://github.com/{repository}/blob/HEAD/lib/async_hooks.js#L275',
...
    'server.address': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2703',
+   'server.close': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L681',
+   'server.closeAllConnections': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L691',
+   'server.closeIdleConnections': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L703',
-   'server.close': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2836',
-   'server.closeAllConnections': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L124',
-   'server.closeIdleConnections': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L126',
    'server.getConnections': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2798',
    'server.listen': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2500',
    'server.ref': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2949',
+   'server.setTimeout': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L719',
-   'server.setTimeout': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L128',
    'server.unref': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2958',
+   'server[SymbolAsyncDispose]': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L687',
-   'server[SymbolAsyncDispose]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2876',
    'server[SymbolAsyncIterator]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2883',
    'server[kDeserialize]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2423',
    'server[kTransferList]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2396',
    'server[kTransfer]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2401',
+   'server[undefined]': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L726',
-   'server[undefined]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2913',
    'serverresponse._finish': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L254',
    'serverresponse._implicitHeader': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L411',
    'serverresponse.assignSocket': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L304',
    'serverresponse.detachSocket': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L315',
    'serverresponse.statusCode': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L277',

Performance estimate (single CI run)

  • Generation time: 7.2% slower (1.38 s → 1.48 s)
  • Peak memory: 2.8% higher (348.65 MB → 358.32 MB)

legacy-html Generator

Performance estimate (single CI run)

  • Generation time: 0.2% faster (16.28 s → 16.24 s)
  • Peak memory: 1.3% higher (2.33 GB → 2.36 GB)

legacy-json Generator

Performance estimate (single CI run)

  • Generation time: 24.5% faster (8.67 s → 6.55 s)
  • Peak memory: 7.1% higher (1.84 GB → 1.97 GB)

llms-txt Generator

Performance estimate (single CI run)

  • Generation time: 21.7% slower (6.44 s → 7.84 s)
  • Peak memory: 3.0% lower (1.86 GB → 1.80 GB)

orama-db Generator

Output size: 1 file changed · net -1.00 B

File size details
File Main PR Change
orama-db.json 9.26 MB 9.26 MB -1.00 B (-0.0%)

Performance estimate (single CI run)

  • Generation time: 18.0% faster (6.85 s → 5.62 s)
  • Peak memory: 2.4% higher (1.84 GB → 1.88 GB)

web Generator

Output size: 61 files changed · net +106.00 B

File size details
File Main PR Change
assets/SearchBox-Wv1oNrZ4.js 84.77 KB -84.77 KB (-100.0%)
assets/SearchBox-BL8dXwTL.js 84.77 KB +84.77 KB
assets/dist-ClZxiFIR.js 30.43 KB -30.43 KB (-100.0%)
assets/dist-CzAzC9z0.js 30.43 KB +30.43 KB
assets/SideBar-BxtRyUC0.js 25.62 KB -25.62 KB (-100.0%)
assets/SideBar-C7cAmuh_.js 25.62 KB +25.62 KB
assets/client-BnRwar35.js 21.61 KB -21.61 KB (-100.0%)
assets/client-BxHMcFum.js 21.61 KB +21.61 KB
assets/config-B5W8Tt3j.js 18.12 KB -18.12 KB (-100.0%)
assets/config-IkLyzSmT.js 18.12 KB +18.12 KB
assets/Combination-BoaLz8yJ.js 16.13 KB -16.13 KB (-100.0%)
assets/Combination-Zb_wbksL.js 16.13 KB +16.13 KB
assets/ThemeToggle-C9uyg7p4.js 13.63 KB -13.63 KB (-100.0%)
assets/ThemeToggle-HeB5GDg3.js 13.63 KB +13.63 KB
assets/dist-B8D6tfAu.js 10.20 KB -10.20 KB (-100.0%)
assets/dist-BTd5CAIn.js 10.20 KB +10.20 KB
assets/Layout-Bnj5_xHZ.js 10.13 KB -10.13 KB (-100.0%)
assets/Layout-CQlDKFf0.js 10.13 KB +10.13 KB
assets/compat-Cs2UD7dR.js 10.12 KB -10.12 KB (-100.0%)
assets/compat-Cad6KQmt.js 10.12 KB +10.12 KB
assets/Tooltip-CBOa1hvu.js 7.93 KB -7.93 KB (-100.0%)
assets/Tooltip-Cv_49yBV.js 7.93 KB +7.93 KB
assets/dist-BdgOfsvK.js 6.99 KB -6.99 KB (-100.0%)
assets/dist-DehGet4Y.js 6.99 KB +6.99 KB
assets/jsx-runtime-CQg_ZAC-.js 5.67 KB -5.67 KB (-100.0%)
assets/jsx-runtime-DvG5qREu.js 5.67 KB +5.67 KB
assets/dist-BL39aKkE.js 3.94 KB -3.94 KB (-100.0%)
assets/dist-Bgf0Jnzs.js 3.94 KB +3.94 KB
assets/CodeTabs-61XaHhAc.js 3.88 KB -3.88 KB (-100.0%)
assets/CodeTabs-DYc8I65q.js 3.88 KB +3.88 KB
assets/CodeBox-QEyvaIRE.js 3.44 KB -3.44 KB (-100.0%)
assets/CodeBox-DOtdsEwP.js 3.44 KB +3.44 KB
assets/hooks.module-B1PJ03KC.js 3.37 KB -3.37 KB (-100.0%)
assets/hooks.module-CYyVbMul.js 3.37 KB +3.37 KB
assets/FunctionSignature-D5ExS4CH.js 2.28 KB -2.28 KB (-100.0%)
assets/FunctionSignature-C1lpWimx.js 2.28 KB +2.28 KB
assets/Banner-BKhErV47.js 2.19 KB -2.19 KB (-100.0%)
assets/Banner-BK3FKvcZ.js 2.19 KB +2.19 KB
assets/ChangeHistory-DBGsX4Ax.js 1.75 KB -1.75 KB (-100.0%)
assets/ChangeHistory-AUxDHeXU.js 1.75 KB +1.75 KB
assets/DataTag-Ddiwy3uh.js 844.00 B -844.00 B (-100.0%)
assets/DataTag-DuDOJ4sg.js 844.00 B +844.00 B
assets/DocumentationIndex-Da_NytUD.js 833.00 B -833.00 B (-100.0%)
assets/DocumentationIndex-CorDpbbO.js 833.00 B +833.00 B
assets/ArrowUpRightIcon-CtDpVK61.js 618.00 B -618.00 B (-100.0%)
assets/ArrowUpRightIcon-BsMpTR54.js 618.00 B +618.00 B
assets/Badge-31CvtEtL.js 607.00 B -607.00 B (-100.0%)
assets/Badge-CgKXSSuQ.js 607.00 B +607.00 B
assets/AlertBox-C46fHV9T.js 591.00 B -591.00 B (-100.0%)
assets/AlertBox-CLWqAN63.js 591.00 B +591.00 B
assets/CodeBracketIcon-DvMuvH1Z.js 512.00 B -512.00 B (-100.0%)
assets/CodeBracketIcon-27W66IwF.js 512.00 B +512.00 B
assets/dist-DIQNjpog.js 477.00 B -477.00 B (-100.0%)
assets/dist-DQ-NNaqQ.js 477.00 B +477.00 B
assets/ChevronDownIcon-B4BCs7rb.js 468.00 B -468.00 B (-100.0%)
assets/ChevronDownIcon-A4vTqgoN.js 468.00 B +468.00 B
assets/Blockquote-DOJx39qt.js 167.00 B -167.00 B (-100.0%)
assets/Blockquote-BR5wxS3q.js 167.00 B +167.00 B
all.html 32.21 MB 32.22 MB +106.00 B (+0.0%)
assets/withIsland-ETxmTyke.js 105.00 B -105.00 B (-100.0%)
assets/withIsland-JV_aVfKY.js 105.00 B +105.00 B

Performance estimate (single CI run)

  • Generation time: 19.7% slower (69.00 s → 82.56 s)
  • Peak memory: 14.9% lower (6.11 GB → 5.20 GB)

@cloudflare-workers-and-pages

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Failed ❌

View logs ↗
7df595e 2026-09-02T22:39:07.083Z View logs ↗


// Where a version of the bundle's schema is published.
export const SCHEMA_URL =
'https://doc-kit.nodejs.org/schemas/api-doc-all/{schemaVersion}.json';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should generate that and give it with the generated artifact so a built can be served independently

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are welcome to export the artifact from the source code and host it independently, we also, by default, host our schema

@avivkeller

Copy link
Copy Markdown
Member Author

Bump @nodejs/web for reviews

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we have an other fixtures thats look like deprecation.

I use this script that read json so I would like to see output of such as file https://github.com/brunocroh/node-deprecations-pretty

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should include the ts file link

@ovflowd

ovflowd commented Sep 7, 2026

Copy link
Copy Markdown
Member

@avivkeller reviews on this will take a bit of time, this PR touchjes 68 files, it is... expected for this to... be complex to review 🙇

@avivkeller

Copy link
Copy Markdown
Member Author

Bump!

import { readFile } from 'node:fs/promises';
import { describe, it } from 'node:test';

import Ajv from 'ajv';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is a test-only (dev) dependency, right?

/**
* A heading that documents no API entry: prose, a deprecation, a command-line option.
*/
export type SectionNode = Entry &

@ovflowd ovflowd Sep 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use interface in these scenarios (SectionNode, ClassNode, ConstructionNode...), so we do use extends?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I know this is generated, but wondering why a generated schema lives on git hmmm)

// with no shallower heading before it) is treated as a child of it
const roots = buildHierarchy(entries);
const root = roots.find(node => node.entry === head) ?? roots[0];
const children = [

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: assign const for each one of these things instead of several assignment + spreads

introducedIn:
head.introduced_in == null ? null : String(head.introduced_in),
sourceLink: head.source_link
? {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: fn just for generating this sourceLink property

body: body.toSpliced(
index,
1,
...(rest.length ? [{ ...list, children: rest }] : [])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: avoid spread + assignment


if (
kind === 'class' &&
!items.some(item => extractListItem(item).prefix === 'Extends')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How many items can be in this Array? .some is not that performant...

* @param {Array<import('mdast').ListItem>} items The entry's typed list items
* @returns {{ properties: object, description?: string }}
*/
const kindProperties = (kind, entry, items) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if instead of a switch we could use a factory, might be overkill, but better specified and controled/standardized.

removed,
napiVersion,
changes,
...properties,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the spread happens before a speciifc item? It'd be good to know what could be accidentally overridden here and that it is fragile that it needs to be in a speciific position.

const [item] = items.map(extractListItem);

return item
? {

@ovflowd ovflowd Sep 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it might not sound practical, but could you remove the ternary and make it like below (easier to read)

if (item) {
 return { ... }
}

return { type: null, ... }

* @returns {import('../types').Type | null}
*/
export const toType = node =>
node

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here... this is the sort of situation I'd rather NOT use a ternary

export const DISPLAY_NAME = /displayName="([^"]*)"/;

// A rest parameter's marker
export const REST_MARKER = /^\.\.\./;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these RegEx's available elsewhere? Are these duplication of already existing RegEx's?

export const SECTION_KIND = 'section';

// The kinds whose leading typed list is lifted out of the body as data
export const KINDS_WITH_TYPED_LIST = new Set([

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also wondering if this is already defined elsewhere

const match = QUERIES.stabilityIndex.exec(text);
const start = match ? text.length - match[2].length : 0;

return slice(node, start, undefined, {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: instead .node -> const { node } =


return compile(schema, 'Document', {
bannerComment:
'/* eslint-disable */\n' +

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be a constant?

Comment thread package.json
"eslint-plugin-react-x": "5.18.1",
"globals": "~17.7.0",
"husky": "9.1.7",
"json-schema-to-typescript": "^16.0.0",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yet another package... At least it is a dev one, so Node.js consuming doc-kit won't use it right?

@ovflowd ovflowd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking really good. Left a few comments, I left out more specific nits, to let this move forward. If you could address my current comments I believe I'm good with a ✔️

@ovflowd

ovflowd commented Sep 19, 2026

Copy link
Copy Markdown
Member

Can you also rebase, @avivkeller?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New JSON generator schema

3 participants