Skip to content

Consider replacing @js-temporal/polyfill with temporal-polyfill to align with native Temporal types #823

Description

@dahlia

Fedify currently uses Temporal extensively, while supporting runtimes where Temporal is not yet universally available. To support Bun and older supported Node.js versions, Fedify depends on @js-temporal/polyfill.

This has become increasingly awkward now that modern runtimes and TypeScript have native Temporal declarations. In particular, the types exported by @js-temporal/polyfill are distinct from the ambient Temporal namespace provided by TypeScript's lib.esnext.temporal.d.ts. This can lead to nominal/structural incompatibilities between values created by the polyfill and APIs typed against ambient Temporal types.

Fedify already added @fedify/vocab-runtime/temporal and switched several public declarations toward ambient Temporal types to reduce this problem, but the current setup still leaves room for type mismatches.

There is another Temporal polyfill, temporal-polyfill, which appears to be designed around TypeScript's native Temporal declarations. It re-exports types from temporal-spec, which is derived from TypeScript's Temporal lib declarations, and its default entrypoint prefers the host's native Temporal implementation when available.

A quick investigation suggests this may fit Fedify's direction better than @js-temporal/polyfill.

Relevant observations:

  • @js-temporal/polyfill values are not directly assignable to ambient Temporal.Duration/Temporal.Instant under TypeScript 6.0's Temporal lib declarations. For example, Duration.round() and Instant.until() signatures differ.
  • temporal-polyfill values are assignable to ambient Temporal types when using bundler-style module resolution.
  • temporal-polyfill works in Deno via npm:temporal-polyfill@1.0.1.
  • temporal-polyfill works in Bun via ESM import, and Bun currently still needs a polyfill because globalThis.Temporal is not available there.
  • temporal-polyfill is currently ESM-only from the package export-map perspective. require("temporal-polyfill") fails because the package does not expose a require, module-sync, or default condition for the root export.
  • Fedify currently emits CJS builds that synchronously inject Temporal via require("@js-temporal/polyfill"), so a direct replacement would break CJS builds.

Because of the CJS issue, this should not be a simple dependency rename. A safer direction may be:

  1. Replace ESM/Deno Temporal imports with temporal-polyfill.
  2. For CJS builds, bundle temporal-polyfill into the emitted output instead of leaving require("temporal-polyfill") as an external dependency.
  3. Keep @fedify/vocab-runtime/temporal guards, since Fedify should continue accepting Temporal values from native implementations, @js-temporal/polyfill, temporal-polyfill, and other spec-conformant implementations.
  4. Add cross-runtime tests covering Node.js, Bun, and Deno.
  5. Add consumer type tests for at least moduleResolution: "Bundler" and moduleResolution: "NodeNext".

Things to verify before making the switch:

  • CJS output runs on the minimum supported Node.js version.
  • ESM output runs on Node.js 22+, Node.js 26+, Deno, and Bun.
  • Declarations do not leak temporal-polyfill-specific types.
  • APIs accepting Temporal.Duration, Temporal.DurationLike, Temporal.Instant, and generated vocabulary date/duration fields continue to type-check with native Temporal values.
  • Task payload serialization/deserialization continues to round-trip Temporal values through devalue.
  • Behavior remains compatible for malformed date/time and duration parsing, especially where Fedify currently relies on Temporal.Instant.from() and Temporal.Duration.from() throwing RangeError.
  • Documentation and changelog entries clearly explain that Fedify uses native Temporal where available and a polyfill where needed.

This issue is for investigating and, if feasible, replacing @js-temporal/polyfill with temporal-polyfill in a way that reduces Temporal type conflicts without regressing Fedify's supported runtime matrix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Fields

    Priority

    Medium

    Effort

    Medium

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions