-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
effect-tsEffect-TS patterns and usageEffect-TS patterns and usagelow-hanging-fruitEasy wins and quick improvementsEasy wins and quick improvementspkg:cliIssues related to @effect-migrate/cli packageIssues related to @effect-migrate/cli packagepriority:lowLow priorityLow prioritytype:featureNew feature or requestNew feature or request
Description
Summary
Use Effect.cached to memoize preset loading, avoiding redundant dynamic imports when the same preset is referenced multiple times.
Problem
If a user's config references the same preset multiple times (or if multiple commands load the same config), the preset is imported fresh each time:
// Current implementation
const loadPreset = (name: string): Effect.Effect<Preset, PresetLoadError> =>
Effect.gen(function* () {
const module = yield* Effect.tryPromise({
try: () => import(name), // Fresh import every time
catch: error => new PresetLoadError({ preset: name, message: String(error) })
})
// ...
})Inefficiency: Each call to loadPreset("@effect-migrate/preset-basic") re-imports the module, even though module loading is inherently cacheable.
Proposed Solution
Use Effect.cached to memoize preset loading per preset name:
import { Cache, Effect } from "effect"
// Create a cache with preset name as key
const presetCache = Cache.make({
capacity: 100, // Max number of presets to cache
timeToLive: "infinity", // Presets don't change during CLI run
lookup: loadPresetUncached
})
const loadPresetUncached = (name: string): Effect.Effect<Preset, PresetLoadError> =>
Effect.gen(function* () {
const module = yield* Effect.tryPromise({
try: () => import(name),
catch: error => new PresetLoadError({ preset: name, message: String(error) })
})
const preset = module.default ?? module.preset
if (!isValidPreset(preset)) {
return yield* Effect.fail(
new PresetLoadError({
preset: name,
message: "Invalid preset shape: must have 'rules' array"
})
)
}
return preset
})
// Public API
export const loadPreset = (name: string): Effect.Effect<Preset, PresetLoadError> =>
Effect.gen(function* () {
const cache = yield* presetCache
return yield* cache.get(name)
})Alternative: Simple Memoization
For simpler approach without Cache:
import { Effect } from "effect"
const loadPresetEffect = (name: string): Effect.Effect<Preset, PresetLoadError> =>
Effect.gen(function* () {
const module = yield* Effect.tryPromise({
try: () => import(name),
catch: error => new PresetLoadError({ preset: name, message: String(error) })
})
// ... validation
})
// Memoize with Effect.cached
const cachedLoadPreset = Effect.cached(loadPresetEffect)
export const loadPreset = (name: string) => cachedLoadPreset(name)Benefits
- Performance: Avoid redundant imports
- Consistency: Same preset instance used throughout CLI run
- Memory efficient: Cache bounded by number of unique presets (small)
Use Cases
- Multiple commands:
auditandmetricsboth load config → same preset imported twice - Config reload: If config is reloaded during watch mode
- Testing: Faster test runs when same preset used across tests
Acceptance Criteria
- Preset loading memoized per name
- Cache bounded (max 100 presets)
- First load imports module, subsequent loads use cache
- All existing tests pass
- Performance test added (verify cache hits)
Measurement
Before/after benchmark:
it.effect("should cache preset loads", () =>
Effect.gen(function* () {
const start1 = Date.now()
yield* loadPreset("@effect-migrate/preset-basic")
const duration1 = Date.now() - start1
const start2 = Date.now()
yield* loadPreset("@effect-migrate/preset-basic") // Should be cached
const duration2 = Date.now() - start2
expect(duration2).toBeLessThan(duration1 * 0.1) // 10x faster
})
)Related
- Uses
Cachefromeffect/CacheorEffect.cached - Complements feat(cli): add Effect.timeout to preset imports to prevent hanging #34 (timeout protection)
- Thread: https://ampcode.com/threads/T-09db5199-2c22-42fd-9730-3a6f1c42e570
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
effect-tsEffect-TS patterns and usageEffect-TS patterns and usagelow-hanging-fruitEasy wins and quick improvementsEasy wins and quick improvementspkg:cliIssues related to @effect-migrate/cli packageIssues related to @effect-migrate/cli packagepriority:lowLow priorityLow prioritytype:featureNew feature or requestNew feature or request