registry-system

Auto-generated registry system for this Next.js application. Covers Data-Only pattern, zero dynamic imports policy, registry structure, and rebuild process. Use this skill when working with registries

SKILL.md

GitHub

Registry System Skill

Patterns for working with the auto-generated registry system that provides O(1) lookups for blocks, entities, themes, and more.

Architecture Overview

REGISTRY SYSTEM (Pre-built at build time):

.nextspark/registries/
├── block-registry.ts        # BLOCK_REGISTRY - Page builder blocks
├── entity-registry.ts       # ENTITY_REGISTRY - Entity configurations
├── entity-registry.client.ts # Client-safe entity registry
├── theme-registry.ts        # THEME_REGISTRY - Theme configurations
├── plugin-registry.ts       # PLUGIN_REGISTRY - Plugin configurations
├── namespace-registry.ts    # NAMESPACE_REGISTRY - Route namespaces
├── scope-registry.ts        # SCOPE_REGISTRY - API scopes
├── route-handlers.ts        # API_ROUTES_METADATA - API route documentation metadata (no handlers)
├── middleware-registry.ts   # MIDDLEWARE_REGISTRY - Middleware configs
├── permissions-registry.ts  # PERMISSIONS_REGISTRY - Permission matrix
├── billing-registry.ts      # BILLING_REGISTRY - Plans and billing
├── translation-registry.ts  # TRANSLATION_REGISTRY - i18n configs
├── testing-registry.ts      # TESTING_REGISTRY - Test fixtures
├── docs-registry.ts         # DOCS_REGISTRY - Documentation index
├── icon-registry.ts         # ICON_REGISTRY - Icons referenced by name
└── index.ts                 # Unified exports

Build script: pnpm build:registries

📍 Context-Aware Paths: Registries are auto-generated from core + theme. In consumer projects, theme configs go in config/. Core registries are read-only. See core-theme-responsibilities skill for complete rules.

When to Use This Skill

  • Understanding registry patterns
  • Importing from registries correctly
  • Debugging registry generation issues
  • Working with entity, block, or theme configurations
  • Avoiding dynamic import violations

Key Concepts

Data-Only Pattern

Registries = Data (pre-computed, static)
Services = Logic (methods that use registries)

Registries provide:

  • O(1) lookups by key
  • Type-safe TypeScript interfaces
  • Pre-computed at build time
  • Zero runtime I/O

Services provide:

  • Business logic methods
  • Filtering and querying
  • Validation and transformation

Performance Impact

Approach Notes
Dynamic Import Filesystem I/O on every operation
Static Registry O(1) lookup, no runtime I/O

Import Patterns

Correct Usage

// ✅ CORRECT - Import from registries
import { BLOCK_REGISTRY } from '@nextsparkjs/registries/block-registry'
import { ENTITY_REGISTRY } from '@nextsparkjs/registries/entity-registry'
import { THEME_REGISTRY } from '@nextsparkjs/registries/theme-registry'

// Access by key
const heroBlock = BLOCK_REGISTRY['hero']
const customerEntity = ENTITY_REGISTRY['customers']

// ✅ CORRECT - Use services for logic
import { BlockService } from '@nextsparkjs/core/lib/services/block.service'
import { EntityTypeService } from '@nextsparkjs/core/lib/services/entity-type.service'

const heroConfig = BlockService.getBySlug('hero')
const allEntities = EntityTypeService.getAll()

Forbidden Patterns

// ❌ FORBIDDEN - Direct imports from project source in runtime code
import { hero } from '@/blocks/hero'
import { customerEntityConfig } from '@/entities/customers'
import * as theme from '@/./default'

// ❌ FORBIDDEN - Dynamic imports from project source in runtime code
const module = await import('@/blocks/hero')
const config = await import(`@/config`)

// ❌ FORBIDDEN - Dynamic registry imports
const { ENTITY_REGISTRY } = await import('@nextsparkjs/registries/entity-registry')

Available Registries

Registry Key Type Value Type Purpose
BLOCK_REGISTRY slug BlockConfig Page builder blocks
ENTITY_REGISTRY slug EntityRegistryEntry Entity configurations
THEME_REGISTRY name ThemeConfig Theme settings
PLUGIN_REGISTRY name PluginConfig Plugin configurations
NAMESPACE_REGISTRY namespace NamespaceConfig Route namespaces
SCOPE_REGISTRY scope ScopeConfig API permission scopes
PERMISSIONS_REGISTRY permission PermissionConfig Permission definitions
BILLING_REGISTRY planSlug PlanConfig Billing plans
MIDDLEWARE_REGISTRY name MiddlewareConfig Route middlewares
API_ROUTES_METADATA core / entities / theme / plugins ApiRouteEntry[] What the API Explorer and devtools list; imports no handler
ICON_REGISTRY icon name LucideIcon Icons a config names by string

Registries that hold components

TEMPLATE_REGISTRY and BLOCK_COMPONENTS bind their components through a deferred import, not a static one: a barrel of static imports puts every template (or block) in the bundle of any route that touches the registry. The registry module itself is still imported statically — the data stays O(1) — only the component behind each key is loaded when it renders.

ICON_REGISTRY is the opposite case: icons are small and there are many lookups per render, so it imports them statically, and the generator emits only the names resolveIcon can actually be handed — entity configs, a theme's sidebar sections, block configs (see discovery/icons.mjs). Icons named by page content are outside it by design: that set lives in the database, and the only thing that resolves it is lucide's by-name loader, which declares a chunk per icon and so charges every route in the app for the few that render a block.

Registry Structure

Typical Registry File

/**
 * Auto-generated Entity Registry
 * Generated at: 2025-12-30T21:56:23.717Z
 * Entities discovered: 4
 * DO NOT EDIT - This file is auto-generated by packages/core/scripts/build/registry.mjs
 */

// 1. Static imports (generated)
import { customerEntityConfig } from '@/entities/customers/customers.config'
import { taskEntityConfig } from '@/entities/tasks/tasks.config'

// 2. TypeScript interface
export interface EntityRegistryEntry {
  name: string
  config: EntityConfig
  tableName?: string
  relativePath: string
  depth: number
  parent: string | null
  children: string[]
  source: 'core' | 'theme' | 'plugin'
}

// 3. Main registry object
export const ENTITY_REGISTRY: Record<string, EntityRegistryEntry> = {
  'customers': {
    name: 'customers',
    config: customerEntityConfig,
    tableName: 'customers',
    relativePath: 'customers',
    depth: 0,
    parent: null,
    children: [],
    source: 'theme'
  },
  // ... more entries
}

// 4. Type export
export type EntityName = keyof typeof ENTITY_REGISTRY

// 5. Metadata
export const ENTITY_METADATA = {
  totalEntities: 4,
  generatedAt: '2025-12-30T21:56:23.717Z',
  entities: ['customers', 'pages', 'posts', 'tasks']
}

Rebuild Command

# One-time build (default)
pnpm build:registries

# Watch mode (auto-rebuild on changes)
pnpm exec nextspark prepare --watch

When to rebuild:

  • After adding/removing entities
  • After adding/removing blocks
  • After modifying entity configs
  • After changing theme configurations
  • After adding/removing plugins

Allowed Exceptions

1. i18n/Translations (Lazy-load per locale)

const messages = await import(`@nextsparkjs/core/messages/${locale}/${namespace}.json`)

2. React Code-Splitting (>100KB components)

const HeavyChart = lazy(() => import('./HeavyChart'))

3. Type-Only Imports (Eliminated at compile)

export type Messages = typeof import('./es/index.ts').default

4. Build Scripts Only (Development)

// core/scripts/build-theme.ts
const config = await import(`./${themeName}/theme.config`)

5. Development Tools (NODE_ENV gated)

if (process.env.NODE_ENV === 'development') {
  const benchmark = await import('./dev-tools/benchmark')
}

6. Heavy User-Triggered Libraries (>500KB)

export async function extractTextFromPDF(file: File) {
  const pdfjsLib = await import('pdfjs-dist')  // ~2MB, only when needed
}

Using Services with Registries

BlockService

import { BlockService } from '@nextsparkjs/core/lib/services/block.service'

// Get block by slug
const hero = BlockService.getBySlug('hero')

// Get all blocks
const blocks = BlockService.getAll()

// Get blocks by category
const heroBlocks = BlockService.getByCategory('hero')

// Get blocks by scope
const pageBlocks = BlockService.getByScope('pages')

EntityTypeService

import { EntityTypeService } from '@nextsparkjs/core/lib/services/entity-type.service'

// Get entity config
const customerConfig = EntityTypeService.getBySlug('customers')

// Get all entities
const entities = EntityTypeService.getAll()

// Check if entity exists
const exists = EntityTypeService.exists('customers')

Anti-Patterns

// NEVER: Import directly from contents
import { hero } from '@/blocks/hero'

// CORRECT: Use registry
import { BLOCK_REGISTRY } from '@nextsparkjs/registries/block-registry'
const hero = BLOCK_REGISTRY['hero']

// NEVER: Dynamic import from contents
const block = await import(`@/blocks/${slug}`)

// CORRECT: Use service
import { BlockService } from '@nextsparkjs/core/lib/services/block.service'
const block = BlockService.getBySlug(slug)

// NEVER: Modify registry files manually
// Registry files have "DO NOT EDIT" header

// CORRECT: Modify source files and rebuild
// pnpm build:registries

// NEVER: Store logic in registries
export const ENTITY_REGISTRY = {
  customers: {
    validate: (data) => { ... }  // Logic doesn't belong here!
  }
}

// CORRECT: Logic goes in services
export class CustomerService {
  static validate(data: CreateCustomer) { ... }
}

Checklist

Before working with registries:

  • Importing generated data from @nextsparkjs/registries/
  • Using services for logic, registries for data
  • Rebuilding after config changes (pnpm build:registries)
  • Not modifying auto-generated registry files
  • Using typed registry keys (e.g., ENTITY_REGISTRY['customers'])
  • Checking registry exists before accessing (or using services)

Related Skills

  • monorepo-architecture - Package hierarchy, dependency rules, Model B distribution
  • entity-system - Entity configuration patterns
  • page-builder-blocks - Block configuration patterns
  • service-layer - Service patterns with registries