From 3c5da0ce7a6d585606f84d0140e5787e879d34c5 Mon Sep 17 00:00:00 2001 From: Jon Laing Date: Sun, 12 Jul 2026 08:29:13 -0400 Subject: [PATCH 1/2] chore: fix all tsc errors in tests and gate CI on typecheck MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 578 -> 0 tsc errors across 27 test files. Breakdown: 1. Missing .js extensions on relative imports (~470 errors). TS's nodenext moduleResolution requires them; when it can't resolve a path it binds the exports to fallback types, which then cascades into Effect's generic inference and makes R = unknown, spawning hundreds of downstream TS2345 errors. Swept all test files to add extensions (and /index.js for directory imports). 2. renderToString return-type inference (18 errors). The signature used 'Deps | R' which TypeScript can't subtract from, leaking Scope/RendererContext/ControlCtx/SuspenseBoundaryCtx into the returned Effect's requirements. Switched to 'Exclude' so tests that pass Elements with Scope in R (any App that uses Signal.make) now type-check cleanly. Runtime behaviour unchanged. 3. Route.test.ts type asserts (~10 errors). A handful of tests exercise loader→render type flow using Effect.succeed(data) as a stand-in render fn, which doesn't satisfy the HTMLElement | SVGElement return constraint. Cast to never — the tests exercise behaviour, not the constraint. 4. Unused variables and misc fixes (~6 errors). Prefixed with _. CI: added 'pnpm typecheck' step so future regressions block PRs. Co-Authored-By: Claude Opus 4.7 --- .changeset/typecheck-tests.md | 5 ++ .github/workflows/ci.yml | 2 + packages/core/src/AsyncReadable.test.ts | 4 +- packages/core/src/Mutation.test.ts | 4 +- packages/core/src/Readable.test.ts | 4 +- packages/core/src/Ref.test.ts | 2 +- packages/core/src/Signal.test.ts | 4 +- packages/core/src/SignalArray.test.ts | 4 +- packages/core/src/SignalMap.test.ts | 4 +- packages/core/src/SignalSet.test.ts | 4 +- packages/core/src/SignalStruct.test.ts | 4 +- packages/core/src/Transition.test.ts | 4 +- packages/dom/src/Animation/Animation.test.ts | 4 +- packages/dom/src/Boundary.test.ts | 8 +-- packages/dom/src/Control/Control.test.ts | 8 +-- packages/dom/src/Element/core.test.ts | 6 +-- packages/dom/src/Element/ref.test.ts | 2 +- packages/dom/src/Portal.test.ts | 8 +-- packages/dom/src/Render/DOMRenderer.test.ts | 2 +- packages/dom/src/Render/client/client.test.ts | 8 +-- .../dom/src/Render/hydrate/hydrate.test.ts | 12 ++--- packages/dom/src/Render/server/index.ts | 20 +++++--- packages/dom/src/Render/server/server.test.ts | 10 ++-- packages/dom/src/Template.test.ts | 2 +- .../dom/src/VirtualList/VirtualList.test.ts | 8 +-- packages/dom/src/helpers/strings.test.ts | 2 +- packages/router/src/Link.test.ts | 2 +- packages/router/src/Navigation.test.ts | 2 +- packages/router/src/Route.test.ts | 51 ++++++++++++------- packages/router/src/Router.test.ts | 2 +- 30 files changed, 115 insertions(+), 87 deletions(-) create mode 100644 .changeset/typecheck-tests.md diff --git a/.changeset/typecheck-tests.md b/.changeset/typecheck-tests.md new file mode 100644 index 00000000..544e388a --- /dev/null +++ b/.changeset/typecheck-tests.md @@ -0,0 +1,5 @@ +--- +"@effex/dom": patch +--- + +Fix the `renderToString` return-type inference so provided dependencies (`RendererContext`, `ControlCtx`, `SuspenseBoundaryCtx`, `Scope`) are properly subtracted from the output `R`. Previously the signature used `Deps | R`, which TypeScript can't do set-subtraction from, so those tags leaked into the returned Effect's requirements — callers whose element required `Scope` (from `Signal.make`) or the other provided tags saw them appear in `Effect.runPromise` arguments even though `renderToString` itself provides them internally. Switched to `Exclude`; the runtime behaviour is unchanged. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5ee0d3c..354db561 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,8 @@ jobs: - run: pnpm install --frozen-lockfile + - run: pnpm typecheck + - run: pnpm build - run: pnpm test -- --run diff --git a/packages/core/src/AsyncReadable.test.ts b/packages/core/src/AsyncReadable.test.ts index 44a5b7e4..ecf19ee5 100644 --- a/packages/core/src/AsyncReadable.test.ts +++ b/packages/core/src/AsyncReadable.test.ts @@ -1,8 +1,8 @@ import { Effect, Option, Scope } from "effect"; import { describe, expect, it } from "vitest"; -import { AsyncReadable } from "./AsyncReadable"; -import { Signal } from "./Signal"; +import { AsyncReadable } from "./AsyncReadable.js"; +import { Signal } from "./Signal.js"; const runTest = (effect: Effect.Effect): Promise => Effect.runPromise(Effect.scoped(effect)); diff --git a/packages/core/src/Mutation.test.ts b/packages/core/src/Mutation.test.ts index 69c441d9..d67c7bcf 100644 --- a/packages/core/src/Mutation.test.ts +++ b/packages/core/src/Mutation.test.ts @@ -1,8 +1,8 @@ import { Effect, Option, Scope } from "effect"; import { describe, expect, it } from "vitest"; -import { Mutation } from "./Mutation"; -import { Signal } from "./Signal"; +import { Mutation } from "./Mutation.js"; +import { Signal } from "./Signal.js"; const runTest = (effect: Effect.Effect): Promise => Effect.runPromise(Effect.scoped(effect)); diff --git a/packages/core/src/Readable.test.ts b/packages/core/src/Readable.test.ts index 6c14c2f5..433c0a38 100644 --- a/packages/core/src/Readable.test.ts +++ b/packages/core/src/Readable.test.ts @@ -1,8 +1,8 @@ import { Chunk, Effect, Stream } from "effect"; import { describe, expect, it } from "vitest"; -import * as Readable from "./Readable"; -import * as Signal from "./Signal"; +import * as Readable from "./Readable.js"; +import * as Signal from "./Signal.js"; describe("Readable", () => { describe("make", () => { diff --git a/packages/core/src/Ref.test.ts b/packages/core/src/Ref.test.ts index d19e136b..30a476d9 100644 --- a/packages/core/src/Ref.test.ts +++ b/packages/core/src/Ref.test.ts @@ -1,7 +1,7 @@ import { Effect, Fiber } from "effect"; import { describe, expect, it } from "vitest"; -import { Ref } from "./Ref"; +import { Ref } from "./Ref.js"; describe("Ref", () => { it("should create a ref with null current initially", async () => { diff --git a/packages/core/src/Signal.test.ts b/packages/core/src/Signal.test.ts index 9b96b57a..8cf4a9d4 100644 --- a/packages/core/src/Signal.test.ts +++ b/packages/core/src/Signal.test.ts @@ -1,8 +1,8 @@ import { Effect, Stream } from "effect"; import { describe, expect, it } from "vitest"; -import { Readable } from "./Readable"; -import { Signal } from "./Signal"; +import { Readable } from "./Readable.js"; +import { Signal } from "./Signal.js"; describe("Signal.fromNullable", () => { it("should return existing signal when provided", async () => { diff --git a/packages/core/src/SignalArray.test.ts b/packages/core/src/SignalArray.test.ts index edee9be7..92e5a674 100644 --- a/packages/core/src/SignalArray.test.ts +++ b/packages/core/src/SignalArray.test.ts @@ -1,8 +1,8 @@ import { Effect, Fiber, Option, Scope, Stream } from "effect"; import { describe, expect, it } from "vitest"; -import { Readable } from "./Readable"; -import { Signal } from "./Signal"; +import { Readable } from "./Readable.js"; +import { Signal } from "./Signal.js"; const runTest = (effect: Effect.Effect): Promise => Effect.runPromise(Effect.scoped(effect)); diff --git a/packages/core/src/SignalMap.test.ts b/packages/core/src/SignalMap.test.ts index 6ca21498..9329feea 100644 --- a/packages/core/src/SignalMap.test.ts +++ b/packages/core/src/SignalMap.test.ts @@ -1,8 +1,8 @@ import { Effect, Fiber, Option, Scope, Stream } from "effect"; import { describe, expect, it } from "vitest"; -import { combine } from "./Readable"; -import { Signal } from "./Signal"; +import { combine } from "./Readable.js"; +import { Signal } from "./Signal.js"; const runTest = (effect: Effect.Effect): Promise => Effect.runPromise(Effect.scoped(effect)); diff --git a/packages/core/src/SignalSet.test.ts b/packages/core/src/SignalSet.test.ts index 5fa080db..4eaec7fb 100644 --- a/packages/core/src/SignalSet.test.ts +++ b/packages/core/src/SignalSet.test.ts @@ -1,8 +1,8 @@ import { Effect, Fiber, Scope, Stream } from "effect"; import { describe, expect, it } from "vitest"; -import { combine } from "./Readable"; -import { Signal } from "./Signal"; +import { combine } from "./Readable.js"; +import { Signal } from "./Signal.js"; const runTest = (effect: Effect.Effect): Promise => Effect.runPromise(Effect.scoped(effect)); diff --git a/packages/core/src/SignalStruct.test.ts b/packages/core/src/SignalStruct.test.ts index f2853d49..8bee9d89 100644 --- a/packages/core/src/SignalStruct.test.ts +++ b/packages/core/src/SignalStruct.test.ts @@ -1,8 +1,8 @@ import { Effect, Fiber, Scope, Stream } from "effect"; import { describe, expect, it } from "vitest"; -import { combine } from "./Readable"; -import { Signal } from "./Signal"; +import { combine } from "./Readable.js"; +import { Signal } from "./Signal.js"; const runTest = (effect: Effect.Effect): Promise => Effect.runPromise(Effect.scoped(effect)); diff --git a/packages/core/src/Transition.test.ts b/packages/core/src/Transition.test.ts index 503aefcc..610bacb7 100644 --- a/packages/core/src/Transition.test.ts +++ b/packages/core/src/Transition.test.ts @@ -1,8 +1,8 @@ import { Effect } from "effect"; import { describe, expect, it } from "vitest"; -import { Signal } from "./Signal"; -import { InvalidTransition, Transition } from "./Transition"; +import { Signal } from "./Signal.js"; +import { InvalidTransition, Transition } from "./Transition.js"; describe("Transition", () => { describe("make", () => { diff --git a/packages/dom/src/Animation/Animation.test.ts b/packages/dom/src/Animation/Animation.test.ts index cbbf58d5..fbe5d219 100644 --- a/packages/dom/src/Animation/Animation.test.ts +++ b/packages/dom/src/Animation/Animation.test.ts @@ -1,13 +1,13 @@ import { Effect } from "effect"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { runEnterAnimation, runExitAnimation } from "./core"; +import { runEnterAnimation, runExitAnimation } from "./core.js"; import { calculateStaggerDelay, stagger, staggerEased, staggerFromCenter, -} from "./index"; +} from "./index.js"; // Mock window.matchMedia for reduced motion tests const mockMatchMedia = (matches: boolean) => { diff --git a/packages/dom/src/Boundary.test.ts b/packages/dom/src/Boundary.test.ts index c76d0b1a..d2196e93 100644 --- a/packages/dom/src/Boundary.test.ts +++ b/packages/dom/src/Boundary.test.ts @@ -1,10 +1,10 @@ import { Effect, Layer } from "effect"; import { beforeEach, describe, expect, it } from "vitest"; -import { Boundary } from "./Boundary"; -import { $ } from "./Element"; -import { DOMRendererLive } from "./Render/DOMRenderer"; -import { ClientSuspenseBoundaryCtx } from "./SuspenseBoundaryCtx"; +import { Boundary } from "./Boundary.js"; +import { $ } from "./Element/index.js"; +import { DOMRendererLive } from "./Render/DOMRenderer.js"; +import { ClientSuspenseBoundaryCtx } from "./SuspenseBoundaryCtx/index.js"; // ClientSuspenseBoundaryCtx depends on DOMRendererLive const TestLayer = Layer.provideMerge( diff --git a/packages/dom/src/Control/Control.test.ts b/packages/dom/src/Control/Control.test.ts index eb6e4cfa..8435f3c1 100644 --- a/packages/dom/src/Control/Control.test.ts +++ b/packages/dom/src/Control/Control.test.ts @@ -4,10 +4,10 @@ import { beforeEach, expect } from "vitest"; import { Readable, Signal } from "@effex/core"; -import { ClientControlCtx, each, match, when } from "."; -import { collect } from "../Collect"; -import { $ } from "../Element"; -import { DOMRendererLive } from "../Render/DOMRenderer"; +import { collect } from "../Collect.js"; +import { $ } from "../Element/index.js"; +import { DOMRendererLive } from "../Render/DOMRenderer.js"; +import { ClientControlCtx, each, match, when } from "./index.js"; const TestLayer = Layer.mergeAll(ClientControlCtx, DOMRendererLive); diff --git a/packages/dom/src/Element/core.test.ts b/packages/dom/src/Element/core.test.ts index 0348f485..bb3869a9 100644 --- a/packages/dom/src/Element/core.test.ts +++ b/packages/dom/src/Element/core.test.ts @@ -3,9 +3,9 @@ import { beforeEach, describe, expect, it } from "vitest"; import { RendererContext, Signal } from "@effex/core"; -import { DOMRendererLive } from "../Render/DOMRenderer"; -import * as Core from "./core"; -import { make as makeRef } from "./ref"; +import { DOMRendererLive } from "../Render/DOMRenderer.js"; +import * as Core from "./core.js"; +import { make as makeRef } from "./ref.js"; const runTest = ( effect: Effect.Effect, diff --git a/packages/dom/src/Element/ref.test.ts b/packages/dom/src/Element/ref.test.ts index c401e367..eec06f3b 100644 --- a/packages/dom/src/Element/ref.test.ts +++ b/packages/dom/src/Element/ref.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest"; import { Readable } from "@effex/core"; -import { bindElementToRef, make } from "./ref"; +import { bindElementToRef, make } from "./ref.js"; const runTest = (effect: Effect.Effect): Promise => Effect.runPromise(Effect.scoped(effect)); diff --git a/packages/dom/src/Portal.test.ts b/packages/dom/src/Portal.test.ts index b8e531a8..88ad7945 100644 --- a/packages/dom/src/Portal.test.ts +++ b/packages/dom/src/Portal.test.ts @@ -1,10 +1,10 @@ import { Effect, Exit, Scope } from "effect"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { collect } from "./Collect"; -import { $ } from "./Element"; -import { Portal } from "./Portal"; -import { DOMRendererLive } from "./Render/DOMRenderer"; +import { collect } from "./Collect.js"; +import { $ } from "./Element/index.js"; +import { Portal } from "./Portal.js"; +import { DOMRendererLive } from "./Render/DOMRenderer.js"; describe("Portal", () => { let portalRoot: HTMLDivElement; diff --git a/packages/dom/src/Render/DOMRenderer.test.ts b/packages/dom/src/Render/DOMRenderer.test.ts index 2e2a9a72..97694830 100644 --- a/packages/dom/src/Render/DOMRenderer.test.ts +++ b/packages/dom/src/Render/DOMRenderer.test.ts @@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it } from "vitest"; import { RendererContext } from "@effex/core"; -import { DOMRenderer, DOMRendererLive } from "./DOMRenderer"; +import { DOMRenderer, DOMRendererLive } from "./DOMRenderer.js"; const runTest = (effect: Effect.Effect) => Effect.runPromise(effect.pipe(Effect.provide(DOMRendererLive))); diff --git a/packages/dom/src/Render/client/client.test.ts b/packages/dom/src/Render/client/client.test.ts index a7694fba..b6cb3a8e 100644 --- a/packages/dom/src/Render/client/client.test.ts +++ b/packages/dom/src/Render/client/client.test.ts @@ -1,10 +1,10 @@ import { Effect, Fiber } from "effect"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { collect } from "../../Collect"; -import { $ } from "../../Element"; -import { DOMRendererLive } from "../DOMRenderer"; -import { mount } from "./client"; +import { collect } from "../../Collect.js"; +import { $ } from "../../Element/index.js"; +import { DOMRendererLive } from "../DOMRenderer.js"; +import { mount } from "./client.js"; describe("mount", () => { let container: HTMLElement; diff --git a/packages/dom/src/Render/hydrate/hydrate.test.ts b/packages/dom/src/Render/hydrate/hydrate.test.ts index d92d4215..185dbc3c 100644 --- a/packages/dom/src/Render/hydrate/hydrate.test.ts +++ b/packages/dom/src/Render/hydrate/hydrate.test.ts @@ -3,12 +3,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { Readable, Signal } from "@effex/core"; -import { Boundary } from "../../Boundary"; -import { collect } from "../../Collect"; -import { each, match, when } from "../../Control"; -import { $ } from "../../Element"; -import { renderToString } from "../server"; -import { hydrate } from "./index"; +import { Boundary } from "../../Boundary.js"; +import { collect } from "../../Collect.js"; +import { each, match, when } from "../../Control/index.js"; +import { $ } from "../../Element/index.js"; +import { renderToString } from "../server/index.js"; +import { hydrate } from "./index.js"; describe("Hydration", () => { let container: HTMLElement; diff --git a/packages/dom/src/Render/server/index.ts b/packages/dom/src/Render/server/index.ts index 6d36d331..6ba5325e 100644 --- a/packages/dom/src/Render/server/index.ts +++ b/packages/dom/src/Render/server/index.ts @@ -24,7 +24,7 @@ * @module */ -import { Effect, Layer } from "effect"; +import { Effect, Layer, type Scope } from "effect"; import { RendererContext, @@ -73,13 +73,13 @@ export const renderToString = < E = never, R = never, >( - element: Element.Element< - A, - E, - RendererContext | ControlCtx | SuspenseBoundaryCtx | R - >, + element: Element.Element, _options: RenderToStringOptions = {}, -): Effect.Effect => { +): Effect.Effect< + string, + E, + Exclude +> => { const StringRendererLayer = Layer.succeed( RendererContext, StringRenderer as Renderer, @@ -105,7 +105,11 @@ export const renderToString = < Effect.provide(StringRendererLayer), Effect.provide(SSRControlCtx), withSSRContext, - ); + ) as Effect.Effect< + string, + E, + Exclude + >; }; // Re-export types and utilities diff --git a/packages/dom/src/Render/server/server.test.ts b/packages/dom/src/Render/server/server.test.ts index fcc0da67..6c607501 100644 --- a/packages/dom/src/Render/server/server.test.ts +++ b/packages/dom/src/Render/server/server.test.ts @@ -3,11 +3,11 @@ import { describe, expect, it } from "vitest"; import { Readable, Signal } from "@effex/core"; -import { Boundary } from "../../Boundary"; -import { collect } from "../../Collect"; -import { each, match, when } from "../../Control"; -import { $ } from "../../Element"; -import { renderToString } from "./index"; +import { Boundary } from "../../Boundary.js"; +import { collect } from "../../Collect.js"; +import { each, match, when } from "../../Control/index.js"; +import { $ } from "../../Element/index.js"; +import { renderToString } from "./index.js"; describe("SSR", () => { describe("renderToString", () => { diff --git a/packages/dom/src/Template.test.ts b/packages/dom/src/Template.test.ts index 3bfadd57..992f6b7e 100644 --- a/packages/dom/src/Template.test.ts +++ b/packages/dom/src/Template.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest"; import { Readable, Signal } from "@effex/core"; -import { t } from "./Template"; +import { t } from "./Template.js"; describe("Template (t)", () => { describe("static templates", () => { diff --git a/packages/dom/src/VirtualList/VirtualList.test.ts b/packages/dom/src/VirtualList/VirtualList.test.ts index 9f2ea4c4..5dbc09b3 100644 --- a/packages/dom/src/VirtualList/VirtualList.test.ts +++ b/packages/dom/src/VirtualList/VirtualList.test.ts @@ -3,8 +3,8 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { Readable, Signal } from "@effex/core"; -import { $ } from "../Element"; -import { DOMRendererLive } from "../Render/DOMRenderer"; +import { $ } from "../Element/index.js"; +import { DOMRendererLive } from "../Render/DOMRenderer.js"; import { calculateItemOffset, calculateScrollToPosition, @@ -12,8 +12,8 @@ import { calculateVisibleRange, parseHeight, rangesEqual, -} from "./helpers"; -import { ClientVirtualListCtx, virtualEach, VirtualListRef } from "./index"; +} from "./helpers.js"; +import { ClientVirtualListCtx, virtualEach, VirtualListRef } from "./index.js"; const runTest = (effect: Effect.Effect) => Effect.runPromise( diff --git a/packages/dom/src/helpers/strings.test.ts b/packages/dom/src/helpers/strings.test.ts index 3bd4ff1d..3aad301d 100644 --- a/packages/dom/src/helpers/strings.test.ts +++ b/packages/dom/src/helpers/strings.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { toCamelCase, toKebabCase } from "./strings"; +import { toCamelCase, toKebabCase } from "./strings.js"; describe("toKebabCase", () => { it("should convert camelCase to kebab-case", () => { diff --git a/packages/router/src/Link.test.ts b/packages/router/src/Link.test.ts index 139685e2..8bd63e88 100644 --- a/packages/router/src/Link.test.ts +++ b/packages/router/src/Link.test.ts @@ -86,7 +86,7 @@ describe("Link", () => { const el = await Effect.runPromise( Link( - { to: UserRoute, params: { id: "123" } }, + { to: UserRoute as LinkProps["to"], params: { id: "123" } }, Effect.succeed("User"), ).pipe(Effect.scoped, Effect.provide(layer)), ); diff --git a/packages/router/src/Navigation.test.ts b/packages/router/src/Navigation.test.ts index 19db384b..d54359ba 100644 --- a/packages/router/src/Navigation.test.ts +++ b/packages/router/src/Navigation.test.ts @@ -1,4 +1,4 @@ -import { Effect, Option, Schema } from "effect"; +import { Effect, Schema } from "effect"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { diff --git a/packages/router/src/Route.test.ts b/packages/router/src/Route.test.ts index 0ad91df6..368bba56 100644 --- a/packages/router/src/Route.test.ts +++ b/packages/router/src/Route.test.ts @@ -134,7 +134,12 @@ describe("Route.make", () => { it("render yields NoRenderError when not set", async () => { const route = Route.make("/users"); - const result = await Effect.runPromiseExit(route.render(undefined)); + // Element carries Scope | RendererContext in R; these tests exercise + // render's Effect-level behaviour without touching the DOM, so the cast + // to a fully-resolved Effect is safe. + const result = await Effect.runPromiseExit( + route.render(undefined) as never, + ); expect(result._tag).toBe("Failure"); }); @@ -163,7 +168,7 @@ describe("Route.render", () => { Route.render(() => Effect.succeed(div)), ); - const result = await Effect.runPromise(route.render(undefined)); + const result = await Effect.runPromise(route.render(undefined) as never); expect(result).toBe(div); }); }); @@ -174,7 +179,7 @@ describe("Route.get", () => { params: Record; searchParams: Record; }) => Effect.succeed({ name: "test" }); - const renderFn = (data: { name: string }) => + const renderFn = (_data: { name: string }) => Effect.succeed(document.createElement("div")); const route = Route.make("/users/:id").pipe(Route.get(loader, renderFn)); @@ -186,11 +191,16 @@ describe("Route.get", () => { const route = Route.make("/users").pipe( Route.get( ({}) => Effect.succeed({ greeting: "hello" }), - (data) => Effect.succeed(data), + // Cast: these tests exercise type flow from loader to render, so we + // return the data itself instead of a real element. The type + // assertion satisfies renderFn's HTMLElement | SVGElement contract. + (data) => Effect.succeed(data) as never, ), ); - const result = await Effect.runPromise(route.render({ greeting: "hello" })); + const result = (await Effect.runPromise( + route.render({ greeting: "hello" }) as never, + )) as { greeting: string }; expect(result).toEqual({ greeting: "hello" }); }); @@ -201,12 +211,14 @@ describe("Route.get", () => { (nums) => { // nums is inferred as number[] const sum: number = nums.reduce((a, b) => a + b, 0); - return Effect.succeed(sum); + return Effect.succeed(sum) as never; }, ), ); - const result = await Effect.runPromise(route.render([1, 2, 3])); + const result = (await Effect.runPromise( + route.render([1, 2, 3]) as never, + )) as number; expect(result).toBe(6); }); }); @@ -216,7 +228,7 @@ describe("Route.static", () => { const paths = () => Effect.succeed([{ slug: "a" }, { slug: "b" }]); const load = ({ params }: { params: { slug: string } }) => Effect.succeed({ content: `Page ${params.slug}` }); - const renderFn = (data: { content: string }) => + const renderFn = (_data: { content: string }) => Effect.succeed(document.createElement("div")); const route = Route.make("/docs/:slug").pipe( @@ -238,7 +250,9 @@ describe("Route.static", () => { }), ); - const result = await Effect.runPromise(route.render({ title: "About" })); + const result = await Effect.runPromise( + route.render({ title: "About" }) as never, + ); expect(result).toBe(div); }); @@ -250,7 +264,9 @@ describe("Route.static", () => { }), ); - const paramSets = await Effect.runPromise(route._staticConfig!.paths()); + const paramSets = await Effect.runPromise( + route._staticConfig!.paths() as never, + ); expect(paramSets).toEqual([{}]); }); @@ -272,22 +288,23 @@ describe("Route.static", () => { paths: () => Effect.succeed([{ slug: "test" }]), load: ({ params }) => Effect.succeed({ content: `Page ${params.slug}` }), - render: (data) => Effect.succeed(data.content), + // Cast: exercises data flow only; renderFn returns the data itself. + render: (data) => Effect.succeed(data.content) as never, }), ); // Simulate the build flow: load data, then pass to render - const data = await Effect.runPromise( - route._staticConfig!.load({ params: { slug: "hello" } }), - ); - const result = await Effect.runPromise(route.render(data)); + const data = (await Effect.runPromise( + route._staticConfig!.load({ params: { slug: "hello" } }) as never, + )) as { content: string }; + const result = await Effect.runPromise(route.render(data) as never); expect(result).toBe("Page hello"); }); }); describe("Route.post", () => { it("stores a post handler", () => { - const handler = (body: unknown) => Effect.succeed({ ok: true }); + const handler = (_body: unknown) => Effect.succeed({ ok: true }); const route = Route.make("/users").pipe(Route.post("submit", handler)); @@ -568,7 +585,7 @@ describe("Route composition", () => { Route.post("submit", (body) => Effect.succeed(body)), Route.get( ({ params: { id } }) => Effect.succeed({ id, name: "test" }), - (user) => Effect.succeed(document.createElement("div")), + (_user) => Effect.succeed(document.createElement("div")), ), Route.withGuard(isAuth, { redirect: "/login" }), Route.withAnimation({ enter: "fade-in", exit: "fade-out" }), diff --git a/packages/router/src/Router.test.ts b/packages/router/src/Router.test.ts index 31701a06..9e106a7e 100644 --- a/packages/router/src/Router.test.ts +++ b/packages/router/src/Router.test.ts @@ -462,7 +462,7 @@ describe("Router", () => { concat(Route.make("/users").pipe(Route.render(render))), // becomes /admin/users concat(Route.make("/settings").pipe(Route.render(render))), // becomes /admin/settings prefixAll("/admin"), - layout((children) => children), // AdminLayout + layout((children) => children as never), // AdminLayout ); // Combine everything From b8e11d2344cf75c0b75111d8af522c599594d228 Mon Sep 17 00:00:00 2001 From: Jon Laing Date: Sun, 12 Jul 2026 08:38:02 -0400 Subject: [PATCH 2/2] chore: add tsconfig paths for @effex/dom subpath exports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @effex/dom exports ./client, ./server, and ./hydrate as subpaths. Root tsconfig only had a path for @effex/dom itself, so consumers (currently @effex/platform importing @effex/dom/server) resolved subpaths via the exports map in dom's package.json — which points at ./dist. Locally that worked because dist existed from earlier builds; in CI, pnpm typecheck runs before pnpm build, so the subpaths were unresolvable. Map each subpath to its source entry so typecheck stays independent of build state. Co-Authored-By: Claude Opus 4.7 --- tsconfig.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tsconfig.json b/tsconfig.json index 7f011551..3baa8c36 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,9 @@ "paths": { "@effex/core": ["packages/core/src/index.ts"], "@effex/dom": ["packages/dom/src/index.ts"], + "@effex/dom/client": ["packages/dom/src/Render/client/index.ts"], + "@effex/dom/server": ["packages/dom/src/Render/server/index.ts"], + "@effex/dom/hydrate": ["packages/dom/src/Render/hydrate/index.ts"], "@effex/router": ["packages/router/src/index.ts"], "@effex/form": ["packages/form/src/index.ts"] }