Skip to content

Commit ba12ed5

Browse files
committed
docs: add TypeScript architecture documentation
- Create comprehensive TypeScript architecture guide - Document the two-type system (ModuleOptions vs SuperscriptConfig) - Explain why 'any' is needed at the module boundary - Update architecture.md with TypeScript section - Add notes for contributors in README and contributing.md - Make clear users don't need to know about internal types This documentation helps contributors understand the intentional design decisions around type handling in the module. Authored by: Aaron Lippold<lippold@gmail.com>
1 parent 5320453 commit ba12ed5

File tree

4 files changed

+154
-0
lines changed

4 files changed

+154
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ pnpm prepack
146146
pnpm lint
147147
```
148148

149+
**For Contributors**: This module uses a two-type system (ModuleOptions for users, SuperscriptConfig for runtime) to handle Nuxt's configuration transformation. See [Contributing Guide](./docs/contributing.md) and [TypeScript Architecture](./docs/contributing/typescript-architecture.md) for details.
150+
149151
## License
150152

151153
[Apache-2.0](./LICENSE.md) - MITRE Corporation

docs/architecture.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,15 @@ export default defineNuxtConfig({
220220
- **Performance**: Slightly slower than regex (worth it for accuracy)
221221
- **Complexity**: More setup code for SSR
222222

223+
## TypeScript Architecture
224+
225+
The module uses a **two-type system** to handle the boundary between Nuxt's module configuration and runtime processing:
226+
227+
1. **ModuleOptions** - What users configure (all properties optional)
228+
2. **SuperscriptConfig** - What runtime uses (all properties required)
229+
230+
This design provides the best developer experience while maintaining type safety. For details, see [TypeScript Architecture](./contributing/typescript-architecture.md).
231+
223232
## Future Enhancements
224233

225234
Potential improvements while maintaining architecture:

docs/contributing.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ Before/after screenshots for visual changes
143143
- Keep functions small and focused
144144
- Add JSDoc comments for public APIs
145145
- Use descriptive variable names
146+
- Understand the [TypeScript Architecture](./contributing/typescript-architecture.md) for config types
146147

147148
## 🐛 Bug Reports
148149

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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

Comments
 (0)