Skip to content

Commit ca2b57e

Browse files
committed
introduce safeImport
1 parent fa2fb6e commit ca2b57e

File tree

4 files changed

+111
-0
lines changed

4 files changed

+111
-0
lines changed

packages/shared/package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,16 @@
8080
"default": "./dist/runtime/ui/index.js"
8181
}
8282
},
83+
"./import": {
84+
"import": {
85+
"types": "./dist/runtime/import/index.d.mts",
86+
"default": "./dist/runtime/import/index.mjs"
87+
},
88+
"require": {
89+
"types": "./dist/runtime/import/index.d.ts",
90+
"default": "./dist/runtime/import/index.js"
91+
}
92+
},
8393
"./types": {
8494
"import": {
8595
"types": "./dist/types/index.d.mts",
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { describe, expect, test, vi } from 'vitest';
2+
3+
import * as retryModule from '../../retry';
4+
import { safeImport } from '../index';
5+
6+
describe('safeImport', () => {
7+
test('calls retry with correct configuration', async () => {
8+
const retrySpy = vi.spyOn(retryModule, 'retry');
9+
const testModule = './test-module';
10+
11+
try {
12+
await safeImport(testModule);
13+
} catch {
14+
// Ignore import errors since we're just testing the retry configuration
15+
}
16+
17+
expect(retrySpy).toHaveBeenCalledWith(
18+
expect.any(Function),
19+
expect.objectContaining({
20+
initialDelay: 100,
21+
retryImmediately: true,
22+
factor: 2,
23+
}),
24+
);
25+
26+
retrySpy.mockRestore();
27+
});
28+
29+
test('returns imported module on success', async () => {
30+
const mockModule = { default: 'test-module', namedExport: 'value' };
31+
32+
// Mock the retry to immediately return our mock module
33+
const retrySpy = vi.spyOn(retryModule, 'retry').mockResolvedValueOnce(mockModule);
34+
35+
const result = await safeImport('./test-module');
36+
37+
expect(result).toBe(mockModule);
38+
expect(retrySpy).toHaveBeenCalledTimes(1);
39+
40+
retrySpy.mockRestore();
41+
});
42+
43+
test('propagates import errors after retries', async () => {
44+
const importError = new Error('Module not found');
45+
46+
// Mock retry to reject with our error
47+
const retrySpy = vi.spyOn(retryModule, 'retry').mockRejectedValueOnce(importError);
48+
49+
await expect(safeImport('./non-existent-module')).rejects.toThrow('Module not found');
50+
51+
retrySpy.mockRestore();
52+
});
53+
54+
test('configures shouldRetry to allow up to 3 retries', async () => {
55+
const retrySpy = vi.spyOn(retryModule, 'retry');
56+
57+
try {
58+
await safeImport('./test-module');
59+
} catch {
60+
// Ignore errors
61+
}
62+
63+
const options = retrySpy.mock.calls[0][1];
64+
const shouldRetry = options?.shouldRetry;
65+
66+
expect(shouldRetry).toBeDefined();
67+
if (shouldRetry) {
68+
// Test the shouldRetry logic
69+
expect(shouldRetry(new Error('test'), 1)).toBe(true); // First retry
70+
expect(shouldRetry(new Error('test'), 2)).toBe(true); // Second retry
71+
expect(shouldRetry(new Error('test'), 3)).toBe(true); // Third retry
72+
expect(shouldRetry(new Error('test'), 4)).toBe(false); // Fourth attempt should not retry
73+
}
74+
75+
retrySpy.mockRestore();
76+
});
77+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { retry } from '../retry';
2+
3+
/**
4+
* Safely imports a module with automatic retries on failure.
5+
* Useful for dynamic imports that might fail due to network issues or temporary loading problems.
6+
* Retries up to 3 times with exponential backoff.
7+
*
8+
* @param modulePath - The path to the module to import
9+
* @returns A promise that resolves to the imported module
10+
*
11+
* @example
12+
* ```typescript
13+
* const module = await safeImport('./my-module');
14+
* ```
15+
*/
16+
export const safeImport = async <T = any>(modulePath: string): Promise<T> => {
17+
return retry(() => import(modulePath) as Promise<T>, {
18+
initialDelay: 100,
19+
shouldRetry: (_, iterations) => iterations <= 3,
20+
retryImmediately: true,
21+
factor: 2,
22+
});
23+
};

packages/shared/tsdown.config.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export default defineConfig(({ watch }) => {
4343
'./src/types/index.ts',
4444
'./src/dom/*.ts',
4545
'./src/ui/index.ts',
46+
'./src/import/index.ts',
4647
'./src/internal/clerk-js/*.ts',
4748
'./src/internal/clerk-js/**/*.ts',
4849
'!./src/**/*.{test,spec}.{ts,tsx}',

0 commit comments

Comments
 (0)