Unverified Commit 6c8df000 authored by Hong Minhee's avatar Hong Minhee
Browse files

Fix JSR publishing hang for @fedify/testing

Fixed JSR publishing hanging indefinitely at the processing stage by
hiding complex type exports from the public API. The JSR type analyzer
struggled with complex type dependencies when analyzing MockFederation,
TestFederation, TestContext, and SentActivity types.

Changes:

- MockFederation class is no longer exported from public API
- TestFederation, TestContext, SentActivity interfaces are not exported
- Updated all tests to use createFederation() factory function
- Updated documentation to remove type imports and use type inference
- Types are still available through TypeScript type inference

This approach minimizes the public API surface that JSR needs to analyze
while maintaining full type safety for users.

https://github.com/fedify-dev/fedify/issues/468



Co-Authored-By: default avatarClaude <noreply@anthropic.com>
parent add825af
Loading
Loading
Loading
Loading
+10 −17
Original line number Diff line number Diff line
@@ -11,24 +11,17 @@ To be released.
### @fedify/testing

 -  Fixed JSR publishing hanging indefinitely at the *processing* stage by
    refactoring the public API to use TypeScript utility types (`Omit`, `Pick`)
    instead of direct class exports.  The JSR type analyzer struggled with
    complex type dependencies when exporting the `MockFederation` class
    directly, causing indefinite hangs during the processing stage.  [[#468]]

     -  Added `TestContext<TContextData>` interface that extends `Context` with
        testing utilities using `Omit<Context, "clone">` and
        `Pick<RequestContext, ...>`.  This ensures automatic inheritance of new
        methods added to `Context` or `RequestContext` while maintaining JSR
        compatibility.
     -  Added `TestFederation<TContextData>` interface that extends `Federation`
        with testing utilities using `Omit<Federation, "createContext">`.
     -  Added `createFederation()` factory function that returns
        `TestFederation<TContextData>` for creating mock federation instances.
     -  Exported `SentActivity` interface for tracking sent activities.
    hiding complex type exports from the public API.  The JSR type analyzer
    struggled with complex type dependencies when analyzing the `MockFederation`,
    `TestFederation`, `TestContext`, and `SentActivity` types, causing indefinite
    hangs during the processing stage.  [[#468]]

     -  **Breaking change**: `MockFederation` class is no longer exported from
        the public API.  Use `createFederation()` instead, which returns a
        `TestFederation` instance with the same functionality.
        the public API.  Use `createFederation()` factory function instead.
     -  `TestFederation<TContextData>`, `TestContext<TContextData>`, and
        `SentActivity` interfaces are no longer exported from the public API,
        but their types are still inferred from `createFederation()` return type
        and can be used via TypeScript's type inference.

### @fedify/cli

+4 −4
Original line number Diff line number Diff line
@@ -200,11 +200,11 @@ interface that allows you to:
Here's a basic example of using `createFederation()`:

~~~~ typescript twoslash
import { createFederation, type TestFederation } from "@fedify/testing";
import { createFederation } from "@fedify/testing";
import { Create, Note } from "@fedify/fedify/vocab";

// Create a mock federation with context data
const federation: TestFederation<{ userId: string }> = createFederation<{ userId: string }>({
const federation = createFederation<{ userId: string }>({
  contextData: { userId: "test-user" }
});

@@ -239,12 +239,12 @@ which provides a mock implementation of the `Context` interface that tracks
sent activities and provides mock implementations of URI generation methods:

~~~~ typescript twoslash
import { createFederation, type TestContext } from "@fedify/testing";
import { createFederation } from "@fedify/testing";
import { Create, Note, Person } from "@fedify/fedify/vocab";

// Create a mock federation and context
const federation = createFederation<{ userId: string }>();
const context: TestContext<{ userId: string }> = federation.createContext(
const context = federation.createContext(
  new URL("https://example.com"),
  { userId: "test-user" }
);
+10 −10
Original line number Diff line number Diff line
@@ -2,10 +2,10 @@ import type { InboxContext } from "@fedify/fedify/federation";
import { Create, Note, Person } from "@fedify/fedify/vocab";
import { assertEquals, assertRejects } from "@std/assert";
import { test } from "../../fedify/src/testing/mod.ts";
import { MockFederation } from "./mock.ts";
import { createFederation } from "./mock.ts";

test("getSentActivities returns sent activities", async () => {
  const mockFederation = new MockFederation<void>();
  const mockFederation = createFederation<void>();
  const context = mockFederation.createContext(
    new URL("https://example.com"),
    undefined,
@@ -36,7 +36,7 @@ test("getSentActivities returns sent activities", async () => {
});

test("reset clears sent activities", async () => {
  const mockFederation = new MockFederation<void>();
  const mockFederation = createFederation<void>();
  const context = mockFederation.createContext(
    new URL("https://example.com"),
    undefined,
@@ -67,7 +67,7 @@ test("reset clears sent activities", async () => {

test("receiveActivity triggers inbox listeners", async () => {
  // Provide contextData through constructor
  const mockFederation = new MockFederation<{ test: string }>({
  const mockFederation = createFederation<{ test: string }>({
    contextData: { test: "data" },
  });
  let receivedActivity: Create | null = null;
@@ -100,7 +100,7 @@ test("receiveActivity triggers inbox listeners", async () => {
});

test("MockContext tracks sent activities", async () => {
  const mockFederation = new MockFederation<void>();
  const mockFederation = createFederation<void>();
  const mockContext = mockFederation.createContext(
    new URL("https://example.com"),
    undefined,
@@ -129,7 +129,7 @@ test("MockContext tracks sent activities", async () => {
});

test("MockContext URI methods should work correctly", () => {
  const mockFederation = new MockFederation<void>();
  const mockFederation = createFederation<void>();
  const mockContext = mockFederation.createContext(
    new URL("https://example.com"),
    undefined,
@@ -167,7 +167,7 @@ test("MockContext URI methods should work correctly", () => {
});

test("MockContext URI methods respect registered paths", () => {
  const mockFederation = new MockFederation<void>();
  const mockFederation = createFederation<void>();

  // Register custom paths with dummy dispatchers
  mockFederation.setNodeInfoDispatcher("/.well-known/nodeinfo", () => ({
@@ -257,7 +257,7 @@ test("MockContext URI methods respect registered paths", () => {
});

test("receiveActivity throws error when contextData not initialized", async () => {
  const mockFederation = new MockFederation<void>();
  const mockFederation = createFederation<void>();

  // Set up an inbox listener without initializing contextData
  mockFederation
@@ -281,7 +281,7 @@ test("receiveActivity throws error when contextData not initialized", async () =
});

test("MockFederation distinguishes between immediate and queued activities", async () => {
  const mockFederation = new MockFederation<void>();
  const mockFederation = createFederation<void>();

  // Start the queue to enable queued sending
  await mockFederation.startQueue(undefined);
@@ -329,7 +329,7 @@ test("MockFederation distinguishes between immediate and queued activities", asy
});

test("MockFederation without queue sends all activities immediately", async () => {
  const mockFederation = new MockFederation<void>();
  const mockFederation = createFederation<void>();

  const context = mockFederation.createContext(
    new URL("https://example.com"),
+4 −4
Original line number Diff line number Diff line
@@ -69,7 +69,7 @@ function expandUriTemplate(
 * Represents a sent activity with metadata about how it was sent.
 * @since 1.8.0
 */
export interface SentActivity {
interface SentActivity {
  /** Whether the activity was queued or sent immediately. */
  queued: boolean;
  /** Which queue was used (if queued). */
@@ -85,7 +85,7 @@ export interface SentActivity {
 * Extends the standard Context interface with additional testing utilities.
 * @since 1.9.1
 */
export interface TestContext<TContextData>
interface TestContext<TContextData>
  extends
    Omit<Context<TContextData>, "clone">,
    Pick<
@@ -116,7 +116,7 @@ export interface TestContext<TContextData>
 * Extends the standard Federation interface with additional testing utilities.
 * @since 1.9.1
 */
export interface TestFederation<TContextData>
interface TestFederation<TContextData>
  extends Omit<Federation<TContextData>, "createContext"> {
  // Test-specific properties
  sentActivities: SentActivity[];
@@ -167,7 +167,7 @@ export interface TestFederation<TContextData>
 * @template TContextData The context data to pass to the {@link Context}.
 * @since 1.8.0
 */
export class MockFederation<TContextData> implements Federation<TContextData> {
class MockFederation<TContextData> implements Federation<TContextData> {
  public sentActivities: SentActivity[] = [];
  public queueStarted = false;
  private activeQueues: Set<"inbox" | "outbox" | "fanout"> = new Set();
+0 −3
Original line number Diff line number Diff line
@@ -28,7 +28,4 @@ export {
  createFederation,
  createInboxContext,
  createRequestContext,
  type SentActivity,
  type TestContext,
  type TestFederation,
} from "./mock.ts";