Loading...
Loading...
Type-safe development patterns for JARVIS AI Assistant
npx skill4agent add martinholovsky/claude-skills-generator typescriptFile Organization: This skill uses split structure. Seefor advanced patterns and security examples.references/
unknownanyreadonlyas const| Package | Version | Security Notes |
|---|---|---|
| typescript | ^5.3.0 | Latest stable with improved type inference |
| zod | ^3.22.0 | Runtime validation, schema-first |
| @types/node | ^20.0.0 | Match Node.js version |
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"exactOptionalPropertyTypes": true,
"noPropertyAccessFromIndexSignature": true,
"forceConsistentCasingInFileNames": true,
"verbatimModuleSyntax": true,
"moduleResolution": "bundler",
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"]
}
}// types/branded.ts
declare const __brand: unique symbol
type Brand<T, B> = T & { [__brand]: B }
// ✅ Prevent accidental mixing of IDs
export type UserId = Brand<string, 'UserId'>
export type SessionId = Brand<string, 'SessionId'>
export type CommandId = Brand<string, 'CommandId'>
// Factory functions with validation
export function createUserId(id: string): UserId {
if (!/^usr_[a-zA-Z0-9]{16}$/.test(id)) {
throw new Error('Invalid user ID format')
}
return id as UserId
}
// ✅ Type system prevents mixing IDs
function getUser(id: UserId): User { /* ... */ }
function getSession(id: SessionId): Session { /* ... */ }
// This won't compile:
// getUser(sessionId) // Error: SessionId not assignable to UserId// types/jarvis-state.ts
// ✅ Type-safe state machine
type JARVISState =
| { status: 'idle' }
| { status: 'listening'; startTime: number }
| { status: 'processing'; commandId: CommandId }
| { status: 'responding'; response: string; confidence: number }
| { status: 'error'; error: JARVISError; retryCount: number }
// ✅ Exhaustive handling
function handleState(state: JARVISState): string {
switch (state.status) {
case 'idle':
return 'Ready'
case 'listening':
return `Listening for ${Date.now() - state.startTime}ms`
case 'processing':
return `Processing ${state.commandId}`
case 'responding':
return `${state.response} (${state.confidence}%)`
case 'error':
return `Error: ${state.error.message}`
default:
// ✅ Compile-time exhaustiveness check
const _exhaustive: never = state
return _exhaustive
}
}// schemas/command.ts
import { z } from 'zod'
// ✅ Schema-first approach
export const commandSchema = z.object({
id: z.string().regex(/^cmd_[a-zA-Z0-9]{16}$/),
action: z.enum(['navigate', 'control', 'query', 'configure']),
target: z.string().min(1).max(100),
parameters: z.record(z.unknown()).optional(),
timestamp: z.number().int().positive(),
priority: z.number().min(0).max(10).default(5)
})
// ✅ Infer TypeScript type from schema
export type Command = z.infer<typeof commandSchema>
// ✅ Parse with full validation
export function parseCommand(data: unknown): Command {
return commandSchema.parse(data)
}
// ✅ Safe parse for error handling
export function tryParseCommand(data: unknown): Command | null {
const result = commandSchema.safeParse(data)
return result.success ? result.data : null
}// ✅ Generic with constraints - ensures type safety for metrics
interface MetricConfig<T extends Record<string, number>> {
metrics: T
thresholds: { [K in keyof T]: { warning: number; critical: number } }
}
type SystemMetrics = { cpu: number; memory: number }
const config: MetricConfig<SystemMetrics> = {
metrics: { cpu: 45, memory: 72 },
thresholds: {
cpu: { warning: 70, critical: 90 },
memory: { warning: 80, critical: 95 }
}
}// ✅ Type predicate for safe narrowing
function isSuccessResponse<T>(
response: APIResponse<T>
): response is APIResponse<T> & { success: true; data: T } {
return response.success && response.data !== undefined
}
// Usage - type automatically narrowed
if (isSuccessResponse(response)) {
return response.data // ✅ Type is T, not T | undefined
}// types/utilities.ts
// ✅ Deep readonly for immutable state
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]
}
// ✅ Make specific keys required
type RequireKeys<T, K extends keyof T> = T & Required<Pick<T, K>>
// ✅ Extract event payloads
type EventPayload<T> = T extends { payload: infer P } ? P : never
// ✅ Async function return type
type AsyncReturnType<T extends (...args: any[]) => Promise<any>> =
T extends (...args: any[]) => Promise<infer R> ? R : never// tests/utils/command-parser.test.ts
import { describe, it, expect } from 'vitest'
import { parseCommand } from '@/utils/command-parser'
describe('parseCommand', () => {
it('should parse valid command', () => {
expect(parseCommand('open settings')).toEqual({
action: 'open', target: 'settings', parameters: {}
})
})
it('should extract parameters', () => {
expect(parseCommand('set volume to 80')).toEqual({
action: 'set', target: 'volume', parameters: { value: 80 }
})
})
it('should throw on empty command', () => {
expect(() => parseCommand('')).toThrow('Command cannot be empty')
})
})npx vitest run # Unit tests
npx eslint . --ext .ts,.tsx # Linting
npx tsc --noEmit # Type checking// ❌ BAD - Recalculates on every render
const processed = data.map(item => heavyTransform(item))
// ✅ GOOD - Memoized computation
import { computed } from 'vue'
const processed = computed(() => data.value.map(item => heavyTransform(item)))// ❌ BAD - Loads everything upfront
import { HeavyChart } from '@/components/HeavyChart'
// ✅ GOOD - Lazy load heavy components
import { defineAsyncComponent } from 'vue'
const HeavyChart = defineAsyncComponent(() => import('@/components/HeavyChart'))// ❌ BAD - API call on every keystroke
const handleSearch = (q: string) => fetchResults(q)
// ✅ GOOD - Debounced search (300ms delay)
import { useDebounceFn } from '@vueuse/core'
const debouncedSearch = useDebounceFn((q: string) => fetchResults(q), 300)// ❌ BAD - O(n) lookup
const user = users.find(u => u.id === id)
// ✅ GOOD - O(1) lookup with Map
const userMap = new Map(users.map(u => [u.id, u]))
const user = userMap.get(id)
// ✅ GOOD - O(1) membership check with Set
const allowed = new Set(['read', 'write'])
const hasAccess = allowed.has(permission)// ❌ BAD - Sequential (total = sum of times)
const user = await fetchUser()
const metrics = await fetchMetrics()
// ✅ GOOD - Parallel (total = max of times)
const [user, metrics] = await Promise.all([fetchUser(), fetchMetrics()])
// ✅ GOOD - With error handling
const results = await Promise.allSettled([fetchUser(), fetchMetrics()])| Risk Area | Description | Mitigation |
|---|---|---|
| Type Erasure | Types don't exist at runtime | Use Zod for external data validation |
| Any Type | Disables type checking | Enable noImplicitAny, use unknown |
| Type Assertions | Can bypass type system | Avoid |
| OWASP Category | TypeScript Mitigation |
|---|---|
| A03 Injection | Typed APIs prevent string interpolation in queries |
| A04 Insecure Design | Strong typing enforces secure interfaces |
| A08 Software Integrity | Compile-time checks catch errors before deployment |
// ❌ DANGEROUS - Type assertion bypasses safety
const userData = apiResponse as User
// ✅ SECURE - Runtime validation
const userData = userSchema.parse(apiResponse)
// ❌ DANGEROUS - any disables all checks
function process(data: any) { /* ... */ }
// ✅ SECURE - unknown requires narrowing
function process(data: unknown) {
const validated = commandSchema.parse(data)
// Now safely typed
}// Type testing with vitest
import { expectTypeOf } from 'vitest'
describe('Type Safety', () => {
it('should enforce branded types', () => {
expectTypeOf(createUserId('usr_1234567890123456')).toEqualTypeOf<UserId>()
})
})
// Schema validation tests
describe('Command Schema', () => {
it('should reject invalid IDs', () => {
expect(() => commandSchema.parse({ id: 'invalid' })).toThrow()
})
it('should accept valid commands', () => {
const result = commandSchema.parse({ id: 'cmd_1234567890123456', action: 'query' })
expect(result.action).toBe('query')
})
})// ❌ DANGEROUS - No runtime validation
const user = JSON.parse(data) as User
// ✅ SECURE - Validate at runtime
const user = userSchema.parse(JSON.parse(data))// ❌ DANGEROUS - Runtime crash if undefined
function getConfig(key: string) {
return config[key].value // May be undefined!
}
// ✅ SECURE - Explicit null handling
function getConfig(key: string): string | undefined {
return config[key]?.value
}// ❌ DANGEROUS - No type safety
const handlers: Record<string, Handler> = {}
handlers['cmd'].execute() // May be undefined!
// ✅ SECURE - Explicit lookup with check
const handler = handlers['cmd']
if (handler) {
handler.execute()
}// ❌ BAD - Unreadable, slow compilation
type DeepPartialRecord<T> = T extends object
? { [K in keyof T]?: DeepPartialRecord<T[K]> }
: T extends Array<infer U>
? Array<DeepPartialRecord<U>>
: T
// ✅ GOOD - Simple, clear types
interface PartialConfig {
theme?: Partial<ThemeConfig>
metrics?: Partial<MetricsConfig>
}strict: truenoUncheckedIndexedAccess: truenoImplicitAny: trueanynpx vitest runnpx eslint .npx tsc --noEmitunknownreferences/advanced-patterns.mdreferences/security-examples.md