Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions examples/caching-demo.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env node

// Demonstration of the new caching functionality for non-specific versions

import { use } from '../use.mjs';
import { promises as fs } from 'fs';
import { tmpdir } from 'os';
import path from 'path';

const getCacheFile = async (packageName, version) => {
const cacheDir = path.join(tmpdir(), 'use-m-cache');
const cacheKey = `${packageName.replace('@', '').replace('/', '-')}-${version}`;
const cachePath = path.join(cacheDir, `${cacheKey}.json`);
try {
const data = await fs.readFile(cachePath, 'utf8');
return JSON.parse(data);
} catch {
return null;
}
};

console.log('=== Use-m Caching Demo ===\n');

console.log('1. First call to lodash@latest (this will fetch from npm and cache the result):');
const start1 = Date.now();
const _ = await use('lodash@latest');
const end1 = Date.now();
console.log(` Time taken: ${end1 - start1}ms`);
console.log(` Lodash version works: ${_.VERSION}`);

const cacheData = await getCacheFile('lodash', 'latest');
if (cacheData) {
console.log(` ✓ Cache created: resolved version ${cacheData.resolvedVersion}`);
console.log(` ✓ Cache timestamp: ${new Date(cacheData.timestamp).toISOString()}`);
} else {
console.log(' ✗ Cache was not created');
}

console.log('\n2. Second call to lodash@latest (this should use cached version and be faster):');
const start2 = Date.now();
const _2 = await use('lodash@latest');
const end2 = Date.now();
console.log(` Time taken: ${end2 - start2}ms`);
console.log(` Same lodash version: ${_2.VERSION}`);

const cacheData2 = await getCacheFile('lodash', 'latest');
if (cacheData2 && cacheData && cacheData2.timestamp === cacheData.timestamp) {
console.log(` ✓ Cache was reused (timestamp unchanged)`);
} else {
console.log(' ✗ Cache was not reused');
}

console.log('\n3. Specific version like [email protected] (this should NOT be cached):');
const start3 = Date.now();
const _3 = await use('[email protected]');
const end3 = Date.now();
console.log(` Time taken: ${end3 - start3}ms`);
console.log(` Lodash version: ${_3.VERSION}`);

const cacheData3 = await getCacheFile('lodash', '4.17.21');
if (!cacheData3) {
console.log(` ✓ Specific version was NOT cached (as expected)`);
} else {
console.log(' ✗ Specific version was cached (unexpected)');
}

console.log('\n=== Summary ===');
console.log('- Latest versions (like @latest) are now cached for 5 minutes by default');
console.log('- Specific versions (like @4.17.21) are not cached');
console.log('- This solves the issue where @latest would be fetched every time');
console.log('- Future versions can extend caching to major version ranges like @4');

console.log('\nDemonstration complete! 🎉');
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

129 changes: 129 additions & 0 deletions tests/caching.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { describe, test, expect } from '../test-adapter.mjs';
import { use } from 'use-m';
import { promises as fs } from 'fs';
import { tmpdir } from 'os';
import path from 'path';

const moduleName = `[${import.meta.url.split('.').pop()} module]`;

// Helper to clean cache directory
const cleanCache = async () => {
try {
const cacheDir = path.join(tmpdir(), 'use-m-cache');
await fs.rmdir(cacheDir, { recursive: true });
} catch (error) {
// Ignore if cache dir doesn't exist
}
};

// Helper to check if cache file exists
const getCacheFile = async (packageName, version) => {
const cacheDir = path.join(tmpdir(), 'use-m-cache');
const cacheKey = `${packageName.replace('@', '').replace('/', '-')}-${version}`;
const cachePath = path.join(cacheDir, `${cacheKey}.json`);
try {
const data = await fs.readFile(cachePath, 'utf8');
return JSON.parse(data);
} catch {
return null;
}
};

describe(`${moduleName} Version caching functionality`, () => {
test(`${moduleName} Should cache latest version resolution`, async () => {
// Clean cache before test
await cleanCache();

// First call should create cache
const _ = await use('lodash@latest');

// Check that cache file was created
const cacheData = await getCacheFile('lodash', 'latest');
expect(cacheData).toBeTruthy();
expect(cacheData.packageName).toBe('lodash');
expect(cacheData.requestedVersion).toBe('latest');
expect(cacheData.resolvedVersion).toMatch(/^\d+\.\d+\.\d+$/);
expect(typeof cacheData.timestamp).toBe('number');

// Verify lodash works
expect(_.add(1, 2)).toBe(3);
});

test(`${moduleName} Should use cached latest version on subsequent calls`, async () => {
// Clean cache before test
await cleanCache();

// First call to cache the version
await use('lodash@latest');
const firstCacheData = await getCacheFile('lodash', 'latest');

// Wait a small amount to ensure timestamp difference
await new Promise(resolve => setTimeout(resolve, 10));

// Second call should use cached version
await use('lodash@latest');
const secondCacheData = await getCacheFile('lodash', 'latest');

// Cache timestamp should not have changed
expect(secondCacheData.timestamp).toBe(firstCacheData.timestamp);
expect(secondCacheData.resolvedVersion).toBe(firstCacheData.resolvedVersion);
}, 10000);

test(`${moduleName} Should not cache specific versions`, async () => {
// Clean cache before test
await cleanCache();

// Use a specific version
await use('[email protected]');

// Check that no cache file was created
const cacheData = await getCacheFile('lodash', '4.17.21');
expect(cacheData).toBeNull();
});

test(`${moduleName} Should cache latest version`, async () => {
// Clean cache before test
await cleanCache();

// Use latest version
await use('lodash@latest');

// Check that cache file was created
const cacheData = await getCacheFile('lodash', 'latest');
expect(cacheData).toBeTruthy();
expect(cacheData.packageName).toBe('lodash');
expect(cacheData.requestedVersion).toBe('latest');
expect(cacheData.resolvedVersion).toMatch(/^\d+\.\d+\.\d+$/);
});

test(`${moduleName} Should expire cache after timeout`, async () => {
// This is a conceptual test - in practice, we'd need to mock Date.now()
// or set a very short timeout to test expiration without waiting 5 minutes

// Clean cache before test
await cleanCache();

// Use a package with latest version (which gets cached)
await use('lodash@latest');
const cacheData = await getCacheFile('lodash', 'latest');
expect(cacheData).toBeTruthy();

// Manually modify cache timestamp to simulate expiration
const expiredCacheData = {
...cacheData,
timestamp: Date.now() - (6 * 60 * 1000) // 6 minutes ago
};

const cacheDir = path.join(tmpdir(), 'use-m-cache');
const cacheKey = 'lodash-latest';
const cachePath = path.join(cacheDir, `${cacheKey}.json`);
await fs.writeFile(cachePath, JSON.stringify(expiredCacheData), 'utf8');

// Now use the package again - should refresh the cache
await use('lodash@latest');
const refreshedCacheData = await getCacheFile('lodash', 'latest');

// Timestamp should be updated
expect(refreshedCacheData.timestamp).toBeGreaterThan(expiredCacheData.timestamp);
});
});
Loading
Loading