Skip to content

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

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

Activity

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

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