npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nxtedition/app

v3.0.3

Published

Application bootstrap and the standard middleware stack used by nxtedition services (`createDefaultApp`).

Readme

@nxtedition/app

Application bootstrap and the standard middleware stack used by nxtedition services (createDefaultApp).

Configuration schemas

createDefaultApp accepts either the existing defaults object or a Zod 4 object schema. Defaults objects remain useful for simple config: their keys and nested shape form an implicit runtime schema.

Use Zod when config needs precise initialization validation, required values, constraints, or transforms. Zod's output type is inferred as app.config, and field-level Zod defaults are applied after environment and command-line config has been collected:

import { createDefaultApp } from '@nxtedition/app'
import * as z from 'zod'

const configSchema = z.object({
  host: z.string().min(1).default('localhost'),
  port: z.coerce.number().int().positive().default(8000),
  metrics: z
    .object({
      enabled: z.union([z.boolean(), z.stringbool()]).default(true),
    })
    .prefault({}),
})

const app = createDefaultApp({
  name: 'example',
  config: configSchema,
})

app.run((app) => {
  app.logger.info({ port: app.config.port }, 'configured')
})

Environment parsing and coercion

Zod mode deliberately gives Zod ownership of parsing. Environment values for schema-declared keys bypass nconf's parseValues; this applies both to top-level values and to nested leaves formed with the __ separator:

environment text -> nconf precedence/nesting -> Zod parse -> typed app.config

For the introductory schema above, the flow is:

| Environment input | Value received by Zod | app.config output | | -------------------------------- | --------------------------- | ------------------------------------- | | port=9000 | port: "9000" | port: 9000 | | metrics__enabled=false | metrics.enabled: "false" | metrics.enabled: false | | port and metrics are omitted | both fields are undefined | port: 8000, metrics.enabled: true |

z.coerce.number() applies JavaScript's Number(input) and then runs the chained number checks. Thus "1e3" becomes 1000; an empty or whitespace-only string becomes 0, which the example's .positive() rejects. Coercion is intentionally broad, so constrain the input first when only strings and already-parsed numbers are acceptable. The real-service example below constrains its string-or-number union before .pipe(...), so empty strings and values such as booleans are rejected before numeric coercion.

Use z.stringbool() for environment booleans. It maps strings such as "true", "1", "yes", and "on" to true, and "false", "0", "no", and "off" to false. Do not use z.coerce.boolean() for this: it calls Boolean(input), so every non-empty string, including "false", becomes true. The union with z.boolean() in the example also accepts an actual boolean already produced by nconf's command-line argument parser.

default(value) applies only when the input is undefined; an invalid explicit value still fails validation. A default also returns immediately without parsing the default value. By contrast, prefault(value) parses its value. The metrics.prefault({}) above therefore passes {} through the nested object so enabled.default(true) can run. A metrics.default({ enabled: true }) would instead require a complete output value and return it without running child parsing.

Plain z.string() preserves environment text. JSON object and array blobs also remain strings, so z.object() and z.array() do not implicitly call JSON.parse. Prefer nested __ keys, or add a guarded z.preprocess() or a JSON codec when a JSON blob is intentional. Command-line values retain the types produced by nconf's argument parser. Built-in config keys not declared by the Zod schema retain the legacy parseValues behavior.

nxt-23.1.x migration reference

The asset-indexer config from nxt-23.1.x is a useful migration example: it is a real service with nested URL settings, integers, and several booleans. Its service-owned config can be expressed as a Zod schema so environment overrides are parsed and validated during initialization:

import * as z from 'zod'

const isProduction = process.env.NODE_ENV === 'production'

// Environment input is string; argv input may already be number. Reject other
// input types before applying Number(...), then validate the numeric output.
const configInteger = (min = 1, max = Number.MAX_SAFE_INTEGER) =>
  z.union([z.string().trim().min(1), z.number()]).pipe(z.coerce.number().int().min(min).max(max))

const configBoolean = z.union([z.boolean(), z.stringbool()])

const serviceUrl = (production: string, development: string) =>
  z.url().prefault(isProduction ? production : development)

const assetIndexerConfigSchema = z.object({
  elasticsearch: z
    .object({
      url: z.url().prefault('http://127.0.0.1:9200'),
    })
    .prefault({}),
  http: z
    .object({
      port: configInteger(1, 65_535).default(38_703),
    })
    .prefault({}),
  statePath: z.string().min(1).default('./.nxt'),
  couchdb: z
    .object({
      url: serviceUrl('http://tasks.deepstream:6100/nxt/', 'http://127.0.0.1:6100/nxt/'),
    })
    .prefault({}),
  ollama: z
    .object({
      url: serviceUrl('http://ollama:11434', 'http://127.0.0.1:11434'),
      model: z.string().min(1).default('mxbai-embed-large'),
      tokenizer: z.string().min(1).default('mixedbread-ai/mxbai-embed-large-v1'),
      maxTokens: configInteger(3).default(512),
      chunkOverlap: configInteger(0).default(48),
    })
    .refine(({ maxTokens, chunkOverlap }) => chunkOverlap < maxTokens - 2, {
      message: 'chunkOverlap must be less than maxTokens - 2',
      path: ['chunkOverlap'],
    })
    .prefault({}),
  query: z
    .object({
      url: serviceUrl('http://tasks.query:6500/nxt/', 'http://127.0.0.1:6500/nxt/'),
    })
    .prefault({}),
  trace: z
    .object({
      url: serviceUrl('http://elasticsearch-log:9200', 'http://127.0.0.1:9200'),
    })
    .prefault({}),
  remote: serviceUrl('http://proxy:8888', 'http://127.0.0.1:8888'),
  legacy: configBoolean.default(false),
  purge: configBoolean.default(true),
  resolve: configBoolean.default(true),
  index: configBoolean.default(true),
  seed: configBoolean.default(true),
})

For example, ollama__maxTokens=768 legacy=false reaches the schema as { ollama: { maxTokens: "768" }, legacy: "false" }. The inferred output has ollama.maxTokens: number with value 768 and legacy: boolean with value false, so downstream code does not need to cast them. z.url() validates the URL strings without changing their type; using .prefault(url) also validates the configured URL defaults. Each .prefault({}) lets nested field defaults run when its whole section is omitted. The Ollama object refinement keeps its token-chunking stride positive by requiring chunkOverlap < maxTokens - 2.

The source's standard deepstream credential and monitor wiring are omitted from the excerpt so it stays focused on service-owned configuration. In a full migration, the monitor wiring stays unchanged; either pass the credential via createDefaultApp's deepstream option or declare it in the schema.

The schema root must be z.object() or z.strictObject(). Root-level wrappers, loose objects, and catchalls are not supported; loose/catchall behavior remains available on nested objects. Only declared top-level keys are passed to Zod, so unrelated process environment variables do not break strict schemas. The standard logger, deepstream, numa, dailyOffpeakTime, and http keys keep their legacy defaults-based behavior unless the Zod schema explicitly declares them.

Parsing is synchronous during app initialization. Invalid config fails app initialization with a ZodError; configMiddleware surfaces it directly, while createDefaultApp().run() applies its normal exitOnError handling. Schemas with asynchronous refinements or transforms are not supported. Zod-owned proxy branches remain open after this one-time validation, so nested unknown-key access diagnostics come from Zod at initialization rather than from later proxy reads; top-level diagnostics and sensitive-value redaction remain active.

When explicitly supplying the Records and RpcMethods generic arguments, also pass typeof configSchema as the third argument so TypeScript can retain the schema output type:

createDefaultApp<Records, RpcMethods, typeof configSchema>({
  name: 'example',
  config: configSchema,
})

The lower-level configMiddleware follows the same two forms:

configMiddleware({ port: 8000 })
configMiddleware(z.object({ port: z.coerce.number().default(8000) }))

Default ports

Several middlewares open their own HTTP server. Unless a port is passed explicitly (via the middleware options or config.http.port for http), these defaults apply. Ports suffixed with + threadId mean one server per worker thread (the main thread is threadId 0).

| Middleware | Production port | Dev port | Scope | Purpose | | ------------- | ----------------------------- | ------------------ | ---------------- | --------------------------------------------------------------------------------------------- | | http | 8000 | off unless set | main app server | Application HTTP server — /healthcheck plus user middleware. | | inspect | 38603 (loopback) | ephemeral (0) | main thread only | V8 inspector control plane: lists worker threads, opens/closes --inspect per thread. | | utils | 38604 (loopback) | ephemeral (0) | main thread only | Ops endpoints: GET /stats, GET /status, POST /gc, POST /writeHeapSnapshot. | | monitor | 60000 + threadId (loopback) | 60000 + threadId | every thread | App stats/status: GET /stats (aggregated across threads on the main thread), GET /status. | | diagnostics | 50000 + threadId (loopback) | 50000 + threadId | every thread | Native profiling & health endpoints — see below. Runs in dev and production. |

Notes:

  • These are internal/diagnostic servers, expected to sit behind the container/network boundary — they are unauthenticated. utils, monitor, diagnostics and inspect default to loopback (127.0.0.1) since they expose stats/state or debugger control; http binds all interfaces. Do not route any of them publicly. Set inspect.host explicitly (for example, to 0.0.0.0) only when the debugger control plane intentionally needs a broader bind address.
  • inspect and utils run only on the main thread but coordinate every thread over a BroadcastChannel. monitor and diagnostics run per thread so each V8 isolate is reachable; for monitor, the main thread's /stats returns the aggregated app stats (all threads), while each worker's /stats returns its own.

diagnostics middleware

Native, dependency-free profiling and health endpoints (uses node:inspector, node:v8, node:perf_hooks, process.report). One server per thread on 50000 + threadId; the main thread (:50000) also aggregates the whole fleet.

| Endpoint | Cost | Returns | | ------------------------------- | -------------- | -------------------------------------------------------------------------------------------- | | GET / | none | Endpoint directory + this thread's identity. | | GET /fleet | low | Live rss/heapUsed/ELU/event-loop-delay for every thread. Start here. | | GET /workers | none | Thread roster — names, threadIds and each thread's diagnostics url. | | GET /stats | low | This thread: memory, V8 heap stats, resource usage, event-loop delay/utilization. | | GET /report | low | This thread: process.report — JS + native stacks, libuv handles, env. | | GET /cpu?seconds=10 | moderate | This thread: CPU profile (.cpuprofile, opens in Chrome DevTools). | | GET /heap-sampling?seconds=10 | low | This thread: allocation sampling profile (.heapprofile). | | GET /heap | stop-world | This thread: full heap snapshot (.heapsnapshot), streamed to the client. Use deliberately. |

A thread whose event loop is blocked will not answer /fleet in time and is reported as ok: false (unresponsive within timeout) — usually the very thread worth profiling. From /fleet, find the busy/leaking thread and its port, then hit that port's /cpu, /heap-sampling, or /report.

Options

createDefaultApp({
  // ...
  diagnostics: {
    port: 50000, // optional; defaults to 50000 + threadId
    host: '127.0.0.1', // optional; loopback by default. Set '0.0.0.0' to expose on all interfaces.
  },
})

Pass diagnostics: false to disable.

Off-peak state

createDefaultApp exposes a live, read-only app.isOffPeak boolean for the local-time window configured by dailyOffpeakTime (default 01:00-04:00) and app.isOffPeak$ as its observable equivalent. The observable emits the current state immediately and follows both window changes and nxt:offPeak broadcasts. Both values are false when the window is disabled. The standard event-loop monitor and toobusy middleware discard lag recorded during this window so expected maintenance pauses do not affect health after off-peak ends.

monitor middleware

Aggregates app health — event-loop lag, memory, deepstream record counts, undici/http pending, per-thread stats, plus any user-supplied stats/status — and exposes it over HTTP (one server per thread on 60000 + threadId), in addition to the existing deepstream monitor.stats/monitor.status records.

| Endpoint | Returns | | ------------- | ----------------------------------------------------------------------------------------- | | GET /stats | Main thread: aggregated AppStats (includes a threads[] array). Worker: its own stats. | | GET /status | { messages: [...] } — the current health/status messages. |

createDefaultApp({
  // ...
  monitor: {
    port: 60000, // optional; defaults to 60000 + threadId
    host: '127.0.0.1', // optional; loopback by default. Set '0.0.0.0' to expose on all interfaces.
    // stats / status: custom providers (see MonitorOptions)
  },
})

Pass monitor: false to disable.