Skip to content

dart_node_core: Wraps ~5% of Node.js APIs — no fs, no process, no streams, no HTTP client #44

@MelbourneDeveloper

Description

@MelbourneDeveloper

Summary

dart_node_core is a bootstrap shim, not a core library. It wraps roughly 5-10% of the Node.js APIs a real application needs. You cannot read files, access environment variables, exit gracefully, or make HTTP requests with typed Dart code.

What exists today

  • requireModule() — loads any Node module (returns untyped JSObject)
  • consoleLog() / consoleError() — console output
  • getGlobal() — read any global (untyped)
  • child_process.spawn() — with stdin/stdout/stderr streaming and exit codes
  • withRetry() — exponential backoff with Result types
  • FP utilities: match(), let()

What's missing — grouped by priority

P0: Cannot build any real Node.js app without these

Filesystem (fs)

None of these exist:

  • readFile() / writeFile() / readFileSync() / writeFileSync()
  • stat() / lstat() / exists()
  • readdir() / mkdir() / rmdir() / rm()
  • rename() / copyFile() / unlink()
  • watch() / watchFile()
  • createReadStream() / createWriteStream()

Impact: You literally cannot read or write files from typed Dart code. Must drop to raw requireModule('fs') and use untyped JS interop.

Process (process)

None of these exist:

  • process.env — no typed access to environment variables
  • process.argv — no typed command-line args
  • process.cwd() — no current working directory
  • process.exit() — no way to exit gracefully
  • process.on('SIGTERM') / process.on('SIGINT') — no signal handling for graceful shutdown
  • process.pid / process.ppid / process.platform / process.arch
  • process.stdin / process.stdout / process.stderr (typed)

Impact: A production server cannot read config from env vars, handle shutdown signals, or exit cleanly.

Path (path)

None of these exist:

  • path.join() / path.resolve() / path.dirname() / path.basename()
  • path.extname() / path.parse() / path.format()
  • path.isAbsolute() / path.relative() / path.normalize()
  • path.sep / path.delimiter

Impact: Must use raw JS interop for every file path operation.

P1: Needed for real-world servers and tools

HTTP client

No way to make outbound HTTP requests from Dart:

  • http.request() / http.get()
  • https.request() / https.get()
  • Or wrap fetch() (available in Node 18+)

Impact: A server that calls external APIs (auth services, payment providers, microservices) cannot do so from typed Dart code.

Streams and Buffer

  • Stream / Readable / Writable / Transform / Duplex — no typed streaming
  • Buffer — no binary data handling
  • pipeline() — no stream composition

Impact: Cannot handle large data, file uploads, or streaming responses.

Timers (typed wrappers)

  • setTimeout() / clearTimeout()
  • setInterval() / clearInterval()
  • setImmediate() / process.nextTick()

Dart timers work but don't integrate with Node.js's event loop semantics.

P2: Important for specific use cases

  • crypto — hashing, HMAC, random bytes, encryption
  • os — hostname, platform, memory, cpus, tmpdir, homedir
  • url — URL parsing (Node's URL class)
  • querystring — query string parsing
  • dns — DNS resolution
  • net — TCP sockets
  • tls — TLS/SSL sockets
  • events — typed EventEmitter
  • util — promisify, inspect, types

Bug: Busy-wait retry blocks the event loop

File: packages/dart_node_core/lib/src/retry.dart

The withRetry() function uses a busy-wait loop for delays:

while (DateTime.now().millisecondsSinceEpoch < end) {
  // Spins in a tight loop, blocking the entire Node.js event loop
}

Impact: In a multi-request server, if ANY request triggers a retry, ALL concurrent requests freeze until the retry delay completes. This is fundamentally incompatible with Node.js's single-threaded async model.

Fix: Replace with await Future.delayed(Duration(...)) or use Node's setTimeout via JS interop.

Test gaps

File: packages/dart_node_core/test/dart_node_core_test.dart

  • child_process.spawn()ZERO tests despite being the most complex code in the package
  • requireModule — only checks it returns a JSObject, not that the module is usable
  • withRetry — properly tested (5 tests, solid)
  • Extensions (match, let) — tested but trivial

Suggested implementation order

  1. process.env + process.exit() + process.cwd() — smallest surface, biggest impact
  2. path module — small API, used everywhere
  3. fs — async versions first (readFile, writeFile, readdir, stat, mkdir, rm)
  4. Fix busy-wait retry — one-line fix, removes a production crash risk
  5. child_process tests — already implemented, just untested
  6. HTTP client — wrap fetch() since it's built into Node 18+
  7. Streams/Buffer — foundational for fs streams and HTTP

Files to modify

  • packages/dart_node_core/lib/src/ — add new files: fs.dart, process.dart, path.dart, fetch.dart, buffer.dart
  • packages/dart_node_core/lib/dart_node_core.dart — export new modules
  • packages/dart_node_core/lib/src/retry.dart — fix busy-wait loop
  • packages/dart_node_core/test/ — add tests for each new module + child_process tests

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions