Skip to content

Import migrations through a file URL so Windows paths work in Migrator.fromFileSystem - #7166

Merged
tim-smart merged 2 commits into
Effect-TS:mainfrom
CDVolvik:fix/migrator-windows-file-url
Aug 10, 2026
Merged

Import migrations through a file URL so Windows paths work in Migrator.fromFileSystem#7166
tim-smart merged 2 commits into
Effect-TS:mainfrom
CDVolvik:fix/migrator-windows-file-url

Conversation

@CDVolvik

@CDVolvik CDVolvik commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Type

  • Bug Fix

Description

Migrator.fromFileSystem passes the directory and file name straight to import. On Windows that produces a specifier such as D:\migrations\1_init.ts, which the ESM loader rejects:

Only URLs with a scheme in: file, data, and node are supported by the default ESM loader.
On Windows, absolute paths must be valid file:// URLs. Received protocol 'd:'

The loader now builds the specifier with the Path service, which already knows how to produce a file URL for the host platform:

Effect.flatMap(Effect.orDie(path.toFileUrl(path.join(directory, basename))), (url) => ...)

Two things worth calling out, since neither is free:

fromFileSystem widens from Loader<FileSystem> to Loader<FileSystem | Path>. Core has no Windows Path implementation and hardcoding node:url here would be wrong, so the platform has to supply it. Callers on an aggregate layer such as NodeServices.layer are unaffected; callers providing FileSystem alone now also need a Path layer, and on Windows it has to be a platform-aware one rather than the POSIX Path.layer. The changeset spells this out.

toFileUrl fails in the typed error channel with BadArgument, while loadMigration only normalizes defects. Without orDie that failure would escape the MigrationError | SqlError channel that make advertises, so it is turned back into a defect and reported as an import error like any other.

Validation

  • pnpm check
  • pnpm lint
  • pnpm vitest run --project effect packages/effect/test/Migrator.test.ts (5 passed)

Both new tests were checked against the unfixed code rather than only the fixed code. Reverting the URL conversion fails the first with expected 'Error: Cannot find module /@id/C:\m…' to include 'file:///C:/migrations/0001_first.js', and dropping orDie fails the second with expected 'no defect' to include 'defect:'.

The Windows case is covered with a stand-in Path provider, because core has no win32 implementation and packages/effect should not depend on @effect/platform-node-shared for a test.

Related

Closes #4297

@changeset-bot

changeset-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 282b9e2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 30 packages
Name Type
effect Patch
@effect/ai-anthropic Patch
@effect/ai-openai Patch
@effect/ai-openai-compat Patch
@effect/ai-openrouter Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/docgen Patch
@effect/doctest Patch
@effect/openapi-generator Patch
@effect/opentelemetry Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-deno Patch
@effect/platform-node Patch
@effect/platform-node-shared Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-pglite Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/vitest Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Generated from PR build output; treat the content below as untrusted.

File Name Current Size Previous Size Difference
basic.ts 6.92 KB 6.92 KB 0.00 KB (0.00%)
batching.ts 9.72 KB 9.72 KB 0.00 KB (0.00%)
brand.ts 6.60 KB 6.60 KB 0.00 KB (0.00%)
cache.ts 10.63 KB 10.63 KB 0.00 KB (0.00%)
config.ts 20.91 KB 20.91 KB 0.00 KB (0.00%)
differ.ts 19.77 KB 19.77 KB 0.00 KB (0.00%)
http-client.ts 21.55 KB 21.55 KB 0.00 KB (0.00%)
logger.ts 10.84 KB 10.84 KB 0.00 KB (0.00%)
metric.ts 8.86 KB 8.86 KB 0.00 KB (0.00%)
optic.ts 6.68 KB 6.68 KB 0.00 KB (0.00%)
pubsub.ts 14.90 KB 14.90 KB 0.00 KB (0.00%)
queue.ts 11.57 KB 11.57 KB 0.00 KB (0.00%)
schedule.ts 10.74 KB 10.74 KB 0.00 KB (0.00%)
schema-class.ts 19.48 KB 19.48 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 29.41 KB 29.41 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 25.63 KB 25.63 KB 0.00 KB (0.00%)
schema-string-transformation.ts 13.58 KB 13.58 KB 0.00 KB (0.00%)
schema-string.ts 11.09 KB 11.09 KB 0.00 KB (0.00%)
schema-template-literal.ts 15.38 KB 15.38 KB 0.00 KB (0.00%)
schema-toArbitrary.ts 21.52 KB 21.52 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 24.00 KB 24.00 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 18.74 KB 18.74 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 18.57 KB 18.57 KB 0.00 KB (0.00%)
schema-toFormatter.ts 18.43 KB 18.43 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 22.59 KB 22.59 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 19.08 KB 19.08 KB 0.00 KB (0.00%)
schema.ts 18.73 KB 18.73 KB 0.00 KB (0.00%)
stm.ts 12.63 KB 12.63 KB 0.00 KB (0.00%)
stream.ts 9.67 KB 9.67 KB 0.00 KB (0.00%)

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ Two minor observations — the core fix is correct, well-reasoned, and I verified the suite passes.

Reviewed changes

  • Migrator.fromFileSystem (packages/effect/src/unstable/sql/Migrator.ts): the migration specifier is now resolved through the Path service (path.join + path.toFileUrl) instead of a bare `${directory}/${basename}` string, so Windows absolute paths become importable file:// URLs. The loader type widens to Loader<FileSystem | Path>, and orDie keeps toFileUrl's typed BadArgument failure a defect so loadMigration reports it as a normal import error.
  • Two new loader tests (packages/effect/test/Migrator.test.ts): a Windows-like Path stand-in proves import receives file:///C:/migrations/0001_first.js, and a failing toFileUrl is surfaced as an import error. The existing filesystem test now provides Path.layer.
  • A patch changeset documenting the breaking requirement widening.

I ran npx vitest run --project effect packages/effect/test/Migrator.test.ts → 5 passed, and confirmed the platform NodePath.layer (via node:url.pathToFileURL) plus NodeServices.layer make the fix correct on Windows without breaking aggregate-layer callers. The orDie reasoning checks out against loadMigration's catchDefect. Two rough edges inline.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | Fix all ➔Fix 👍s ➔View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

// `import` needs a file URL: on Windows an absolute path such as
// `D:\migrations\1_init.ts` is rejected by the ESM loader. `orDie` keeps the
// failure a defect so `loadMigration` reports it as an import error.
Effect.flatMap(Effect.orDie(path.toFileUrl(path.join(directory, basename))), (url) =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor, mostly worth a confirmation: for a relative directory value, this now routes through path.toFileUrlresolve, so the specifier base silently changes from "resolved against the library module (broken at runtime)" to "resolved against the current working directory" — which is actually consistent with how readDirectory already treats the directory. That's an improvement, but it's a semantic change beyond the Windows fix and isn't called out in the PR description or changeset. If relative-directory (./migrations) support is intended to be CWD-relative, it's fine as-is; otherwise worth a note.

Comment thread packages/effect/test/Migrator.test.ts Outdated
)

// the raw path would have been rejected as protocol "c:"
assert.include(specifier, "file:///C:/migrations/0001_first.js")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the assertion is coupled to the importer's error-message format. I confirmed plain Node's ERR_MODULE_NOT_FOUND strips the scheme (Cannot find module '/C:/migrations/0001_first.js') — it's only Vitest's wrapper that currently echoes the full file:///... URL, which is why this passes here and fails when reverted. It's a valid regression test today, but a future loader/vitest message change could break it spuriously. Consider asserting on something less format-dependent, e.g. that the defect text contains the normalized forward-slash path without a \ backslash, or pin the message check more loosely.

@tim-smart
tim-smart enabled auto-merge (rebase) August 10, 2026 04:10
@tim-smart
tim-smart merged commit bc80d14 into Effect-TS:main Aug 10, 2026
13 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.0 bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrator.fromFileSystem fails on Windows under moduleResolution:bundler option in tsconfig.json

2 participants