@data-fair/lib-node

中文文档
3.0.0发布于 2026/10/29MITES Module近 30 天下载 2006

Small nodejs related utility functions for the data-fair stack.

原始使用文档
文档目录9 节
  1. Install
  2. Modules
  3. Outgoing HTTP requests and SSRF
  4. Logging
  5. Implicit context
  6. OpenTelemetry
  7. Migration
  8. Observer
  9. Upgrade scripts

@data-fair/lib-node

Server-side Node.js utilities shared by the services of the data-fair stack (data-fair, processings, events, catalogs…): HTTP clients protected against SSRF, Prometheus observability, MongoDB helpers, upgrade scripts, websocket emitter and client.

Install

npm i @data-fair/lib-node

Peer dependencies, all declared optional: install the ones required by the modules you import.

Peer dependencyNeeded by
prom-client@15observer.js, http-agents.js and everything built on them (axios.js, events-queue.js…); without it log.js does not count log lines
pino-pretty@13pretty output of log.js in dev (falls back to JSON without it)
mongodb@6mongo.js, locks.js, upgrade-scripts.js
ws@8ws-client.js
tough-cookie@5axios-with-cookies.js, axios-auth.js

There is no root entry point: import each module by its path.

Modules

Import pathDescription
@data-fair/lib-node/axios.jsAxios instances with shorter error stack traces and a data-fair User-Agent: axiosInstance (default export, public), privateAxiosInstance, axiosBuilder().
@data-fair/lib-node/http-agents.jsKeepalive HTTP(S) agents with cached DNS lookups and Prometheus metrics: httpAgent / httpsAgent (public, SSRF protected), privateHttpAgent / privateHttpsAgent.
@data-fair/lib-node/ssrf.jsSSRF checks for clients that don't use the agents above: resolvePublicAddress(), publicLookup, SsrfError.
@data-fair/lib-node/axios-with-cookies.jsAxios instance with a tough-cookie cookie jar.
@data-fair/lib-node/axios-auth.jsAxios instance logged into simple-directory with email/password. Integration tests only.
@data-fair/lib-node/log.jsThe logger of the stack: JSON lines on stdout (pretty in dev), implicit context, metrics, debug output redirected.
@data-fair/lib-node/observer.jsMini web server exposing Prometheus metrics and CPU/heap profiles.
@data-fair/lib-node/mongo.jsMongo connection wrapper (default export: a singleton) with declarative named index management (configure()).
@data-fair/lib-node/locks.jsDistributed locks stored in a MongoDB locks collection with a TTL (default export: a singleton).
@data-fair/lib-node/upgrade-scripts.jsRunner for the database migration scripts of a service.
@data-fair/lib-node/events-queue.jsQueue of events and notifications pushed to the events service.
@data-fair/lib-node/session.jsSessionHandler: reads and verifies simple-directory session cookies (JWKS). Used by @data-fair/lib-express/session.js and Nuxt server code.
@data-fair/lib-node/ws-emitter.jsPublishes websocket messages through a capped MongoDB collection, read by @data-fair/lib-express/ws-server.js.
@data-fair/lib-node/ws-client.jsWebsocket client with auto-reconnect and channel subscriptions (WsClient, DataFairWsClient).
@data-fair/lib-node/fs.jsensureDir(), emptyDir(), exists().
@data-fair/lib-node/validation.jsRe-export of @data-fair/lib-validation.
@data-fair/lib-node/test-spies.jsTest helpers: turns // @test:spy(...) comments into events that tests can waitFor().

For sessions see the data-fair-session skill, for websockets the data-fair-ws skill.

Outgoing HTTP requests and SSRF

The default axios instance and the public agents refuse to connect to non public addresses (loopback, private ranges, link-local including the cloud metadata service, etc.). Use them for every URL that a user can influence.

privateAxiosInstance and the private agents have no such restriction. Use them only for URLs that come from the configuration of the service (other services of the same infrastructure).

import axios, { privateAxiosInstance } from '@data-fair/lib-node/axios.js'

await axios.get(userProvidedUrl)
await privateAxiosInstance.get(config.privateDirectoryUrl + '/api/...')
  • SSRF_PUBLIC_IPS: comma separated IPs / CIDR ranges to consider public, for example an intranet data source of a self-hosted instance, or 127.0.0.1,::1 in a development environment.
  • SSRF_PRIVATE_IPS: comma separated IPs / CIDR ranges to consider private even if they are public addresses, for example the database servers.

If HTTP_PROXY, HTTPS_PROXY or ALL_PROXY (or their lowercase forms) is defined, the protection of the agents is disabled (they connect to the proxy, not to the target) and a warning is logged at startup.

For clients that don't use these agents (ftp, sftp, raw sockets, SDKs…), @data-fair/lib-node/ssrf.js exports resolvePublicAddress(host) (checks every address of the host and returns the one to connect to) and publicLookup (a dns.lookup compatible function for any lookup option). They apply the same rules and are not disabled by a proxy env variable. publicLookup is not called for IP literals: check those with resolvePublicAddress.

See the data-fair-security skill for the stack-wide rules.

Logging

One logger for everything, imported where needed, never passed around:

import log, { withLogContext } from '@data-fair/lib-node/log.js'

log.info('dataset finalized', { datasetId })
log.info('memory deleted', { code: 'agents.memory.delete' })
log.warn('forbidden request', { code: 'forbidden-req', err })
log.alert('admin api key created', { code: 'df.apikeys.createadmin' })
log.error('failed to create mongo index', { code: 'mongo-index-fail', err, collection })

log.init({ service: 'data-fair' }) // optional, defaults to OTEL_SERVICE_NAME

Every method has the same signature (msg, fields?) and never throws. Do not use the keys written by pino (level, time, msg, hostname) as field names, they would be duplicated in the JSON line.

LevelUseEmittedCounted
debugthrough the debug module (see below)only namespaces enabled by DEBUGno
infonormal operations, audit trailalwaysno
warnclient errors, suspicious but expected situationsalwaysyes
alertevents that are not failures but deserve the same scrutiny as errors (mostly security)alwaysyes
errorinternal failures only, never client errorsalwaysyes

Output: one JSON object per line on stdout when NODE_ENV=production, pretty output otherwise (LOG_FORMAT=json|pretty overrides). Fields: time, level (label), msg, hostname, service, code, err, the context fields, the per-call fields, and trace_id / span_id / trace_flags when OpenTelemetry is active.

Codes: code is optional on every line. It must be a static string (mongo-index-fail, df.apikeys.createadmin): never put ids, hosts or other dynamic values in it, they go in other fields. It labels the counters.

Errors: pass them as the err field. They are serialized with a whitelist (type, message, stack, primitive code, status, cause, and method / url without query string for axios errors), so headers, request config and document content carried by errors never reach the logs. Messages are truncated to 1000 characters (the message of an AxiosRequestError starts with the response body).

Metrics: warn, alert and error lines increment df_logs{level, code, host}. Deprecated, kept during the transition: df_internal_error{errorCode} (error lines) and df_events_log{level, code, host} (coded warn and alert lines).

Debug: keep using the debug module (import Debug from 'debug'), DEBUG=ns1,ns2* works as before. When log.js is loaded, the output of the shared debug module is redirected into the logger as debug lines with a ns field. A third-party library bundling its own copy of debug is not redirected.

Implicit context

The fields of the current context are added to every line. session.middleware() of @data-fair/lib-express opens a context per request with ip, host, userId, userName, accountKey, accountDep, accountName. Any code can add fields for a scope:

await withLogContext({ datasetId }, async () => {
  // every line logged in here, even deep in the call stack, carries datasetId
})

Rules:

  • only small immutable values (ids, keys, short names), never req, documents or buffers: they stay in memory as long as the scope is alive,
  • create long-lived resources (pools, intervals, caches, queues) at startup, outside any scope, or they capture it and attribute their later lines to the wrong user,
  • event listeners run in the context of the code that emits, not of the code that subscribed.

Worker threads have their own context: pass getLogContext() in the task payload and wrap the task with withLogContext(payload.logContext, ...) in the worker.

OpenTelemetry

Nothing to do in the code. Activate the OpenTelemetry Operator auto-instrumentation on the Kubernetes side: its pino instrumentation adds trace_id, span_id and trace_flags to the lines, and the traceparent header is propagated on outgoing HTTP calls (third parties included). Set OTEL_LOGS_EXPORTER=none so that logs are not also shipped over OTLP. Without the operator (dev, tests, scripts) lines have no trace fields.

Migration

BeforeAfter
eventsLog.info(code, msg, ctx)log.info(msg, { code })
eventsLog.warn(code, msg, ctx)log.warn(msg, { code })
eventsLog.alert(code, msg, ctx)log.alert(msg, { code })
internalError(code, err)log.error(msg, { code, err })
console.log / console.warn / console.errorlog.info / log.warn / log.error with a code
EVENTS_LOG_LEVELremoved, every level but debug is emitted
import Debug from 'debug'unchanged

User, account, ip and host of eventsLog's context argument now come from the implicit context.

Observer

A very light web server for observability, on port 9090 by default:

  • GET /metrics: Prometheus metrics of this process,
  • GET /service-metrics (or /global-metrics): metrics registered in servicePromRegistry,
  • GET /cpu-profile, /heap-profile, /heap-profile-all (optional ?duration= in ms, default 2000): sampled profiles,
  • GET /heap-snapshot: full retained heap snapshot (blocks the event loop while V8 walks the heap).
import { startObserver, stopObserver } from '@data-fair/lib-node/observer.js'

await startObserver() // or startObserver(port)
// ...
await stopObserver()

Process metrics are plain prom-client metrics. A service-level metric (its value depends on a shared state, like the database, not on the activity of the current process) is registered in servicePromRegistry:

import { Gauge } from 'prom-client'
import { servicePromRegistry } from '@data-fair/lib-node/observer.js'

new Gauge({
  name: 'df_my_gauge',
  help: '...',
  registers: [servicePromRegistry],
  async collect () {
    this.set(await db.collection('my-collection').estimatedDocumentCount())
  }
})

Upgrade scripts

Scripts live in upgrade/<last-released-version>/ directories of the service. The folder name must be the version of the last release of the service at the time the script is written, never an anticipated future version.

At startup the runner reads the version stored in the services Mongo collection, runs every script whose folder version is >= that stored version, then stores the current package.json version. So a script runs once on the production upgrade and re-runs on every staging deploy until the next release ships.

Scripts must be idempotent: each script should run once, but this cannot be strictly ensured (concurrent pods, crashes, manual re-runs, misnamed folders), so running it again must be a no-op, not a failure.

A script is a module whose default export follows the UpgradeScript interface:

import type { UpgradeScript } from '@data-fair/lib-node/upgrade-scripts.js'

export default {
  description: 'Add a default status to items',
  async exec (db, debug) {
    await db.collection('items').updateMany({ status: { $exists: false } }, { $set: { status: 'active' } })
  }
} as UpgradeScript

Run them during service initialization (the runner takes the upgrade lock, so only one process runs them):

import upgradeScripts from '@data-fair/lib-node/upgrade-scripts.js'
import locks from '@data-fair/lib-node/locks.js'

await locks.start(db)
await upgradeScripts(db, locks) // optional: basePath = './', isFresh callback

The upgrade-scripts skill covers the folder-naming rule, idempotency patterns and fresh installs in detail.

版本与发布记录

@data-fair/lib-node 共发布 55 个版本,首次发布于 2024/10/08;过去 12 个月发布了 17 个版本。

版本发布日期距今
3.0.0latest2026/10/2916 天前
2.17.12026/10/2025 天前
2.17.02026/10/1926 天前
2.16.12026/10/1629 天前
2.16.02026/10/1629 天前
2.15.02026/10/051 个月前

关于 @data-fair/lib-node 的常见问题

@data-fair/lib-node 是什么?

Small nodejs related utility functions for the data-fair stack.

如何安装 @data-fair/lib-node?

在项目目录运行 npm install @data-fair/lib-node,或使用 pnpm add / yarn add / bun add。

@data-fair/lib-node 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/data-fair__lib-node 是否存在。

@data-fair/lib-node 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@data-fair/lib-node 可以商用吗?

@data-fair/lib-node 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@data-fair/lib-node 的下载量有多少?

根据 npm 官方下载统计,@data-fair/lib-node 在 2026/10/12 – 2026/11/10 这 30 天内被下载 2,006 次,日均约 67 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@data-fair/lib-node 还在维护吗?

最新版本 3.0.0 发布于 2026/10/29(16 天前);至今共发布 55 个版本,其中过去 12 个月发布了 17 个;首次发布于 2024/10/08。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。