|
| 1 | +# TypeScript Architecture |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +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. |
| 6 | + |
| 7 | +## The Two-Type System |
| 8 | + |
| 9 | +### 1. ModuleOptions (User-Facing) |
| 10 | + |
| 11 | +Located in `src/module.ts`, this interface defines what users can configure in their `nuxt.config.ts`: |
| 12 | + |
| 13 | +```typescript |
| 14 | +export interface ModuleOptions { |
| 15 | + enabled?: boolean |
| 16 | + debug?: boolean |
| 17 | + ssr?: boolean | 'force' |
| 18 | + client?: boolean |
| 19 | + positioning?: { |
| 20 | + trademark?: { body?: string; headers?: string } |
| 21 | + // ... other positioning options |
| 22 | + } |
| 23 | + transformations?: { |
| 24 | + trademark?: boolean |
| 25 | + registered?: boolean |
| 26 | + // ... other transformations |
| 27 | + } |
| 28 | + cssVariables?: Record<string, string> |
| 29 | + // ... other options |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +**Key characteristics:** |
| 34 | +- All properties are **optional** (users only specify what they want to override) |
| 35 | +- Designed for developer ergonomics |
| 36 | +- Validated and merged with defaults by Nuxt |
| 37 | + |
| 38 | +### 2. SuperscriptConfig (Runtime) |
| 39 | + |
| 40 | +Located in `src/runtime/smartscript/types.ts`, this interface defines the complete configuration used internally: |
| 41 | + |
| 42 | +```typescript |
| 43 | +export interface SuperscriptConfig { |
| 44 | + enabled: boolean // Note: NOT optional |
| 45 | + debug: boolean |
| 46 | + ssr: boolean | 'force' |
| 47 | + client: boolean |
| 48 | + symbols: { // Note: Additional runtime property |
| 49 | + trademark: string[] |
| 50 | + registered: string[] |
| 51 | + copyright: string[] |
| 52 | + ordinals: boolean |
| 53 | + } |
| 54 | + // ... all properties are required |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +**Key characteristics:** |
| 59 | +- All properties are **required** (runtime always has complete config) |
| 60 | +- Includes computed properties like `symbols` derived from `transformations` |
| 61 | +- Used by all processing functions |
| 62 | + |
| 63 | +## The Module Boundary |
| 64 | + |
| 65 | +### Why We Need `any` |
| 66 | + |
| 67 | +At the boundary between module and runtime (`src/module.ts` line ~224), we have: |
| 68 | + |
| 69 | +```typescript |
| 70 | +// The options are merged with defaults by Nuxt, so all required fields are present |
| 71 | +// eslint-disable-next-line ts/no-explicit-any |
| 72 | +nuxt.options.runtimeConfig.public.smartscript = options as any |
| 73 | +``` |
| 74 | + |
| 75 | +This `any` is **intentional and correct** because: |
| 76 | + |
| 77 | +1. **Nuxt's transformation is opaque to TypeScript** - The module system merges user options with defaults, but TypeScript can't track this |
| 78 | +2. **Runtime transformation happens** - Properties like `symbols` are computed from `transformations` |
| 79 | +3. **The boundary is dynamic** - The transformation happens at runtime, not compile time |
| 80 | + |
| 81 | +## For Module Contributors |
| 82 | + |
| 83 | +### When Working on Configuration |
| 84 | + |
| 85 | +1. **User-facing changes** go in `ModuleOptions` (src/module.ts) |
| 86 | +2. **Runtime changes** go in `SuperscriptConfig` (src/runtime/smartscript/types.ts) |
| 87 | +3. **Keep them in sync** - If you add a property to one, consider if it needs to be in the other |
| 88 | + |
| 89 | +### When Working on Processing Functions |
| 90 | + |
| 91 | +- Always use `SuperscriptConfig` - never `ModuleOptions` |
| 92 | +- Trust that the config is complete (all required fields are present) |
| 93 | +- Don't add optional checks for required config properties |
| 94 | + |
| 95 | +### Type Safety Best Practices |
| 96 | + |
| 97 | +```typescript |
| 98 | +// ✅ GOOD: Use SuperscriptConfig in runtime code |
| 99 | +function processContent(config: SuperscriptConfig) { |
| 100 | + if (config.debug) { // No need for config.debug ?? false |
| 101 | + logger.info('Processing...') |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +// ❌ BAD: Don't use ModuleOptions in runtime code |
| 106 | +function processContent(config: ModuleOptions) { |
| 107 | + if (config.debug ?? false) { // Unnecessary defaulting |
| 108 | + logger.info('Processing...') |
| 109 | + } |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +## For Module Users |
| 114 | + |
| 115 | +**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`: |
| 116 | + |
| 117 | +```typescript |
| 118 | +export default defineNuxtConfig({ |
| 119 | + modules: ['@mitre/nuxt-smartscript'], |
| 120 | + smartscript: { |
| 121 | + // All properties are optional - defaults are provided |
| 122 | + debug: true, |
| 123 | + transformations: { |
| 124 | + trademark: false // Disable only trademark transformation |
| 125 | + } |
| 126 | + } |
| 127 | +}) |
| 128 | +``` |
| 129 | + |
| 130 | +## Why This Architecture? |
| 131 | + |
| 132 | +1. **Better Developer Experience** - Users get optional properties with IntelliSense |
| 133 | +2. **Runtime Safety** - Processing functions get guaranteed complete configs |
| 134 | +3. **Nuxt Compatibility** - Works with Nuxt's module transformation system |
| 135 | +4. **Type Safety** - Maximum type safety where TypeScript can help |
| 136 | + |
| 137 | +## Related Files |
| 138 | + |
| 139 | +- `src/module.ts` - ModuleOptions interface and module setup |
| 140 | +- `src/runtime/smartscript/types.ts` - SuperscriptConfig interface |
| 141 | +- `src/runtime/smartscript/config.ts` - Config merging and defaults |
| 142 | +- `src/runtime/types.ts` - RuntimeConfig interface (bridge type) |
0 commit comments