Skip to content

Conversation

@lvcabral
Copy link
Contributor

@lvcabral lvcabral commented Dec 19, 2025

ZenFS Sync Configuration Enhancements

Overview

This PR adds full support for configuring ZenFS backends in purely synchronous JavaScript runtimes. Historically, mounting or reconfiguring a backend required awaiting configure() or configureSingle(). We now expose synchronous equivalents, enforce deterministic readiness across file systems, and document/test the new flow end-to-end.


Key Additions

  • Synchronous configuration APIs

    • configureSync() mirrors configure() and accepts the same shape. It validates that every backend involved is synchronously constructible and mounted.
    • configureSingleSync() mirrors configureSingle() for the common single-mount use case.
    • resolveMountConfigSync() performs backend resolution without promises and rejects configurations that would require async work (e.g., async isAvailable() checks or create() implementations that return promises).
  • FileSystem readiness contract

    • New helper ensureReadySync() lives in src/internal/filesystem.ts. It invokes an optional readySync() hook on file systems and throws when the instance can only be prepared asynchronously.
    • StoreFS, CopyOnWriteFS, and the Mutexed mixin now implement readySync(), unlocking synchronous initialization for every backend layered on top of them (e.g., InMemory, SingleBuffer, and CoW compositions).
  • Synchronous mounting utilities

    • mountWithMkdirSync() mirrors the async helper, ensuring mount points exist (creating directories synchronously when missing) before mount() is invoked.
    • Device injection honors synchronous semantics. When addDevices is enabled, the DeviceFS instance is initialized via ensureReadySync().

Updated Backends & Mixins

  • InMemory / SingleBuffer: both still wrap StoreFS, so once StoreFS.readySync() became available the backends worked automatically with the new APIs.
  • CopyOnWrite: now ensures both readable and writable layers are synchronously ready.
  • Mutexed: delegates readySync() to the wrapped file system, keeping mutex-wrapped instances compatible with synchronous configuration paths.

Developer Guidance

  1. Prefer configureSync() / configureSingleSync() only when your environment cannot await promises (e.g., kernel bootstrap or embedders that expect immediate mounting).
  2. Backends that perform network IO, rely on async isAvailable() checks, or defer initialization will throw ENOTSUP when used with the sync APIs. Stick with the async configuration path in those scenarios.
  3. When authoring new backends:
    • Keep create() synchronous if you want compatibility with configureSync().
    • If extra initialization time is needed, implement both ready() and readySync() (the latter should throw when truly impossible).
    • Ensure any nested mount configuration that might be synchronous also calls ensureReadySync() once constructed.

Testing & Documentation

  • Added tests/common/config.test.ts covering configureSync, configureSingleSync, and backend compatibility (including rejection of async backends).
  • Extended documentation/configuration.md with examples for configureSync() / configureSingleSync() and guidance on using resolveMountConfigSync().

These improvements collectively let synchronous runtimes opt into ZenFS without rewriting application initialization, while keeping asynchronous setups unchanged.

@lvcabral lvcabral requested a review from james-pre as a code owner December 19, 2025 18:39
@james-pre james-pre linked an issue Dec 19, 2025 that may be closed by this pull request
@james-pre
Copy link
Member

Hey @lvcabral, I think that supporting synchronous initialization is a really good idea.

The approach you present is solid, though I worry about the maintainability of it. To that end, my thinking for how this would be implemented:

  • We could change ready() (and any dependent functions, like resolveMountConfig) to allows returning a non-promise.
  • configure and configureSync would get a new option, e.g. synchronous: boolean.
  • We can then use constant types to enforce that backends do not return a Promise when synchronous is true, resulting in a Typescript error if trying to use an async-only backend

Essentially configure({ synchronous: true }) would have the same effect as you are proposing.

This would probably take a little more effort, however I think it will deduplicate code and not lead to a mess of functions.

@lvcabral
Copy link
Contributor Author

lvcabral commented Dec 19, 2025

@james-pre Please check the latest commit if that accomplishes your suggestions.

Hey @lvcabral, I think that supporting synchronous initialization is a really good idea.

The approach you present is solid, though I worry about the maintainability of it. To that end, my thinking for how this would be implemented:

  • We could change ready() (and any dependent functions, like resolveMountConfig) to allows returning a non-promise.
  • configure and configureSync would get a new option, e.g. synchronous: boolean.
  • We can then use constant types to enforce that backends do not return a Promise when synchronous is true, resulting in a Typescript error if trying to use an async-only backend

Essentially configure({ synchronous: true }) would have the same effect as you are proposing.

This would probably take a little more effort, however I think it will deduplicate code and not lead to a mess of functions.

@james-pre
Copy link
Member

My primary concern is making sure the feature is maintainable. The two best options I see are:

  1. Completely separate syncronous and asynchronous code paths

This is the initial content of the PR. Briefly looking over the code, it looks good.

  1. Completely unified code paths

This is what I intended in my last comment. The idea is that instead of having two functions to handle sync and async, we do the logic in once place. The goal with this one is deduplicating the same logic paths.

Both approaches are satisfactory, I just think we should try for the second one because it would have a reducded maintenance burden.

I'll look into whether having unified logic is practical. For now I think we should keep this PR having separate logic, since approach 2 would likely involve a drasticly different implementation. Reverting the most recent commit would help keep the two approaches separate.

Please be patient; it may take me a while to do more comprehensive analysis and review. Thanks again for the PR.

@lvcabral
Copy link
Contributor Author

@james-pre ahh got what you mean, not sure if that would be possible (or easy). I reverted last commit.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Synchronous configuration

2 participants