Skip to content

Commit b34b2d1

Browse files
committed
fix(blocks): stop registry.ts reading BLOCK_REGISTRY at module scope
`blocks/registry-maps` and `blocks/registry` sit in an import cycle: a block config reaches back through providers/utils -> tools/params -> blocks/index -> blocks/registry, which imports registry-maps. Whichever module the cycle is entered through evaluates first, so `blocks/registry` can run while `registry-maps` is still initializing. Two module-scope reads of the imported binding therefore threw `ReferenceError: Cannot access 'BLOCK_REGISTRY' before initialization` for anything importing `@/blocks/registry-maps` directly — scripts and tests under Bun: const HAS_PREVIEW_BLOCKS = Object.values(BLOCK_REGISTRY).some(...) // line 28 export const registry: Record<string, BlockConfig> = BLOCK_REGISTRY // line 238 Only line 28 showed in the stack trace; fixing it alone would have moved the failure to line 238. Both are now deferred: `anyPreviewBlocks()` memoizes on first use (still computed once, as the old comment promised), and the raw map is exposed as `getBlockRegistry()`. This worked under Turbopack only because that bundler happened to order the modules favourably — not a property to build on, and any registry restructuring would perturb it. Breaking it now is a prerequisite for the metadata/implementation split that the tool-registry module-graph audit proposes, not a fix to discover midway through one. Three consumers updated. `tsc` found two that grep missed, because they destructure the binding off a dynamic import (`const { registry: blockRegistry } = await import(...)`) rather than naming it in a static import. Behaviour-preserving: `getBlockRegistry()[type]` is the same raw lookup the alias gave, deliberately not `getBlock()`, which would add version normalization and custom-block overlay fallback.
1 parent 911b958 commit b34b2d1

4 files changed

Lines changed: 35 additions & 10 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/components/chat-context-kind-registry/chat-context-kind-registry.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { AgentSkillsIcon, McpIcon } from '@/components/icons'
1414
import { getDocumentIcon } from '@/components/icons/document-icons'
1515
import type { ChatContextKind, ChatMessageContext } from '@/app/workspace/[workspaceId]/home/types'
1616
import { getBareIconStyle } from '@/blocks/icon-color'
17-
import { registry as blockRegistry } from '@/blocks/registry'
17+
import { getBlockRegistry } from '@/blocks/registry'
1818

1919
interface RenderIconArgs {
2020
context: ChatMessageContext
@@ -41,7 +41,7 @@ function renderWorkflowIcon({ className }: RenderIconArgs): ReactNode | null {
4141
function renderIntegrationTile({ context, className }: RenderIconArgs): ReactNode | null {
4242
if (context.kind !== 'integration') return null
4343
if (!context.blockType) return null
44-
const block = blockRegistry[context.blockType]
44+
const block = getBlockRegistry()[context.blockType]
4545
if (!block) return null
4646
const Icon = block.icon
4747
return <Icon className={className} style={getBareIconStyle(Icon)} />

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/hooks/use-mention-data.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@ export function useMentionData(props: UseMentionDataProps): MentionDataReturn {
192192
// Fetch current blocks from store
193193
const workflowStoreBlocks = useWorkflowStore.getState().blocks
194194

195-
const { registry: blockRegistry } = await import('@/blocks/registry')
195+
const { getBlockRegistry } = await import('@/blocks/registry')
196+
const blockRegistry = getBlockRegistry()
196197
const mapped = Object.values(workflowStoreBlocks).map((b: any) => {
197198
const reg = (blockRegistry as any)[b.type]
198199
return {

apps/sim/blocks/registry.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,32 @@ export function getBlock(type: string): BlockConfig | undefined {
2424
return BLOCK_REGISTRY[type] ?? BLOCK_REGISTRY[normalizeType(type)] ?? resolveOverlayBlock(type)
2525
}
2626

27-
/** Whether any registered block is an unreleased `preview` block. Static — computed once. */
28-
const HAS_PREVIEW_BLOCKS = Object.values(BLOCK_REGISTRY).some((block) => block.preview)
27+
/**
28+
* Whether any registered block is an unreleased `preview` block. Computed once,
29+
* on first use rather than at module scope.
30+
*
31+
* `blocks/registry-maps` and this module sit in an import cycle (a block config
32+
* reaches back here through `providers/utils` → `tools/params` → `blocks/index`),
33+
* so whichever module the cycle is entered through evaluates first. Reading
34+
* `BLOCK_REGISTRY` at module scope therefore threw
35+
* `ReferenceError: Cannot access 'BLOCK_REGISTRY' before initialization` for
36+
* anything that imported `blocks/registry-maps` directly — scripts and tests
37+
* under Bun. It only worked under Turbopack because that bundler happened to
38+
* order the modules favourably, which is not a guarantee to build on.
39+
*/
40+
let hasPreviewBlocks: boolean | undefined
41+
function anyPreviewBlocks(): boolean {
42+
hasPreviewBlocks ??= Object.values(BLOCK_REGISTRY).some((block) => block.preview)
43+
return hasPreviewBlocks
44+
}
2945

3046
/**
3147
* True when the visibility projection cannot change any block, so accessors can
3248
* return raw arrays untouched: no `preview` blocks exist (they must be hidden
3349
* even with a null state) and no kill-switch entries apply.
3450
*/
3551
function visibilityInert(vis: BlockVisibilityState | null): boolean {
36-
if (HAS_PREVIEW_BLOCKS) return false
52+
if (anyPreviewBlocks()) return false
3753
return vis === null || vis.disabled.size === 0
3854
}
3955

@@ -232,9 +248,16 @@ export function getSuggestedSkillsForBlock(type: string): readonly SuggestedSkil
232248

233249
/**
234250
* Raw block registry map keyed by block type. Prefer the typed accessors
235-
* (`getBlock`, `getAllBlocks`, `getCanonicalBlocksByCategory`); this alias is
236-
* retained for callers that need the underlying record directly.
251+
* (`getBlock`, `getAllBlocks`, `getCanonicalBlocksByCategory`); this is retained
252+
* for callers that need the underlying record directly.
253+
*
254+
* A function rather than an eager `const` alias: binding `BLOCK_REGISTRY` at
255+
* module scope re-introduces the initialization-order failure that
256+
* {@link anyPreviewBlocks} documents, since this module can evaluate before
257+
* `blocks/registry-maps` has finished.
237258
*/
238-
export const registry: Record<string, BlockConfig> = BLOCK_REGISTRY
259+
export function getBlockRegistry(): Record<string, BlockConfig> {
260+
return BLOCK_REGISTRY
261+
}
239262

240263
export type { BlockCategory }

apps/sim/lib/copilot/chat/process-contents.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,8 @@ async function processBlockMetadata(
545545
return null
546546
}
547547

548-
const { registry: blockRegistry } = await import('@/blocks/registry')
548+
const { getBlockRegistry } = await import('@/blocks/registry')
549+
const blockRegistry = getBlockRegistry()
549550
if (!(blockRegistry as any)[blockId]) {
550551
return null
551552
}

0 commit comments

Comments
 (0)