Skip to content

Commit

Permalink
chore: document service-primitives.ts
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanhopperlowe committed Jan 31, 2025
1 parent 889c57a commit eeb999d
Showing 1 changed file with 51 additions and 10 deletions.
61 changes: 51 additions & 10 deletions ui/admin/app/lib/service/api/service-primitives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,30 @@ type FetcherConfig = {
signal?: AbortSignal;
};

export const RevalidateSkip = Symbol("RevalidateSkip");
/**
* Allows us to skip matching on specific key segments
* @example
* revalidateArray(["Agents", SkipKey])
* // will revalidate the following keys:
* ["Agents", "1234"]
* ["Agents", "5678"]
* // but not:
* ["Agents", "1234", "Threads"]
* ["Agents", "1234", "Threads", "5678"]
*
* // If exact is false:
* revalidateArray(["Agents", SkipKey], false)
* // will also revalidate the following keys:
* ["Agents", "1234", "Threads"]
* ["Agents", "1234", "Threads", "5678"]
*/
export const SkipKey = Symbol("SkipKey");

/**
* Revalidates all keys that match the given key
* @param key - The key to match. Use SkipKey to skip matching on specific segments
* @param exact - Whether the key must match exactly
*/
export const revalidateArray = <TKey extends unknown[]>(
key: TKey,
exact = true
Expand All @@ -15,11 +37,18 @@ export const revalidateArray = <TKey extends unknown[]>(
if (!Array.isArray(cacheKey)) return false;

return (
key.every((k, i) => [cacheKey[i], RevalidateSkip].includes(k)) &&
key.every((k, i) => [cacheKey[i], SkipKey].includes(k)) &&
(!exact || cacheKey.length === key.length)
);
});

/**
* Creates a fetcher for a given API function
* @param input - The input schema
* @param handler - The API function
* @param key - The function that generates the UNIQUE key for the given params. This should include all dependencies of the method
* @returns The fetcher
*/
export const createFetcher = <
TInput extends ZodObject<ZodRawShape>,
TParams extends z.infer<TInput>,
Expand All @@ -32,28 +61,32 @@ export const createFetcher = <
) => {
type KeyParams = NullishPartial<TParams>;

const buildKey = (params: KeyParams) => {
const { data } = input.safeParse(params);
return data ? key(data as TParams) : null;
};
/** Creates a closure to trigger abort controller on consecutive requests */
let abortController: AbortController;

// this schema will skip any parameters that would cause an error
const skippedSchema = z.object(
Object.fromEntries(
Object.entries(input.shape).map(([key, schema]) => [
key,
schema.catch(RevalidateSkip),
// this means that if a parameter would cause an error, it will be skipped
schema.catch(SkipKey),
])
)
);

// this function will return null if the params are invalid
// SWR will not call the handler if the key is null
const buildKey = (params: KeyParams) => {
const { data } = input.safeParse(params);
return data ? key(data as TParams) : null;
};

const buildRevalidator = (params: KeyParams, exact?: boolean) => {
const data = skippedSchema.parse(params);
revalidateArray(key(data as TParams), exact);
};

// create a closure to trigger abort controller on consecutive requests
let abortController: AbortController;

const handleFetch = (params: TParams, config: FetcherConfig) => {
abortController?.abort();

Expand All @@ -64,9 +97,11 @@ export const createFetcher = <
return {
handler,
key,
/** Creates a SWR key and fetcher for the given params. This works for both `useSWR` and `prefetch` from SWR */
swr: (params: KeyParams, config: FetcherConfig = {}) => {
return [
buildKey(params),
// casting (params as TParams) is safe here because handleFetch will never be called when params are invalid
() => handleFetch(params as TParams, config),
] as const;
},
Expand All @@ -75,9 +110,15 @@ export const createFetcher = <
};
};

/**
* Creates a mutator for a given API function
* @param fn - The API function
* @returns The mutator
*/
export const createMutator = <TInput extends object, TResponse>(
fn: (params: TInput, config: FetcherConfig) => Promise<TResponse>
) => {
/** Creates a closure to trigger abort controller on consecutive requests */
let abortController: AbortController;

return (params: TInput, config: FetcherConfig = {}) => {
Expand Down

0 comments on commit eeb999d

Please sign in to comment.