Skip to content

fix(combo): use .ts extension for localDb import to fix Turbopack dev-boot resolution - #10688

Closed
hartmark wants to merge 2 commits into
diegosouzapw:release/v3.8.50from
hartmark:fix/localdb-ts-extension-import
Closed

hartmark wants to merge 2 commits into
diegosouzapw:release/v3.8.50from
hartmark:fix/localdb-ts-extension-import

Conversation

@hartmark

@hartmark hartmark commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • open-sse/services/combo.ts:90 imported localDb with an explicit .js extension, unlike every sibling static import in the same file (.ts or no extension). That .js suffix stopped resolving under Turbopack's dev-instrumentation path after the dependency bump in feat(compression): adota omniglyph 1.4.0, perfis semânticos e contabilidade com evidência #10647, so npm ci && npm run dev on the current tip dies during boot before serving anything:

    [STARTUP] Fatal: instrumentation hook failed during boot: Cannot find module '../../src/lib/localDb.js'
    

    One-line fix: use .ts, matching the sibling import one line above it.

  • Update (second commit): fixes the dev-boot bug above but doesn't fix npm run build, which was still red for a separate reason — thanks @jonlwheat2-gif for the full diagnosis and verified fix in the review comments. open-sse/config/providers/registry/aihorde/imageModels.ts statically imports open-sse/services/aihordeImageCatalog.ts, which pulls in safeOutboundFetch -> featureFlags -> db/core -> better-sqlite3. imageRegistry.ts (which imports imageModels.ts) is reachable from the "use client" ProviderDetailPageClient.tsx dashboard page via mediaServiceKinds.ts -> serviceKindIndex.ts, so the whole server DB/network stack was getting dragged into the browser bundle — Turbopack reported 28 Module not found errors (fs/net/tls/...) across the reachable server graph. imageModels.ts no longer imports the catalog service directly; it holds a module-level snapshot that the server-side Horde poller pushes into via a new setAiHordeImageCatalogEntries() setter, called from replace()/clear() after every snapshot change. No behavior change — server-side the registry still reflects the live catalog, client-side the model list was always empty before this too (the browser can neither poll Horde nor reach the DB).

Related Issues

Validation

  • Change type: build-deploy (dev-server boot resolution + client-bundle isolation; no runtime logic changed)
  • npx eslint on all touched files: clean
  • lint-staged pre-commit (prettier + eslint --fix + docs-sync + t11 any-budget): passed
  • Reconciled with release/v3.8.50 tip (df9059141)
  • npm run build: before the second commit, Turbopack build failed with 28 errors (reproduced against a pristine checkout + fresh npm ci, matching @jonlwheat2-gif's report exactly). After: ✓ Compiled successfully, 0 errors.
  • tests/unit/aihorde-*.test.ts (5 files, the full AI Horde suite): 29/29 pass via node --test.

Tests Added Or Updated

  • None new. The imageModels.ts / aihordeImageCatalog.ts refactor is a wiring change (push instead of pull) with identical runtime behavior on both ends; the existing tests/unit/aihorde-image-catalog.test.ts and tests/unit/aihorde-image-generation.test.ts already cover getCachedAiHordeImageCatalogEntries() and the registry entry shape, and both pass unmodified against the refactor.

Coverage Notes

  • No coverage impact for the first (import-extension) commit — file content and exports are otherwise unchanged.
  • Second commit is a pure indirection change (setter/getter instead of direct call) behind the same existing test coverage; no new branches introduced.

Reviewer Notes

…-boot resolution

The .js-suffixed import at combo.ts:90 stopped resolving to localDb.ts
under Turbopack's dev-instrumentation path after the dependency bump in
diegosouzapw#10647, crashing npm run dev on a fresh npm ci with:

  Cannot find module '../../src/lib/localDb.js'

Every sibling static import in this same file already uses the .ts
extension (see line 89); this one was the only .js-suffixed exception.
Matching that style resolves cleanly under both webpack and Turbopack.

Verified live: fresh npm ci + npm run dev booted and served
GET /api/health/ping -> 200 on the current release/v3.8.50 tip
(df90591), where it previously failed instrumentation before serving
anything.

Closes diegosouzapw#10674
@hartmark
hartmark requested a review from diegosouzapw as a code owner August 18, 2026 18:35
@jonlwheat2-gif

jonlwheat2-gif commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

Heads-up: this fixes the dev-boot error but the production build (npm run build) is still red — there are 28 more errors, all from a separate root cause (the AI Horde live-catalog import added in #10542).

I tested this change against a pristine release/v3.8.50 checkout (df9059141) with a fresh npm ci:

  • Before: Turbopack build failed with 29 errors
  • After applying this PR: Turbopack build failed with 28 errors — so this removes exactly 1 error (the combo.ts:90 localDb.js resolution, which also unblocks npm run dev boot), but the production build still fails.

The remaining 28 errors — one root cause

All 28 are the same client-bundle contamination, introduced by the static import in #10542 (feat(providers): optional AI Horde API key and live image catalog):

ProviderDetailPageClient.tsx   ("use client" dashboard providers page)
→ src/lib/providers/serviceKindIndex.ts
→ open-sse/config/mediaServiceKinds.ts
→ open-sse/config/imageRegistry.ts
→ open-sse/config/providers/registry/aihorde/imageModels.ts:9   ← static import (added by #10542)
→ open-sse/services/aihordeImageCatalog.ts:10                    ← static import of safeOutboundFetch
→ src/shared/network/safeOutboundFetch.ts → open-sse/utils/proxyFetch.ts
→ src/shared/utils/featureFlags.ts → src/lib/db/featureFlags.ts → src/lib/db/core.ts
→ better-sqlite3 / sqlite-vec / ioredis / socks / tls-client …

imageRegistry is reachable from a client component, so the new static import drags the entire server DB/network stack (better-sqlite3, ioredis, socks, sqlite-vec, yazl, …) into the browser bundle, where fs/net/dns/tls don't exist. Turbopack reports it as 28 Module not found: Can't resolve 'fs'/'net'/'tls'/… errors across the whole reachable server graph.

The complete fix (2 files, behavior-preserving)

Red = removed (before), green = added (after). Line anchors below refer to the current release/v3.8.50 tree (before this PR).

File 1: open-sse/config/providers/registry/aihorde/imageModels.ts

Line (current tree) Change
L9 Remove the static import of aihordeImageCatalog.ts (the trigger)
L9–10 Add AiHordeRegistryModelEntry + aiHordeCatalogEntries snapshot + setAiHordeImageCatalogEntries()
L18–23 get models() reads the snapshot instead of the service
diff --git a/open-sse/config/providers/registry/aihorde/imageModels.ts b/open-sse/config/providers/registry/aihorde/imageModels.ts
index be04ce18a..5aee065cf 100644
--- a/open-sse/config/providers/registry/aihorde/imageModels.ts
+++ b/open-sse/config/providers/registry/aihorde/imageModels.ts
@@ -5,8 +5,34 @@
  * async API (`/v2/generate/async`). `models` is a live getter so
  * imageRegistry stays under the file-size cap and zero-worker names are
  * never advertised.
+ *
+ * NOTE: this module must NOT statically import the live catalog service
+ * (open-sse/services/aihordeImageCatalog.ts). That service pulls in the
+ * network/DB stack (safeOutboundFetch → featureFlags → db/core →
+ * better-sqlite3), which would drag server-only code into the client bundle
+ * and fail the production build. Instead, the server-side Horde poller pushes
+ * refreshed snapshots in through `setAiHordeImageCatalogEntries()`.
  */
-import { getCachedAiHordeImageCatalogEntries } from "../../../../services/aihordeImageCatalog.ts";
+
+export interface AiHordeRegistryModelEntry {
+  id: string;
+  name: string;
+  provider: string;
+  supportedSizes: string[];
+  inputModalities: string[];
+  description?: string;
+}
+
+// Snapshot of the live AI Horde catalog. Empty until the server-side poller
+// pushes a refresh. (The client bundle never populates it — same as before,
+// when the client always read an empty in-memory cache.)
+let aiHordeCatalogEntries: AiHordeRegistryModelEntry[] = [];
+
+export function setAiHordeImageCatalogEntries(
+  entries: AiHordeRegistryModelEntry[]
+): void {
+  aiHordeCatalogEntries = entries;
+}
 
 export const AI_HORDE_IMAGE_PROVIDER = {
   id: "aihorde",
@@ -16,7 +42,7 @@ export const AI_HORDE_IMAGE_PROVIDER = {
   authHeader: "apikey",
   format: "aihorde",
   get models() {
-    return getCachedAiHordeImageCatalogEntries().map((entry) => ({
+    return aiHordeCatalogEntries.map((entry) => ({
       id: entry.id.startsWith("aihorde/") ? entry.id.slice("aihorde/".length) : entry.id,
       name: entry.name,
       inputModalities: entry.inputModalities,

File 2: open-sse/services/aihordeImageCatalog.ts

Line (current tree) Change
L10 Add the setter import (after safeOutboundFetch)
L138–147 replace(...) → syncRegistrySnapshot(this)
L149–153 clear() → syncRegistrySnapshot(this)
L198 Add registryEntriesFrom() + syncRegistrySnapshot() helpers
L204–215 getCachedAiHordeImageCatalogEntries() refactored onto the shared helper
diff --git a/open-sse/services/aihordeImageCatalog.ts b/open-sse/services/aihordeImageCatalog.ts
index a0cffa02a..e544aca88 100644
--- a/open-sse/services/aihordeImageCatalog.ts
+++ b/open-sse/services/aihordeImageCatalog.ts
@@ -8,6 +8,10 @@
  */
 
 import { safeOutboundFetch } from "@/shared/network/safeOutboundFetch";
+import {
+  setAiHordeImageCatalogEntries,
+  type AiHordeRegistryModelEntry,
+} from "../config/providers/registry/aihorde/imageModels.ts";
 
 export const AI_HORDE_API_BASE = "https://aihorde.net/api";
 export const AI_HORDE_ANONYMOUS_KEY = "0000000000";
@@ -143,6 +147,7 @@ export class HordeImageCatalog {
     } else {
       this.lastError = error;
     }
+    syncRegistrySnapshot(this);
   }
 
   /** Drop the snapshot so the next `ensureFresh` must hit Horde. */
@@ -150,6 +155,7 @@ export class HordeImageCatalog {
     this.models = new Map();
     this.updatedAt = null;
     this.lastError = null;
+    syncRegistrySnapshot(this);
   }
 
   setFetch(fetchImpl: HordeFetch): void {
@@ -197,19 +203,13 @@ export class HordeImageCatalog {
 
 export const aiHordeImageCatalog = new HordeImageCatalog();
 
-export function resetAiHordeImageCatalog(): void {
-  aiHordeImageCatalog.clear();
-}
-
-export function getCachedAiHordeImageCatalogEntries(): Array<{
-  id: string;
-  name: string;
-  provider: string;
-  supportedSizes: string[];
-  inputModalities: string[];
-  description?: string;
-}> {
-  return aiHordeImageCatalog.listModels().map((model) => ({
+/**
+ * Map a catalog snapshot to the registry entry shape and push it into
+ * `AI_HORDE_IMAGE_PROVIDER` (see imageModels.ts). Only the module-level
+ * singleton drives the registry — injected test instances are ignored.
+ */
+function registryEntriesFrom(models: HordeImageCatalogModel[]): AiHordeRegistryModelEntry[] {
+  return models.map((model) => ({
     id: `aihorde/${model.name}`,
     name: `${model.name} (AI Horde)`,
     provider: "aihorde",
@@ -218,3 +218,16 @@ export function getCachedAiHordeImageCatalogEntries(): Array<{
     description: `${model.count} worker${model.count === 1 ? "" : "s"} online`,
   }));
 }
+
+function syncRegistrySnapshot(from: HordeImageCatalog): void {
+  if (from !== aiHordeImageCatalog) return;
+  setAiHordeImageCatalogEntries(registryEntriesFrom(from.listModels()));
+}
+
+export function resetAiHordeImageCatalog(): void {
+  aiHordeImageCatalog.clear();
+}
+
+export function getCachedAiHordeImageCatalogEntries(): AiHordeRegistryModelEntry[] {
+  return registryEntriesFrom(aiHordeImageCatalog.listModels());
+}

Verification (pristine base + this PR's fix + the above 2 files)

  • npm run build: ✓ Compiled successfully — 0 errors (was 29)
  • tests/unit/aihorde-image-catalog.test.ts + aihorde-image-generation.test.ts: 12/12 pass
  • npm run typecheck:core: clean
  • No runtime behavior change: server-side the registry still gets live catalog data (the poller pushes on replace()/clear()); client-side the model list was always empty (browser can't poll Horde or hit the DB), so nothing regresses there.

Happy to open this as a follow-up PR (or fold it in here) — the release branch currently can't build without both changes.

…Models.ts

imageModels.ts statically imported aihordeImageCatalog.ts, which pulls
in safeOutboundFetch -> featureFlags -> db/core -> better-sqlite3 and
friends. imageRegistry.ts (which imports imageModels.ts) is reachable
from the "use client" ProviderDetailPageClient.tsx dashboard page via
mediaServiceKinds.ts -> serviceKindIndex.ts, so the whole server DB/
network stack was getting dragged into the browser bundle. Turbopack
reported this as 28 Module-not-found errors (fs/net/tls/...) across
the reachable server graph, failing npm run build outright.

imageModels.ts no longer imports the catalog service. It holds a
module-level snapshot (empty until refreshed) and exposes
setAiHordeImageCatalogEntries() for the server-side poller to push
into. aihordeImageCatalog.ts's replace()/clear() now call that setter
after every snapshot change, and getCachedAiHordeImageCatalogEntries()
is refactored onto the same registryEntriesFrom() mapping helper so
the two entry points can't drift.

No behavior change: server-side the registry still reflects the live
catalog (pushed on every replace()/clear()); client-side the model
list was always empty before this fix too, since the browser can
neither poll Horde nor reach the DB.

Verified: npm run build goes from 28 errors to 0 (Compiled
successfully). tests/unit/aihorde-*.test.ts: 29/29 pass.

Reported and diagnosed with a full repro + verified diff by
@jonlwheat2-gif on PR diegosouzapw#10688; this commit re-verifies the diagnosis
against the current tree and applies it.
@diegosouzapw

Copy link
Copy Markdown
Owner

Thanks for the careful diagnosis and live verification — this is exactly the right fix, but it has already been merged into release/v3.8.50, so this PR is fully subsumed and I'm closing it.

Both changes you made here are already on the base tip:

  1. The combo.ts localDb.js -> localDb.ts import landed via fix(sse): import localDb through its real .ts extension (#10674) #10691 (commit 37c81ce), which closes the same issue (base-red: dev server cannot boot on release/v3.8.50 tip — instrumentation fails with "Cannot find module '../../src/lib/localDb.js'" after #10647's lockfile change #10674). That merge is a superset of your commit 1: it also fixes the identical latent .js pattern in src/lib/usage/usageLedger.ts and adds a permanent guard test (no-js-extension-on-repo-imports-10674.test.ts) so the .ts-extension rule can't regress.

  2. The AI Horde client-bundle isolation also landed via the base-red: production build fails — 28 Node-builtin resolution errors from server-only code reaching the client bundle (dashboard/providers page → db/core) #10692 merges — fix(config): keep the SQLite driver out of the client bundle (#10692) #10695 (22b89a2) keeps the SQLite driver out of the client bundle and rewrote open-sse/config/providers/registry/aihorde/imageModels.ts to pull models through getDynamicImageModels("aihorde") instead of importing the catalog service, and test(guard): widen the client-bundle guard to every "use client" entry point (#10692) #10700 widened the use client bundle guard test. GitHub currently reports this PR as CONFLICTING precisely because the base already carries these changes.

Since the base tip already contains every line this PR intends (combo.ts:90 is localDb.ts; imageModels.ts no longer imports the catalog service), there is nothing left to merge. Closing as subsumed — no action needed from you.

@hartmark hartmark closed this Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants