Skip to content

Kody is live

Watch the launch video — what Kody is, and why it exists.

← Public packages

@kentcdodds/package-app-kit

Design tokens, PWA install/update, About/version, cache helpers, and optional realtime notes sync for Kody package apps.

src/server-cache.ts

112 lines · 3.1 KB · TypeScript
/**
 * @epic-web/cachified factory backed by packageStorage for Worker/package exports.
 */

import { cachified, type Cache, type CacheEntry } from '@epic-web/cachified'
import { packageStorage } from 'kody:runtime'

export type PackageStorageCacheOptions = {
	/** Key prefix inside packageStorage (default `cache:`). */
	prefix?: string
	/** Optional cache name for reporters. */
	name?: string
}

/**
 * Build a cachified `Cache` backed by this package's `packageStorage()`.
 * Use for Worker-side / package-export caching (not browser IndexedDB).
 *
 * @example
 * import { createPackageStorageCache } from 'kody:@kentcdodds/package-app-kit/server-cache'
 * const cache = createPackageStorageCache({ prefix: 'status:' })
 */
export function createPackageStorageCache(
	options: PackageStorageCacheOptions = {},
): Cache {
	const prefix = options.prefix ?? 'cache:'
	const name = options.name ?? 'packageStorage'
	return {
		name,
		async get(key) {
			const raw = await packageStorage().get(prefix + key)
			if (raw == null) return null
			if (typeof raw === 'string') {
				try {
					return JSON.parse(raw) as CacheEntry
				} catch {
					return null
				}
			}
			if (typeof raw === 'object') return raw as CacheEntry
			return null
		},
		async set(key, value) {
			await packageStorage().set(prefix + key, value)
		},
		async delete(key) {
			await packageStorage().delete(prefix + key)
		},
	}
}

export type CreateServerCachifiedInput<Value> = {
	key: string
	getFreshValue: () => Promise<Value> | Value
	ttl?: number
	swr?: number
	cache?: Cache
	prefix?: string
}

/**
 * One-shot cachified call using a packageStorage-backed cache.
 * Prefer `createPackageStorageCache` + `cachified` when sharing one cache across keys.
 *
 * @example
 * import { serverCachified } from 'kody:@kentcdodds/package-app-kit/server-cache'
 * const status = await serverCachified({
 *   key: 'court-status',
 *   ttl: 15_000,
 *   getFreshValue: async () => ({ ok: true }),
 * })
 */
export async function serverCachified<Value>(
	input: CreateServerCachifiedInput<Value>,
): Promise<Value> {
	const cache = input.cache ?? createPackageStorageCache({ prefix: input.prefix })
	return await cachified({
		key: input.key,
		cache,
		ttl: input.ttl ?? 60_000,
		swr: input.swr ?? 0,
		getFreshValue: input.getFreshValue,
	})
}

/**
 * Factory that binds a shared packageStorage cache for repeated cachified calls.
 *
 * @example
 * import { createServerCachified } from 'kody:@kentcdodds/package-app-kit/server-cache'
 * const cached = createServerCachified({ prefix: 'api:' })
 * const value = await cached({ key: 'hello', ttl: 10_000, getFreshValue: async () => 'hi' })
 */
export function createServerCachified(options: PackageStorageCacheOptions = {}) {
	const cache = createPackageStorageCache(options)
	return async function run<Value>(input: {
		key: string
		getFreshValue: () => Promise<Value> | Value
		ttl?: number
		swr?: number
	}): Promise<Value> {
		return await serverCachified({ ...input, cache })
	}
}

/**
 * Server-cache helpers overview.
 *
 */

/** Primary callable export for this subpath. */
export default serverCachified