Skip to content

Commit 249df12

Browse files
committed
feat: add support for traceIds
1 parent 4041fd1 commit 249df12

File tree

5 files changed

+118
-19
lines changed

5 files changed

+118
-19
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,11 @@ interface CachifiedOptions<Value> {
233233
* Default: `undefined`
234234
*/
235235
waitUntil?: (promise: Promise<unknown>) => void;
236+
/**
237+
* Trace ID for debugging, is stored along cache metadata and can be accessed
238+
* in `getFreshValue` and reporter
239+
*/
240+
traceId?: any;
236241
}
237242
```
238243

src/cachified.spec.ts

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ function ignoreNode14<T>(callback: () => T) {
3535
return callback();
3636
}
3737

38+
const anyMetadata = expect.objectContaining({
39+
createdTime: expect.any(Number),
40+
} satisfies CacheMetadata);
41+
3842
let currentTime = 0;
3943
beforeEach(() => {
4044
currentTime = 0;
@@ -1423,7 +1427,7 @@ describe('cachified', () => {
14231427

14241428
expect(values).toEqual(['value-1', 'YOLO!', 'value-3']);
14251429
expect(getValues).toHaveBeenCalledTimes(1);
1426-
expect(getValues).toHaveBeenCalledWith([1, 3]);
1430+
expect(getValues).toHaveBeenCalledWith([1, 3], [anyMetadata, anyMetadata]);
14271431
});
14281432

14291433
it('rejects all values when batch get fails', async () => {
@@ -1469,7 +1473,10 @@ describe('cachified', () => {
14691473

14701474
expect(await valuesP).toEqual(['value-1', 'value-seven']);
14711475
expect(getValues).toHaveBeenCalledTimes(1);
1472-
expect(getValues).toHaveBeenCalledWith([1, 'seven']);
1476+
expect(getValues).toHaveBeenCalledWith(
1477+
[1, 'seven'],
1478+
[anyMetadata, anyMetadata],
1479+
);
14731480
});
14741481

14751482
it('can edit metadata for single batch values', async () => {
@@ -1670,6 +1677,65 @@ describe('cachified', () => {
16701677

16711678
expect(value).toBe('ONE');
16721679
});
1680+
1681+
it('supports trace ids', async () => {
1682+
expect.assertions(6);
1683+
1684+
const cache = new Map<string, CacheEntry>();
1685+
const traceId1 = Symbol();
1686+
const d = new Deferred<string>();
1687+
1688+
const value = cachified({
1689+
cache,
1690+
key: 'test-1',
1691+
ttl: 200,
1692+
traceId: traceId1,
1693+
async getFreshValue({ metadata: { traceId } }) {
1694+
// in getFreshValue
1695+
expect(traceId).toBe(traceId1);
1696+
return d.promise;
1697+
},
1698+
});
1699+
await delay(0);
1700+
1701+
d.resolve('ONE');
1702+
expect(await value).toBe('ONE');
1703+
1704+
// on cache entry
1705+
expect(cache.get('test-1')?.metadata.traceId).toBe(traceId1);
1706+
1707+
const traceId2 = 'some-string-id';
1708+
1709+
// in batch getFreshValues
1710+
const batch = createBatch((freshIndexes, metadata) => {
1711+
expect(metadata[0].traceId).toBe(traceId2);
1712+
return freshIndexes.map((i) => `value-${i}`);
1713+
});
1714+
1715+
const createReporter = jest.fn(() => () => {});
1716+
1717+
await cachified(
1718+
{
1719+
cache,
1720+
key: 'test-2',
1721+
ttl: 200,
1722+
traceId: traceId2,
1723+
getFreshValue: batch.add(1),
1724+
},
1725+
createReporter,
1726+
);
1727+
1728+
expect(cache.get('test-2')?.metadata.traceId).toBe(traceId2);
1729+
1730+
expect(createReporter).toHaveBeenCalledWith(
1731+
expect.objectContaining({
1732+
traceId: traceId2,
1733+
metadata: expect.objectContaining({
1734+
traceId: traceId2,
1735+
}),
1736+
}),
1737+
);
1738+
});
16731739
});
16741740

16751741
function createReporter() {

src/cachified.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
CachifiedOptions,
33
CachifiedOptionsWithSchema,
44
Cache,
5-
CacheEntry,
65
createContext,
76
HANDLE,
87
} from './common';
@@ -16,6 +15,16 @@ import { isExpired } from './isExpired';
1615
// Keys are unique per cache but may be used by multiple caches
1716
const pendingValuesByCache = new WeakMap<Cache, Map<string, any>>();
1817

18+
/**
19+
* Get the internal pending values cache for a given cache
20+
*/
21+
function getPendingValuesCache(cache: Cache) {
22+
if (!pendingValuesByCache.has(cache)) {
23+
pendingValuesByCache.set(cache, new Map());
24+
}
25+
return pendingValuesByCache.get(cache)!;
26+
}
27+
1928
export async function cachified<Value, InternalValue>(
2029
options: CachifiedOptionsWithSchema<Value, InternalValue>,
2130
reporter?: CreateReporter<Value>,
@@ -30,15 +39,7 @@ export async function cachified<Value>(
3039
): Promise<Value> {
3140
const context = createContext(options, reporter);
3241
const { key, cache, forceFresh, report, metadata } = context;
33-
34-
// Register this cache
35-
if (!pendingValuesByCache.has(cache)) {
36-
pendingValuesByCache.set(cache, new Map());
37-
}
38-
const pendingValues: Map<
39-
string,
40-
CacheEntry<Promise<Value>> & { resolve: (value: Value) => void }
41-
> = pendingValuesByCache.get(cache)!;
42+
const pendingValues = getPendingValuesCache(cache);
4243

4344
const hasPendingValue = () => {
4445
return pendingValues.has(key);

src/common.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export interface CacheMetadata {
55
createdTime: number;
66
ttl?: number | null;
77
swr?: number | null;
8+
traceId?: any;
89
/** @deprecated use swr instead */
910
readonly swv?: number | null;
1011
}
@@ -201,6 +202,11 @@ export interface CachifiedOptions<Value> {
201202
* Default: `undefined`
202203
*/
203204
waitUntil?: (promise: Promise<unknown>) => void;
205+
/**
206+
* Trace ID for debugging, is stored along cache metadata and can be accessed
207+
* in `getFreshValue` and reporter
208+
*/
209+
traceId?: any;
204210
}
205211

206212
/* When using a schema validator, a strongly typed getFreshValue is not required
@@ -216,12 +222,13 @@ export type CachifiedOptionsWithSchema<Value, InternalValue> = Omit<
216222
export interface Context<Value>
217223
extends Omit<
218224
Required<CachifiedOptions<Value>>,
219-
'fallbackToCache' | 'reporter' | 'checkValue' | 'swr'
225+
'fallbackToCache' | 'reporter' | 'checkValue' | 'swr' | 'traceId'
220226
> {
221227
checkValue: CheckValue<Value>;
222228
report: Reporter<Value>;
223229
fallbackToCache: number;
224230
metadata: CacheMetadata;
231+
traceId?: any;
225232
}
226233

227234
function validateWithSchema<Value>(
@@ -277,7 +284,11 @@ export function createContext<Value>(
277284
staleRefreshTimeout: 0,
278285
forceFresh: false,
279286
...options,
280-
metadata: createCacheMetaData({ ttl, swr: staleWhileRevalidate }),
287+
metadata: createCacheMetaData({
288+
ttl,
289+
swr: staleWhileRevalidate,
290+
traceId: options.traceId,
291+
}),
281292
waitUntil: options.waitUntil ?? (() => {}),
282293
};
283294

@@ -313,11 +324,13 @@ export function createCacheMetaData({
313324
ttl = null,
314325
swr = 0,
315326
createdTime = Date.now(),
327+
traceId,
316328
}: Partial<Omit<CacheMetadata, 'swv'>> = {}) {
317329
return {
318330
ttl: ttl === Infinity ? null : ttl,
319331
swr: swr === Infinity ? null : swr,
320332
createdTime,
333+
...(traceId ? { traceId } : {}),
321334
};
322335
}
323336

src/createBatch.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import type { GetFreshValue, GetFreshValueContext } from './common';
1+
import type {
2+
CacheMetadata,
3+
GetFreshValue,
4+
GetFreshValueContext,
5+
} from './common';
26
import { HANDLE } from './common';
37

48
type OnValueCallback<Value> = (
@@ -12,20 +16,25 @@ export type AddFn<Value, Param> = (
1216
onValue?: OnValueCallback<Value>,
1317
) => GetFreshValue<Value>;
1418

19+
export type GetFreshValues<Value, Param> = (
20+
params: Param[],
21+
metadata: CacheMetadata[],
22+
) => Value[] | Promise<Value[]>;
23+
1524
export function createBatch<Value, Param>(
16-
getFreshValues: (params: Param[]) => Value[] | Promise<Value[]>,
25+
getFreshValues: GetFreshValues<Value, Param>,
1726
autoSubmit: false,
1827
): {
1928
submit: () => Promise<void>;
2029
add: AddFn<Value, Param>;
2130
};
2231
export function createBatch<Value, Param>(
23-
getFreshValues: (params: Param[]) => Value[] | Promise<Value[]>,
32+
getFreshValues: GetFreshValues<Value, Param>,
2433
): {
2534
add: AddFn<Value, Param>;
2635
};
2736
export function createBatch<Value, Param>(
28-
getFreshValues: (params: Param[]) => Value[] | Promise<Value[]>,
37+
getFreshValues: GetFreshValues<Value, Param>,
2938
autoSubmit: boolean = true,
3039
): {
3140
submit?: () => Promise<void>;
@@ -35,6 +44,7 @@ export function createBatch<Value, Param>(
3544
param: Param,
3645
res: (value: Value) => void,
3746
rej: (reason: unknown) => void,
47+
metadata: CacheMetadata,
3848
][] = [];
3949

4050
let count = 0;
@@ -62,7 +72,10 @@ export function createBatch<Value, Param>(
6272

6373
try {
6474
const results = await Promise.resolve(
65-
getFreshValues(requests.map(([param]) => param)),
75+
getFreshValues(
76+
requests.map(([param]) => param),
77+
requests.map((args) => args[3]),
78+
),
6679
);
6780
results.forEach((value, index) => requests[index][1](value));
6881
submission.resolve();
@@ -97,6 +110,7 @@ export function createBatch<Value, Param>(
97110
res(value);
98111
},
99112
rej,
113+
context.metadata,
100114
]);
101115
if (!handled) {
102116
handled = true;

0 commit comments

Comments
 (0)