TypeScript Architecture
Overview
nuxt-smartscript uses a two-type architecture to handle the boundary between Nuxt's module system and the runtime environment. This design pattern is necessary due to how Nuxt transforms and passes configuration between build-time and runtime.
The Two-Type System
1. ModuleOptions (User-Facing)
Located in src/module.ts
, this interface defines what users can configure in their nuxt.config.ts
:
export interface ModuleOptions {
enabled?: boolean
debug?: boolean
ssr?: boolean | 'force'
client?: boolean
positioning?: {
trademark?: { body?: string; headers?: string }
// ... other positioning options
}
transformations?: {
trademark?: boolean
registered?: boolean
// ... other transformations
}
cssVariables?: Record<string, string>
// ... other options
}
Key characteristics:
- All properties are optional (users only specify what they want to override)
- Designed for developer ergonomics
- Validated and merged with defaults by Nuxt
2. SuperscriptConfig (Runtime)
Located in src/runtime/smartscript/types.ts
, this interface defines the complete configuration used internally:
export interface SuperscriptConfig {
enabled: boolean // Note: NOT optional
debug: boolean
ssr: boolean | 'force'
client: boolean
symbols: { // Note: Additional runtime property
trademark: string[]
registered: string[]
copyright: string[]
ordinals: boolean
}
// ... all properties are required
}
Key characteristics:
- All properties are required (runtime always has complete config)
- Includes computed properties like
symbols
derived fromtransformations
- Used by all processing functions
The Module Boundary
Why We Need any
At the boundary between module and runtime (src/module.ts
line ~224), we have:
// The options are merged with defaults by Nuxt, so all required fields are present
// eslint-disable-next-line ts/no-explicit-any
nuxt.options.runtimeConfig.public.smartscript = options as any
This any
is intentional and correct because:
- Nuxt's transformation is opaque to TypeScript - The module system merges user options with defaults, but TypeScript can't track this
- Runtime transformation happens - Properties like
symbols
are computed fromtransformations
- The boundary is dynamic - The transformation happens at runtime, not compile time
For Module Contributors
When Working on Configuration
- User-facing changes go in
ModuleOptions
(src/module.ts) - Runtime changes go in
SuperscriptConfig
(src/runtime/smartscript/types.ts) - Keep them in sync - If you add a property to one, consider if it needs to be in the other
When Working on Processing Functions
- Always use
SuperscriptConfig
- neverModuleOptions
- Trust that the config is complete (all required fields are present)
- Don't add optional checks for required config properties
Type Safety Best Practices
// ✅ GOOD: Use SuperscriptConfig in runtime code
function processContent(config: SuperscriptConfig) {
if (config.debug) { // No need for config.debug ?? false
logger.info('Processing...')
}
}
// ❌ BAD: Don't use ModuleOptions in runtime code
function processContent(config: ModuleOptions) {
if (config.debug ?? false) { // Unnecessary defaulting
logger.info('Processing...')
}
}
For Module Users
You don't need to know about this! The two-type system is an internal implementation detail. You only interact with ModuleOptions
in your nuxt.config.ts
:
export default defineNuxtConfig({
modules: ['@mitre/nuxt-smartscript'],
smartscript: {
// All properties are optional - defaults are provided
debug: true,
transformations: {
trademark: false // Disable only trademark transformation
}
}
})
Why This Architecture?
- Better Developer Experience - Users get optional properties with IntelliSense
- Runtime Safety - Processing functions get guaranteed complete configs
- Nuxt Compatibility - Works with Nuxt's module transformation system
- Type Safety - Maximum type safety where TypeScript can help
Related Files
src/module.ts
- ModuleOptions interface and module setupsrc/runtime/smartscript/types.ts
- SuperscriptConfig interfacesrc/runtime/smartscript/config.ts
- Config merging and defaultssrc/runtime/types.ts
- RuntimeConfig interface (bridge type)