Skip to main content

nodejs

Write and review Node.js: the module system, package manifest, streams, event loop, process and worker lifecycle, the permission model, native TypeScript execution, `node:test`, and the runtime version `engines.node` permits.

Informations de source

Dépôt
xobotyi/cc-foundry
Dernière activité de la source
13 octobre 2026 à 09:31
Langue détectée de SKILL.md
anglais
Étoiles
22
Forks
0

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Explorateur de fichiers
17 fichiers

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
nodejs
description
Write and review Node.js: the module system, package manifest, streams, event loop, process and worker lifecycle, the permission model, native TypeScript execution, `node:test`, and the runtime version `engines.node` permits.
when_to_use
Invoke whenever Node.js runtime code is touched at all — writing, reviewing, refactoring, or debugging a server, a CLI, a script, or a `package.json`, and whenever a project's Node floor is raised. Also invoke on the symptoms: `ERR_REQUIRE_ESM`, `ERR_REQUIRE_ASYNC_MODULE`, or `ERR_MODULE_NOT_FOUND`; an import that resolves in the bundler and not in Node; a `.ts` file Node refuses to run; a process that hangs at exit or dies with no stack; a stream that buffers without bound; a leaked HTTP connection; `ERR_ACCESS_DENIED`; a `node:test` mock or fake timer that does not take effect; or a flag that works on one Node major and not the next. Covers the Node runtime and `node:test`; JavaScript semantics belong to the javascript skill, types to typescript, the Bun runtime to bun, and Vitest to vitest.
compatibility
Uses Claude Code frontmatter beyond the Agent Skills spec (when_to_use)
One thread runs every callback in a Node process, so anything that holds it holds the whole program. Three biases decide most calls: - **The `engines.node` floor decides what may be written**, not the `node` binary on the machine. - **Read an API's stability index before depending on it.** Node ships an API for years before committing to it, and an experimental one can be deleted without a deprecation cycle. - **Check the standard library before adding a dependency.** It absorbed `fetch`, a test runner, a file watcher, `glob`, SQLite, `.env` parsing, a script runner, and TypeScript execution. ## Node Version `engines.node` in `package.json` is the floor a project claims. Read it before writing code and never reach for an API above it. Nothing enforces it at run time — Node never reads the field, and npm checks it only under `engine-strict=true` — so the floor holds by discipline and by CI running the oldest major it names. **The installed binary is not the floor.** `node --version` reports what one machine has, not what the project supports. Support windows, which decide whether a floor is defensible: - **20** — end of life 2026-04-30. A floor of `>=20` claims a runtime that receives no fixes at all. - **22** — maintenance from 2025-10-21, end of life 2027-04-30. Security and critical fixes only; no new capability. - **24** — Active LTS from 2025-10-28, maintenance from 2026-10-20, end of life 2028-04-30. - **26** — released 2026-05-05, Active LTS from 2026-10-28, end of life 2029-04-30. Floor version per feature — the APIs and behavior changes that decide what may be written. - **20.19.0** — `require(esm)` unflagged on the 20 line; syntax detection on by default - **22.0.0** — `node --run`; `fs.glob`; `WebSocket` global; watch mode Stable; `assert { type: 'json' }` removed - **22.12.0** — `require(esm)` unflagged on the 22 line; JSON modules Stable - **22.13.0** — Permission Model Stable and `--permission` accepted; `node:sqlite` unflagged - **22.15.0** — `module.registerHooks()` - **22.16.0** — `import.meta.dirname` and `import.meta.filename` Stable; `--experimental-config-file` - **22.18.0** — type stripping on by default; `import.meta.main` - **22.21.0** — `--env-file` Stable; `NODE_USE_ENV_PROXY` - **24.0.0** — `--experimental-permission` renamed `--permission`; `AsyncLocalStorage` on `AsyncContextFrame`; `URLPattern` global; test runner awaits subtests; `url.parse()` runtime-deprecated (DEP0169); `args` with `{ shell: true }` runtime-deprecated (DEP0190) - **24.12.0** — type stripping Stable - **24.20.0** — `--permission-audit` - **26.0.0** — `Temporal` global; `module.register()` runtime-deprecated (DEP0205); `--experimental-transform-types` removed; `node:_stream_*` removed. It also inherits what 25 added and neither 22 nor 24 received: `--allow-net`, and with it network confinement under `--permission` - **26.3.0** — `permission.drop()` **No odd major is a defensible floor.** Node 21, 23, and 25 are end of life — 25 since 2026-06-01 — so a feature that arrived on one is reachable only from the LTS major above it. Read [`${CLAUDE_SKILL_DIR}/references/versions/node-NN.md`] — one file per LTS major: `node-20.md`, `node-22.md`, `node-24.md`, `node-26.md` — when writing against a feature near the floor, and whenever raising the floor. Each carries what its major added, the stability each addition holds, the behavior it changed, and the traps it introduced. ## Stability Index Every documented API carries one, and it decides how the API may be used, not merely how finished it is. - **2 - Stable** — protected by semver. Safe in a published library's surface. - **1 - Experimental** — outside semver, and the level most unfinished APIs print. Three subdivisions refine it: **1.2 - Release candidate**, where no further breaking change is anticipated but none is ruled out; **1.1 - Active development**, nearing minimum viability; **1.0 - Early development**, unfinished and expecting substantial change. - **3 - Legacy** — semver-protected and unmaintained. Bugs in it are not fixed. Migrate rather than report. - **0 - Deprecated** — carries a DEP number and a migration. What follows from the index: - **An experimental API can be removed with no deprecation cycle.** `--experimental-transform-types` shipped in 22.7.0 and was deleted in 26.0.0, taking the runtime path for TypeScript enums with it. - **Never put an experimental API in a published library's public surface.** A consumer cannot see that the risk was taken on their behalf. - **The index moves per major.** `TracingChannel` is Stable from 26.8.0 and experimental on 24; `module.register()` is supported on 24 and runtime-deprecated on 26. State the index for the floor, not for the machine. - **Check the index rather than the flag.** Type stripping needs no flag from 22.18.0 and was not Stable until 24.12.0; an unflagged feature is not a committed one. ## Module System - **Declare `"type"` in every `package.json`.** Without it, a `.js` or extensionless file is parsed as CommonJS and re-parsed as an ES module when that fails. The retry costs startup time on every ES module and turns a real syntax error into a confusing module-system error. - **`.mjs` and `.cjs` override `"type"`; nothing overrides them.** Use them to place one file of the other kind inside a package, never as the default. - **Node's resolver performs no extension search and no directory-index lookup.** It loads the specifier as written, so `./startup` resolves in a bundler and throws `ERR_MODULE_NOT_FOUND` here. - **Import a builtin through `node:`** — `import fs from 'node:fs/promises'`. The prefix cannot be shadowed by a package of the same name, and `node:test` and `node:sqlite` resolve under no other specifier. - **`import.meta.dirname` and `import.meta.filename` replace `__dirname` and `__filename`**, and exist only on `file:` modules. - **The `assert` import-attribute form was removed in 22.0.0 and throws.** Only `with` parses. - **`require()` of an ES module works** from 20.19.0 and 22.12.0, and throws `ERR_REQUIRE_ASYNC_MODULE` when anything in the graph uses top-level `await`. That is a property of the whole graph, so a transitive dependency can break a caller that did not change. - **Reach for `module.registerHooks()`, not `module.register()`.** The latter is runtime-deprecated in 26.0.0 (DEP0205) and needs `--allow-worker` under the Permission Model, because its hooks run on a worker thread. Read [`${CLAUDE_SKILL_DIR}/references/conventions/modules.md`] when a specifier resolves in a bundler and not in Node, when writing a loader hook, or when repeated start-up cost is worth caching — it carries what decides a file's module system, the specifier rules, the two hook APIs, and the compile cache. ## Package Manifest - **`"exports"` seals the package.** Once present, nothing outside the map is importable, deep relative paths included. Add it deliberately: it is the only way to keep internals private, and adding it to a published package is a breaking change. - **Condition order in the map is the match order.** Earlier keys win, so write the most specific first. - **`"default"` goes last and `"types"` first.** `"default"` matches every caller, and `"types"` is a convention of the type systems rather than a condition Node reads. - **`"import"` and `"require"` describe the caller's syntax, not the target's format.** A `.mjs` file behind `"require"` is legal and loads through `require(esm)`. - **Use `"module-sync"` instead of a dual build** where one ES module must serve both callers. Two builds of one package give a process two class identities and two copies of every module-level singleton. - **`"imports"` keys start with `#`** and are the only path-alias mechanism that survives `node file.ts`, because nothing in the runtime reads `tsconfig.json`. - **Every `"exports"` target starts with `./`.** Absolute paths, `file:` URLs, `../`, and any `.`, `..`, or `node_modules` segment are rejected with `ERR_INVALID_PACKAGE_TARGET`. Read [`${CLAUDE_SKILL_DIR}/references/conventions/modules.md`] when designing an `"exports"` map or publishing a package that must serve both `require` and `import` — it carries the condition-matching rules, the target-path restrictions, and the dual-package hazard. ## TypeScript Execution Node erases type syntax and runs the result. It does not compile, does not type-check, and does not read `tsconfig.json`. - **Keep `tsc --noEmit` in the verification path.** Running a `.ts` file proves it parses, nothing more. - **`import type` is mandatory for type-only imports.** Erasure is syntactic: without the keyword the import survives as a value import and fails at run time. `verbatimModuleSyntax: true` makes `tsc` enforce it. - **Write the real extension in the specifier** — `import './x.ts'`, and `require('./x.ts')` in CommonJS. Node loads the file that exists; it does not rewrite `.js` to `.ts`. - **Enums, `namespace` with runtime code, parameter properties, and import aliases raise `ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`.** `--experimental-transform-types` handled some of them and was removed in 26.0.0. Set `erasableSyntaxOnly: true` so the compiler rejects them first. - **A decorator fails as a bare `SyntaxError` instead**, with no `code` to match on — measured on 26.2.0. The parser rejects the `@` before type stripping runs. - **A dependency shipping `.ts` files cannot be loaded.** Node refuses TypeScript under any `node_modules` path, deliberately and at every version. Read [`${CLAUDE_SKILL_DIR}/references/conventions/typescript-execution.md`] when a `.ts` file fails to run, when choosing between type stripping and a compiler, or when writing the `tsconfig.json` that matches the runtime — it carries the flag history, the full refused-syntax list, and the matching compiler options. ## Blocking - **No synchronous file, process, or crypto call on a request path.** `readFileSync`, `execSync`, `pbkdf2Sync`, `inflateSync` belong in CLIs, build scripts, and startup code that runs before the server listens. - **Bound every input before parsing it.** `JSON.parse` is synchronous and scales with input size, so an unbounded body is a denial of service that needs no bug. - **Never match a backtracking pattern against user input.** Nested quantifiers, overlapping alternations, and backreferences with repetition give one request the whole thread. Use `indexOf` or a linear engine. - **Recursive `process.nextTick` starves I/O.** The tick queue drains to exhaustion before the loop advances, so a self-scheduling `nextTick` stops the process serving traffic while looking idle. `queueMicrotask` has the same property one level down. - **`setImmediate` beats `setTimeout(fn, 0)` inside an I/O callback and races it at the top level.** Never encode either ordering as a dependency. - **Partition long synchronous loops** with `setImmediate` between chunks, or move them to a worker. Read [`${CLAUDE_SKILL_DIR}/references/conventions/concurrency.md`] when a process stops serving traffic while looking idle, or when one slow operation surfaces as an unrelated failure — it carries the loop phase order and the list of work that shares the libuv thread pool. ## Streams - **Compose with `pipeline` from `node:stream/promises`.** A `.pipe()` chain propagates neither errors nor destruction, so one failure leaves the rest of the chain open with its descriptors held. - **Respect the return value of `write()`.** `false` means the buffer is over `highWaterMark`; keep writing and memory grows without any error being raised. `pipeline` and `for await` handle this. - **`highWaterMark` is a threshold, not a cap.** A single write larger than it still buffers in full, and in object mode the count is objects rather than bytes. - **Destroy a stream on the error path.** `stream.destroy(err)` releases the descriptor or socket; an undestroyed stream leaks it. - **An unhandled `'error'` event crashes the process.** Every stream outside a `pipeline` needs a listener. - **`readable.map`, `filter`, `take`, `reduce` and the rest are Experimental**, on every major through 26. They read like committed API and are not. Keep them out of a library's surface. - **Do not stream data already in memory.** Streams earn their cost on unbounded or arriving-over-time input. Read [`${CLAUDE_SKILL_DIR}/references/conventions/streams.md`] when crossing between Node streams and web streams, when writing a custom `Readable`, `Writable`, or `Transform`, or when tuning `highWaterMark` — it carries the interop matrix, the platform-dependent defaults, and the whole-stream consumers. ## Errors and Exit - **Set `process.exitCode` and let the loop drain.** `process.exit()` terminates immediately, and a write to a pipe or a socket may still be in flight — measured on 26.2.0/macOS, 512 KiB written to a pipe arrives as 64 KiB. A write to a file is synchronous and survives, which is why the loss reads as intermittent. - **Match on `error.code`, never on the message.** Every built-in error carries one; messages change between minors. - **`util.inspect` renders a `cause` chain**, so an error chained with `{ cause: err }` reaches a Node logger intact without the caller unwrapping it. - **Never resume after `'uncaughtException'`.** Installing the listener disables the crash and leaves the process running with unknown state. Log, flush, exit. - **An unhandled rejection is fatal by default.** Do not add `--unhandled-rejections=warn` to make a test suite pass. - **`'exit'` listeners run synchronously only.** Anything asynchronous scheduled there never runs. - **Cancel with `AbortSignal`.** `fetch`, `fs/promises`, `timers/promises`, `pipeline`, `stream.addAbortSignal`, and `events.on` all take `{ signal }`. - **A cancelled call does not report itself the same way twice.** Node's own APIs reject with an `AbortError` carrying `code: 'ABORT_ERR'`; `fetch` rejects with a `DOMException` whose `code` is the number `20`; and a `fetch` deadline built from `AbortSignal.timeout(ms)` rejects with `name === 'TimeoutError'`, not `'AbortError'` — measured on 26.2.0. Test `signal.aborted` where the signal is in scope, and `err.name === 'AbortError' || err.name === 'TimeoutError'` where it is not. - **Set the HTTP server timeouts.** `requestTimeout` defaults to 300000 ms and `keepAliveTimeout` to 5000 ms; the latter must be shorter than the idle timeout of any proxy in front, or the proxy reuses a socket Node has closed. Read [`${CLAUDE_SKILL_DIR}/references/conventions/lifecycle.md`] when writing a graceful shutdown, when a process refuses to exit or exits without flushing, or when auditing server timeouts — it carries the shutdown sequence, the deprecation flags, and the timeout defaults. ## Async Context and Diagnostics - **`run(store, fn)` is the only Stable way to enter a store.** `enterWith` and `withScope` are Experimental, and `enterWith` applies for the rest of the current synchronous execution rather than for a scope — called inside one event handler it leaks into every later handler on the same emit. - **Instrument with `diagnostics_channel`, not with a logger call.** Publishing to a channel with no subscriber costs nothing measurable, so the instrumentation can ship enabled. - **Create a channel once at module top level** and guard expensive payload construction with `channel.hasSubscribers`. - **Subscribers receive the message object by reference.** A subscriber that mutates it changes what the publisher and later subscribers see. - **Measure loop lag with `perf_hooks.monitorEventLoopDelay()`**, not with a `setTimeout` drift estimate. - **Get a stack out of a native crash with `--report-on-fatalerror`.** No JavaScript handler runs at that point. Read [`${CLAUDE_SKILL_DIR}/references/conventions/observability.md`] when context goes missing across an async boundary, when adding tracing to a library, or when picking a profiling flag — it carries the context-loss diagnosis, the `TracingChannel` contract, and the list of channels Node publishes itself. ## HTTP Client - **Consume or cancel every response body**, on the error path too. undici does not release the connection until the body is read or cancelled, and the garbage collector is not prompt enough to save it — a pool exhausted this way stalls rather than errors. - **`fetch` does not reject on a non-2xx status.** Check `res.ok`. - **`fetch` has no total timeout.** Pass `signal: AbortSignal.timeout(ms)`, combined with the caller's signal through `AbortSignal.any`. - **`http.Agent` options do not reach `fetch`.** It uses undici's global dispatcher; change it with a `dispatcher` option per request or `setGlobalDispatcher` process-wide. - **`fetch` ignores `HTTP_PROXY` unless told otherwise** — `NODE_USE_ENV_PROXY=1` or `--use-env-proxy`, from 22.21.0 and 24.5.0.
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub