memory-journal-mcp 7.0.1 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -66
- package/dist/{chunk-6J4RPJ4I.js → chunk-GR4T3SRW.js} +146 -105
- package/dist/{chunk-ARLH46WS.js → chunk-IWKLHSPU.js} +89 -3
- package/dist/{chunk-2BJHLTYP.js → chunk-ORV7ZZOE.js} +1086 -86
- package/dist/cli.js +30 -4
- package/dist/github-integration-2TFMXHIJ.js +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +3 -3
- package/dist/{tools-FFFGXIKN.js → tools-CXR2FEB2.js} +2 -2
- package/package.json +2 -2
- package/skills/README.md +77 -0
- package/skills/autonomous-dev/SKILL.md +56 -0
- package/skills/bin/sync.js +50 -0
- package/skills/bun/SKILL.md +156 -0
- package/skills/github-commander/SKILL.md +1 -1
- package/skills/github-commander/workflows/code-quality-audit.md +7 -5
- package/skills/github-commander/workflows/issue-triage.md +13 -4
- package/skills/github-commander/workflows/milestone-sprint.md +9 -1
- package/skills/github-commander/workflows/perf-audit.md +2 -0
- package/skills/github-commander/workflows/pr-review.md +9 -3
- package/skills/github-commander/workflows/roadmap-kickoff.md +79 -0
- package/skills/github-commander/workflows/security-audit.md +3 -3
- package/skills/github-commander/workflows/update-deps.md +2 -2
- package/skills/gitlab/SKILL.md +115 -0
- package/skills/gitlab/package-lock.json +392 -0
- package/skills/gitlab/package.json +14 -0
- package/skills/gitlab/scripts/gitlab-client.ts +125 -0
- package/skills/gitlab/scripts/gitlab-helper.ts +80 -0
- package/skills/golang/SKILL.md +54 -0
- package/skills/mysql/SKILL.md +30 -0
- package/skills/package.json +48 -0
- package/skills/playwright-standard/SKILL.md +58 -0
- package/skills/playwright-standard/examples/fixtures.ts +66 -0
- package/skills/playwright-standard/examples/type-stubs.d.ts +10 -0
- package/skills/playwright-standard/references/advanced-scenarios.md +59 -0
- package/skills/playwright-standard/references/infrastructure.md +43 -0
- package/skills/postgres/SKILL.md +33 -0
- package/skills/react-best-practices/AGENTS.md +2883 -0
- package/skills/react-best-practices/README.md +127 -0
- package/skills/react-best-practices/SKILL.md +138 -0
- package/skills/react-best-practices/metadata.json +17 -0
- package/skills/react-best-practices/rules/_sections.md +46 -0
- package/skills/react-best-practices/rules/_template.md +28 -0
- package/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/skills/react-best-practices/rules/advanced-init-once.md +42 -0
- package/skills/react-best-practices/rules/advanced-use-latest.md +39 -0
- package/skills/react-best-practices/rules/async-api-routes.md +35 -0
- package/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/skills/react-best-practices/rules/async-dependencies.md +48 -0
- package/skills/react-best-practices/rules/async-parallel.md +24 -0
- package/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/skills/react-best-practices/rules/bundle-conditional.md +37 -0
- package/skills/react-best-practices/rules/bundle-defer-third-party.md +48 -0
- package/skills/react-best-practices/rules/bundle-dynamic-imports.md +34 -0
- package/skills/react-best-practices/rules/bundle-preload.md +44 -0
- package/skills/react-best-practices/rules/client-event-listeners.md +78 -0
- package/skills/react-best-practices/rules/client-localstorage-schema.md +74 -0
- package/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/skills/react-best-practices/rules/js-batch-dom-css.md +110 -0
- package/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/skills/react-best-practices/rules/js-cache-storage.md +68 -0
- package/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/skills/react-best-practices/rules/js-length-check-first.md +50 -0
- package/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/skills/react-best-practices/rules/rendering-activity.md +24 -0
- package/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +38 -0
- package/skills/react-best-practices/rules/rendering-conditional-render.md +32 -0
- package/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/skills/react-best-practices/rules/rendering-hoist-jsx.md +36 -0
- package/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +72 -0
- package/skills/react-best-practices/rules/rendering-hydration-suppress-warning.md +26 -0
- package/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/skills/react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/skills/react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/skills/react-best-practices/rules/rerender-functional-setstate.md +77 -0
- package/skills/react-best-practices/rules/rerender-lazy-state-init.md +56 -0
- package/skills/react-best-practices/rules/rerender-memo-with-default-value.md +36 -0
- package/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/skills/react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/skills/react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/skills/react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/skills/react-best-practices/rules/server-auth-actions.md +96 -0
- package/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/skills/react-best-practices/rules/server-cache-react.md +76 -0
- package/skills/react-best-practices/rules/server-dedup-props.md +65 -0
- package/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/skills/rust/SKILL.md +86 -0
- package/skills/shadcn-ui/SKILL.md +72 -0
- package/skills/skill-builder/SKILL.md +457 -0
- package/skills/skill-builder/checklist.md +65 -0
- package/skills/sqlite/SKILL.md +38 -0
- package/skills/typescript/SKILL.md +453 -0
- package/skills/typescript/assets/eslint-template.js +102 -0
- package/skills/typescript/assets/tsconfig-template.json +45 -0
- package/skills/typescript/references/enterprise-patterns.md +531 -0
- package/skills/typescript/references/generics.md +493 -0
- package/skills/typescript/references/nestjs-integration.md +579 -0
- package/skills/typescript/references/react-integration.md +616 -0
- package/skills/typescript/references/toolchain.md +547 -0
- package/skills/typescript/references/type-system.md +481 -0
- package/skills/vitest-standard/SKILL.md +82 -0
- package/skills/vitest-standard/examples/service-mock.ts +60 -0
- package/skills/vitest-standard/examples/tdd-calculator.ts +41 -0
- package/skills/vitest-standard/examples/type-stubs.d.ts +18 -0
- package/skills/vitest-standard/references/async-and-errors.md +58 -0
- package/skills/vitest-standard/references/coverage-and-config.md +53 -0
- package/skills/vitest-standard/references/mocking.md +61 -0
- package/skills/vitest-standard/references/tdd-patterns.md +60 -0
- package/dist/github-integration-PDRLXKGM.js +0 -1
- package/skills/github-commander/workflows/full-audit.md +0 -134
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
# Enterprise Patterns Reference
|
|
2
|
+
|
|
3
|
+
> **Load when:** User asks about error handling, validation, project architecture, migration strategies, or large-scale TypeScript patterns.
|
|
4
|
+
|
|
5
|
+
Proven patterns for building maintainable TypeScript applications.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
- [Error Handling](#error-handling)
|
|
10
|
+
- [Validation Patterns](#validation-patterns)
|
|
11
|
+
- [Project Organization](#project-organization)
|
|
12
|
+
- [Migration Strategies](#migration-strategies)
|
|
13
|
+
- [Security Patterns](#security-patterns)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Error Handling
|
|
18
|
+
|
|
19
|
+
### Result Type Pattern
|
|
20
|
+
|
|
21
|
+
Instead of throwing exceptions, return typed results:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// Define Result type
|
|
25
|
+
type Result<T, E = Error> = { success: true; data: T } | { success: false; error: E }
|
|
26
|
+
|
|
27
|
+
// Helper functions
|
|
28
|
+
function ok<T>(data: T): Result<T, never> {
|
|
29
|
+
return { success: true, data }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function err<E>(error: E): Result<never, E> {
|
|
33
|
+
return { success: false, error }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Usage
|
|
37
|
+
interface ValidationError {
|
|
38
|
+
field: string
|
|
39
|
+
message: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseEmail(input: string): Result<string, ValidationError> {
|
|
43
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
44
|
+
|
|
45
|
+
if (!emailRegex.test(input)) {
|
|
46
|
+
return err({ field: 'email', message: 'Invalid email format' })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return ok(input.toLowerCase())
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Consuming Result
|
|
53
|
+
const result = parseEmail(userInput)
|
|
54
|
+
|
|
55
|
+
if (result.success) {
|
|
56
|
+
console.log(`Valid email: ${result.data}`)
|
|
57
|
+
} else {
|
|
58
|
+
console.error(`Error in ${result.error.field}: ${result.error.message}`)
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Typed Error Classes
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// Base application error
|
|
66
|
+
abstract class AppError extends Error {
|
|
67
|
+
abstract readonly code: string
|
|
68
|
+
abstract readonly statusCode: number
|
|
69
|
+
|
|
70
|
+
constructor(message: string) {
|
|
71
|
+
super(message)
|
|
72
|
+
this.name = this.constructor.name
|
|
73
|
+
Error.captureStackTrace(this, this.constructor)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Specific error types
|
|
78
|
+
class NotFoundError extends AppError {
|
|
79
|
+
readonly code = 'NOT_FOUND'
|
|
80
|
+
readonly statusCode = 404
|
|
81
|
+
|
|
82
|
+
constructor(resource: string, id: string) {
|
|
83
|
+
super(`${resource} with id ${id} not found`)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
class ValidationError extends AppError {
|
|
88
|
+
readonly code = 'VALIDATION_ERROR'
|
|
89
|
+
readonly statusCode = 400
|
|
90
|
+
|
|
91
|
+
constructor(
|
|
92
|
+
message: string,
|
|
93
|
+
public readonly fields: Record<string, string[]>
|
|
94
|
+
) {
|
|
95
|
+
super(message)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
class UnauthorizedError extends AppError {
|
|
100
|
+
readonly code = 'UNAUTHORIZED'
|
|
101
|
+
readonly statusCode = 401
|
|
102
|
+
|
|
103
|
+
constructor(message = 'Authentication required') {
|
|
104
|
+
super(message)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Type guard for app errors
|
|
109
|
+
function isAppError(error: unknown): error is AppError {
|
|
110
|
+
return error instanceof AppError
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Error handler
|
|
114
|
+
function handleError(error: unknown): { status: number; body: object } {
|
|
115
|
+
if (isAppError(error)) {
|
|
116
|
+
return {
|
|
117
|
+
status: error.statusCode,
|
|
118
|
+
body: {
|
|
119
|
+
code: error.code,
|
|
120
|
+
message: error.message,
|
|
121
|
+
...(error instanceof ValidationError && { fields: error.fields }),
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.error('Unexpected error:', error)
|
|
127
|
+
return {
|
|
128
|
+
status: 500,
|
|
129
|
+
body: { code: 'INTERNAL_ERROR', message: 'Internal server error' },
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Validation Patterns
|
|
137
|
+
|
|
138
|
+
### Zod Schema Validation
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { z } from 'zod'
|
|
142
|
+
|
|
143
|
+
// Define schemas
|
|
144
|
+
const UserSchema = z.object({
|
|
145
|
+
id: z.string().uuid(),
|
|
146
|
+
name: z.string().min(1).max(100),
|
|
147
|
+
email: z.string().email(),
|
|
148
|
+
age: z.number().int().min(0).max(150).optional(),
|
|
149
|
+
role: z.enum(['user', 'admin', 'moderator']),
|
|
150
|
+
metadata: z.record(z.string()).optional(),
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// Infer TypeScript type
|
|
154
|
+
type User = z.infer<typeof UserSchema>
|
|
155
|
+
|
|
156
|
+
// Create DTO schemas
|
|
157
|
+
const CreateUserSchema = UserSchema.omit({ id: true })
|
|
158
|
+
type CreateUserDto = z.infer<typeof CreateUserSchema>
|
|
159
|
+
|
|
160
|
+
const UpdateUserSchema = UserSchema.partial().omit({ id: true })
|
|
161
|
+
type UpdateUserDto = z.infer<typeof UpdateUserSchema>
|
|
162
|
+
|
|
163
|
+
// Validation functions
|
|
164
|
+
function validateCreateUser(data: unknown): Result<CreateUserDto, z.ZodError> {
|
|
165
|
+
const result = CreateUserSchema.safeParse(data)
|
|
166
|
+
|
|
167
|
+
if (result.success) {
|
|
168
|
+
return ok(result.data)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return err(result.error)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Transform Zod errors to user-friendly format
|
|
175
|
+
function formatZodError(error: z.ZodError): Record<string, string[]> {
|
|
176
|
+
const formatted: Record<string, string[]> = {}
|
|
177
|
+
|
|
178
|
+
for (const issue of error.issues) {
|
|
179
|
+
const path = issue.path.join('.')
|
|
180
|
+
if (!formatted[path]) {
|
|
181
|
+
formatted[path] = []
|
|
182
|
+
}
|
|
183
|
+
formatted[path].push(issue.message)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return formatted
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Branded Types for Validation
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// Branded/Nominal types
|
|
194
|
+
declare const EmailBrand: unique symbol
|
|
195
|
+
type Email = string & { readonly [EmailBrand]: true }
|
|
196
|
+
|
|
197
|
+
declare const UserIdBrand: unique symbol
|
|
198
|
+
type UserId = string & { readonly [UserIdBrand]: true }
|
|
199
|
+
|
|
200
|
+
// Validation functions that return branded types
|
|
201
|
+
function validateEmail(input: string): Email {
|
|
202
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
203
|
+
if (!emailRegex.test(input)) {
|
|
204
|
+
throw new ValidationError('Invalid email', { email: ['Invalid format'] })
|
|
205
|
+
}
|
|
206
|
+
return input as Email
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function validateUserId(input: string): UserId {
|
|
210
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
211
|
+
if (!uuidRegex.test(input)) {
|
|
212
|
+
throw new ValidationError('Invalid user ID', { id: ['Must be UUID'] })
|
|
213
|
+
}
|
|
214
|
+
return input as UserId
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Usage: Functions require validated types
|
|
218
|
+
function sendEmail(to: Email, subject: string): void {
|
|
219
|
+
// to is guaranteed to be valid email
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function getUser(id: UserId): Promise<User> {
|
|
223
|
+
// id is guaranteed to be valid UUID
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Compiler enforces validation
|
|
227
|
+
sendEmail('invalid', 'Hello') // Error: string not assignable to Email
|
|
228
|
+
sendEmail(validateEmail('a@b.com'), 'Hello') // OK
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Project Organization
|
|
234
|
+
|
|
235
|
+
### Feature-Based Structure
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
src/
|
|
239
|
+
├── features/
|
|
240
|
+
│ ├── users/
|
|
241
|
+
│ │ ├── index.ts # Public exports (barrel)
|
|
242
|
+
│ │ ├── user.types.ts # Types and interfaces
|
|
243
|
+
│ │ ├── user.schema.ts # Zod schemas
|
|
244
|
+
│ │ ├── user.service.ts # Business logic
|
|
245
|
+
│ │ ├── user.repository.ts # Data access
|
|
246
|
+
│ │ ├── user.controller.ts # HTTP handlers
|
|
247
|
+
│ │ └── __tests__/
|
|
248
|
+
│ │ ├── user.service.test.ts
|
|
249
|
+
│ │ └── user.controller.test.ts
|
|
250
|
+
│ ├── auth/
|
|
251
|
+
│ │ ├── index.ts
|
|
252
|
+
│ │ ├── auth.types.ts
|
|
253
|
+
│ │ └── ...
|
|
254
|
+
│ └── posts/
|
|
255
|
+
│ └── ...
|
|
256
|
+
├── shared/
|
|
257
|
+
│ ├── types/
|
|
258
|
+
│ │ ├── result.ts
|
|
259
|
+
│ │ └── pagination.ts
|
|
260
|
+
│ ├── utils/
|
|
261
|
+
│ │ ├── validation.ts
|
|
262
|
+
│ │ └── date.ts
|
|
263
|
+
│ └── errors/
|
|
264
|
+
│ └── app-error.ts
|
|
265
|
+
├── infrastructure/
|
|
266
|
+
│ ├── database/
|
|
267
|
+
│ │ └── client.ts
|
|
268
|
+
│ ├── cache/
|
|
269
|
+
│ │ └── redis.ts
|
|
270
|
+
│ └── logging/
|
|
271
|
+
│ └── logger.ts
|
|
272
|
+
└── config/
|
|
273
|
+
├── index.ts
|
|
274
|
+
└── env.ts
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Barrel Exports
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
// features/users/index.ts
|
|
281
|
+
export type { User, CreateUserDto, UpdateUserDto } from './user.types'
|
|
282
|
+
export { UserSchema, CreateUserSchema } from './user.schema'
|
|
283
|
+
export { UserService } from './user.service'
|
|
284
|
+
export { UserController } from './user.controller'
|
|
285
|
+
|
|
286
|
+
// Don't export repository (internal detail)
|
|
287
|
+
// Don't export internal helper functions
|
|
288
|
+
|
|
289
|
+
// Usage in other modules
|
|
290
|
+
import { User, UserService } from '@/features/users'
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Path Aliases
|
|
294
|
+
|
|
295
|
+
```json
|
|
296
|
+
// tsconfig.json
|
|
297
|
+
{
|
|
298
|
+
"compilerOptions": {
|
|
299
|
+
"baseUrl": ".",
|
|
300
|
+
"paths": {
|
|
301
|
+
"@/*": ["src/*"],
|
|
302
|
+
"@features/*": ["src/features/*"],
|
|
303
|
+
"@shared/*": ["src/shared/*"],
|
|
304
|
+
"@config/*": ["src/config/*"]
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Migration Strategies
|
|
313
|
+
|
|
314
|
+
### Incremental Migration from JavaScript
|
|
315
|
+
|
|
316
|
+
**Phase 1: Enable TypeScript alongside JavaScript**
|
|
317
|
+
|
|
318
|
+
```json
|
|
319
|
+
// tsconfig.json
|
|
320
|
+
{
|
|
321
|
+
"compilerOptions": {
|
|
322
|
+
"allowJs": true,
|
|
323
|
+
"checkJs": false,
|
|
324
|
+
"outDir": "./dist",
|
|
325
|
+
"target": "ES2022",
|
|
326
|
+
"module": "ESNext",
|
|
327
|
+
"moduleResolution": "bundler",
|
|
328
|
+
"strict": false,
|
|
329
|
+
"noImplicitAny": false
|
|
330
|
+
},
|
|
331
|
+
"include": ["src/**/*"]
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**Phase 2: Rename files gradually**
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
# Convert one file at a time
|
|
339
|
+
mv src/utils/helpers.js src/utils/helpers.ts
|
|
340
|
+
|
|
341
|
+
# Add minimal type annotations
|
|
342
|
+
# Fix any type errors
|
|
343
|
+
# Run tests to verify
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**Phase 3: Enable stricter checks incrementally**
|
|
347
|
+
|
|
348
|
+
```json
|
|
349
|
+
// Progression of strict options
|
|
350
|
+
{
|
|
351
|
+
"compilerOptions": {
|
|
352
|
+
// Step 1: Basic strictness
|
|
353
|
+
"noImplicitAny": true,
|
|
354
|
+
|
|
355
|
+
// Step 2: Null safety
|
|
356
|
+
"strictNullChecks": true,
|
|
357
|
+
|
|
358
|
+
// Step 3: Full strict mode
|
|
359
|
+
"strict": true,
|
|
360
|
+
|
|
361
|
+
// Step 4: Extra safety (optional)
|
|
362
|
+
"noUncheckedIndexedAccess": true,
|
|
363
|
+
"exactOptionalPropertyTypes": true
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### JSDoc for Gradual Typing
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
// Before full migration, use JSDoc
|
|
372
|
+
/**
|
|
373
|
+
* @typedef {Object} User
|
|
374
|
+
* @property {string} id
|
|
375
|
+
* @property {string} name
|
|
376
|
+
* @property {string} email
|
|
377
|
+
*/
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Find user by ID
|
|
381
|
+
* @param {string} id - User ID
|
|
382
|
+
* @returns {Promise<User | null>}
|
|
383
|
+
*/
|
|
384
|
+
async function findUser(id) {
|
|
385
|
+
// implementation
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* @template T
|
|
390
|
+
* @param {T[]} items
|
|
391
|
+
* @returns {T | undefined}
|
|
392
|
+
*/
|
|
393
|
+
function first(items) {
|
|
394
|
+
return items[0]
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### CommonJS to ESM Migration
|
|
399
|
+
|
|
400
|
+
```json
|
|
401
|
+
// package.json
|
|
402
|
+
{
|
|
403
|
+
"type": "module"
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
// Before (CommonJS)
|
|
409
|
+
const express = require('express')
|
|
410
|
+
const { UserService } = require('./user.service')
|
|
411
|
+
module.exports = { router }
|
|
412
|
+
|
|
413
|
+
// After (ESM)
|
|
414
|
+
import express from 'express'
|
|
415
|
+
import { UserService } from './user.service.js' // Note .js extension
|
|
416
|
+
export { router }
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## Security Patterns
|
|
422
|
+
|
|
423
|
+
### Input Sanitization
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
import { z } from 'zod'
|
|
427
|
+
import DOMPurify from 'isomorphic-dompurify'
|
|
428
|
+
|
|
429
|
+
// Sanitized string schema
|
|
430
|
+
const SanitizedString = z.string().transform((val) => {
|
|
431
|
+
return DOMPurify.sanitize(val.trim())
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
// HTML content schema (for rich text)
|
|
435
|
+
const HtmlContentSchema = z.string().transform((val) => {
|
|
436
|
+
return DOMPurify.sanitize(val, {
|
|
437
|
+
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
|
|
438
|
+
ALLOWED_ATTR: ['href', 'target'],
|
|
439
|
+
})
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
// SQL-safe identifier
|
|
443
|
+
const SafeIdentifierSchema = z.string().regex(/^[a-zA-Z_][a-zA-Z0-9_]*$/, 'Invalid identifier')
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Type-Safe Environment Variables
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
import { z } from 'zod'
|
|
450
|
+
|
|
451
|
+
const EnvSchema = z.object({
|
|
452
|
+
NODE_ENV: z.enum(['development', 'production', 'test']),
|
|
453
|
+
PORT: z.coerce.number().default(3000),
|
|
454
|
+
DATABASE_URL: z.string().url(),
|
|
455
|
+
JWT_SECRET: z.string().min(32),
|
|
456
|
+
REDIS_URL: z.string().url().optional(),
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
// Validate on startup
|
|
460
|
+
function loadEnv() {
|
|
461
|
+
const result = EnvSchema.safeParse(process.env)
|
|
462
|
+
|
|
463
|
+
if (!result.success) {
|
|
464
|
+
console.error('Invalid environment variables:')
|
|
465
|
+
console.error(result.error.format())
|
|
466
|
+
process.exit(1)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return result.data
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export const env = loadEnv()
|
|
473
|
+
|
|
474
|
+
// Usage: fully typed
|
|
475
|
+
env.PORT // number
|
|
476
|
+
env.NODE_ENV // "development" | "production" | "test"
|
|
477
|
+
env.REDIS_URL // string | undefined
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Secure API Response Types
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
// Never expose internal fields
|
|
484
|
+
interface InternalUser {
|
|
485
|
+
id: string
|
|
486
|
+
name: string
|
|
487
|
+
email: string
|
|
488
|
+
passwordHash: string
|
|
489
|
+
internalNotes: string
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Public API response (Pick only safe fields)
|
|
493
|
+
type PublicUser = Pick<InternalUser, 'id' | 'name' | 'email'>
|
|
494
|
+
|
|
495
|
+
// Or explicitly define
|
|
496
|
+
interface UserResponse {
|
|
497
|
+
id: string
|
|
498
|
+
name: string
|
|
499
|
+
email: string
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Transform function
|
|
503
|
+
function toPublicUser(user: InternalUser): UserResponse {
|
|
504
|
+
return {
|
|
505
|
+
id: user.id,
|
|
506
|
+
name: user.name,
|
|
507
|
+
email: user.email,
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### Rate Limiting Types
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
interface RateLimitConfig {
|
|
516
|
+
windowMs: number
|
|
517
|
+
maxRequests: number
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
interface RateLimitResult {
|
|
521
|
+
allowed: boolean
|
|
522
|
+
remaining: number
|
|
523
|
+
resetAt: Date
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const rateLimits: Record<string, RateLimitConfig> = {
|
|
527
|
+
api: { windowMs: 60000, maxRequests: 100 },
|
|
528
|
+
auth: { windowMs: 300000, maxRequests: 5 },
|
|
529
|
+
upload: { windowMs: 3600000, maxRequests: 10 },
|
|
530
|
+
} as const satisfies Record<string, RateLimitConfig>
|
|
531
|
+
```
|