Skip to content

Commit 9f764a1

Browse files
committed
fix(cache): isolate cache keys per provider name and options
1 parent 13fbe3a commit 9f764a1

File tree

3 files changed

+94
-7
lines changed

3 files changed

+94
-7
lines changed

src/index.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Storage } from './cache'
2-
import type { InitializedProvider, Provider, ResolveFontOptions, ResolveFontResult } from './types'
2+
import type { InitializedProvider, Provider, ProviderContext, ResolveFontOptions, ResolveFontResult } from './types'
33
import { createCachedAsyncStorage, memoryStorage } from './cache'
44

55
export * as providers from './providers'
@@ -34,8 +34,12 @@ export const defaultResolveOptions: ResolveFontOptions = {
3434

3535
export async function createUnifont<T extends Provider[]>(providers: T, unifontOptions?: UnifontOptions): Promise<Unifont<T[number]['_name']>> {
3636
const stack: Record<string, InitializedProvider> = {}
37-
const unifontContext = {
38-
storage: createCachedAsyncStorage(unifontOptions?.storage ?? memoryStorage()),
37+
38+
const storageImpl = unifontOptions?.storage ?? memoryStorage()
39+
function createProviderAwareStorage(providerName: string, providerOptions: unknown): ProviderContext['storage'] {
40+
return createCachedAsyncStorage(storageImpl, {
41+
namespace: [providerName, providerOptions],
42+
})
3943
}
4044

4145
// preserve provider order
@@ -46,8 +50,12 @@ export async function createUnifont<T extends Provider[]>(providers: T, unifontO
4650

4751
// initialize all providers in parallel
4852
await Promise.all(providers.map(async (provider) => {
53+
const context: ProviderContext = {
54+
storage: createProviderAwareStorage(provider._name, provider._options),
55+
}
56+
4957
try {
50-
const initializedProvider = await provider(unifontContext)
58+
const initializedProvider = await provider(context)
5159
if (initializedProvider)
5260
stack[provider._name] = initializedProvider
5361
}

test/cache.test.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,16 @@ describe('cache storage', () => {
4646
})
4747
const unifont = await createUnifont([provider()], { storage: customStorage })
4848
await unifont.resolveFont('Poppins')
49-
expect(customStorage.getItem).toHaveBeenCalledWith('key')
50-
expect(customStorage.setItem).toHaveBeenCalledWith('key', expect.objectContaining({ data: 'value' }))
51-
expect(customStorage.setItem).toHaveBeenCalledWith('another-key', expect.objectContaining({ data: 'value' }))
49+
50+
expect(customStorage.getItem).toHaveBeenCalledWith(expect.stringMatching(/key$/))
51+
expect(customStorage.setItem).toHaveBeenCalledWith(
52+
expect.stringMatching(/key$/),
53+
expect.objectContaining({ data: 'value' }),
54+
)
55+
expect(customStorage.setItem).toHaveBeenCalledWith(
56+
expect.stringMatching(/another-key$/),
57+
expect.objectContaining({ data: 'value' }),
58+
)
5259
})
5360

5461
describe('keyFragments option', () => {

test/index.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,76 @@ describe('unifont', () => {
165165
await expect(() => unifont.listFonts()).rejects.toThrow()
166166
})
167167
})
168+
169+
describe('cache isolation', () => {
170+
it('uses isolated namespace per provider\'s name', async () => {
171+
const storage = {
172+
getItem: vi.fn(),
173+
setItem: vi.fn(),
174+
}
175+
176+
const getProvider = (name: string) =>
177+
defineFontProvider(name, async (_options, ctx) => {
178+
return {
179+
async resolveFont() {
180+
await ctx.storage.setItem('key', 'value')
181+
return { fonts: [] }
182+
},
183+
}
184+
})()
185+
186+
const unifontA = await createUnifont([
187+
getProvider('provider-A'),
188+
], { storage })
189+
const unifontB = await createUnifont([
190+
getProvider('provider-B'),
191+
], { storage })
192+
193+
await unifontA.resolveFont('Poppins')
194+
await unifontB.resolveFont('Poppins')
195+
196+
const providerACacheKey = storage.setItem.mock.calls.at(0)?.at(0) as string | undefined
197+
const providerBCacheKey = storage.setItem.mock.calls.at(1)?.at(0) as string | undefined
198+
199+
expect(storage.setItem).toHaveBeenCalledTimes(2)
200+
expect(providerACacheKey).toBeDefined()
201+
expect(providerBCacheKey).toBeDefined()
202+
expect(providerACacheKey).not.toBe(providerBCacheKey)
203+
})
204+
205+
it('uses isolated namespace per provider\'s options', async () => {
206+
const storage = {
207+
getItem: vi.fn(),
208+
setItem: vi.fn(),
209+
}
210+
211+
const getProvider = (options: { variant: string }) =>
212+
defineFontProvider('optioned-provider', async (_options: { variant: string }, ctx) => {
213+
return {
214+
async resolveFont() {
215+
await ctx.storage.setItem('key', 'value')
216+
return { fonts: [] }
217+
},
218+
}
219+
})(options)
220+
221+
const unifontA = await createUnifont([
222+
getProvider({ variant: 'A' }),
223+
], { storage })
224+
const unifontB = await createUnifont([
225+
getProvider({ variant: 'B' }),
226+
], { storage })
227+
228+
await unifontA.resolveFont('Poppins')
229+
await unifontB.resolveFont('Poppins')
230+
231+
const variantACacheKey = storage.setItem.mock.calls.at(0)?.at(0) as string | undefined
232+
const variantBCacheKey = storage.setItem.mock.calls.at(1)?.at(0) as string | undefined
233+
234+
expect(storage.setItem).toHaveBeenCalledTimes(2)
235+
expect(variantACacheKey).toBeDefined()
236+
expect(variantBCacheKey).toBeDefined()
237+
expect(variantACacheKey).not.toBe(variantBCacheKey)
238+
})
239+
})
168240
})

0 commit comments

Comments
 (0)