@timber-js/app 0.2.0-alpha.158 → 0.2.0-alpha.159
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/{actions-BA5tIpVu.js → actions-CBbP1hHO.js} +3 -3
- package/dist/_chunks/{actions-BA5tIpVu.js.map → actions-CBbP1hHO.js.map} +1 -1
- package/dist/_chunks/{build-output-helper-C8q4Zpf5.js → build-output-helper-DF5uScqS.js} +4 -1
- package/dist/_chunks/{build-output-helper-C8q4Zpf5.js.map → build-output-helper-DF5uScqS.js.map} +1 -1
- package/dist/_chunks/cache-api-DsWEYwVZ.js +1120 -0
- package/dist/_chunks/cache-api-DsWEYwVZ.js.map +1 -0
- package/dist/_chunks/{cli-schema-sync-DcmQrzJG.js → cli-schema-sync-Bivx4Rn7.js} +4 -3
- package/dist/_chunks/{cli-schema-sync-DcmQrzJG.js.map → cli-schema-sync-Bivx4Rn7.js.map} +1 -1
- package/dist/_chunks/dist-BA3u1z3W.js +423 -0
- package/dist/_chunks/dist-BA3u1z3W.js.map +1 -0
- package/dist/_chunks/{logger-C5v1IMuh.js → logger-CrRmZMlo.js} +81 -56
- package/dist/_chunks/logger-CrRmZMlo.js.map +1 -0
- package/dist/_chunks/{plugin-context-DUl3dnCF.js → plugin-context-DeAxFRMq.js} +3 -36
- package/dist/_chunks/plugin-context-DeAxFRMq.js.map +1 -0
- package/dist/_chunks/sizeof-lXffQ1Vr.js +35 -0
- package/dist/_chunks/sizeof-lXffQ1Vr.js.map +1 -0
- package/dist/_chunks/{walkers-D5jCGPum.js → walkers-DXbJkeCY.js} +217 -19
- package/dist/_chunks/walkers-DXbJkeCY.js.map +1 -0
- package/dist/adapters/build-output-helper.d.ts.map +1 -1
- package/dist/adapters/cloudflare.d.ts +28 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +143 -9
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/nitro.js +1 -1
- package/dist/cache/cache-api.d.ts +2 -0
- package/dist/cache/cache-api.d.ts.map +1 -1
- package/dist/cache/handler-store.d.ts +2 -0
- package/dist/cache/handler-store.d.ts.map +1 -1
- package/dist/cache/index.d.ts +2 -0
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +3 -186
- package/dist/cache/redis-handler.d.ts +17 -5
- package/dist/cache/redis-handler.d.ts.map +1 -1
- package/dist/cache/singleflight.d.ts +1 -1
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/store.d.ts +53 -0
- package/dist/cache/store.d.ts.map +1 -0
- package/dist/cache/stores/cloudflare-kv.d.ts +38 -0
- package/dist/cache/stores/cloudflare-kv.d.ts.map +1 -0
- package/dist/cache/stores/cloudflare-kv.js +29 -0
- package/dist/cache/stores/cloudflare-kv.js.map +1 -0
- package/dist/cache/stores/memory.d.ts +42 -0
- package/dist/cache/stores/memory.d.ts.map +1 -0
- package/dist/cache/stores/memory.js +90 -0
- package/dist/cache/stores/memory.js.map +1 -0
- package/dist/cache/stores/redis.d.ts +69 -0
- package/dist/cache/stores/redis.d.ts.map +1 -0
- package/dist/cache/stores/redis.js +35 -0
- package/dist/cache/stores/redis.js.map +1 -0
- package/dist/cache/stores/vercel.d.ts +36 -0
- package/dist/cache/stores/vercel.d.ts.map +1 -0
- package/dist/cache/stores/vercel.js +33 -0
- package/dist/cache/stores/vercel.js.map +1 -0
- package/dist/cache/tag-aware-handler.d.ts +51 -0
- package/dist/cache/tag-aware-handler.d.ts.map +1 -0
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/cdn/cloudflare-purge.d.ts +23 -0
- package/dist/cdn/cloudflare-purge.d.ts.map +1 -0
- package/dist/cdn/fastly-purge.d.ts +25 -0
- package/dist/cdn/fastly-purge.d.ts.map +1 -0
- package/dist/cdn/headers.d.ts +82 -0
- package/dist/cdn/headers.d.ts.map +1 -0
- package/dist/cdn/index.d.ts +5 -0
- package/dist/cdn/index.d.ts.map +1 -0
- package/dist/cdn/purge-store.d.ts +23 -0
- package/dist/cdn/purge-store.d.ts.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/history.d.ts +0 -3
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.js +0 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.js +11 -86
- package/dist/client/internal.js.map +1 -1
- package/dist/client/navigation-api.d.ts +1 -3
- package/dist/client/navigation-api.d.ts.map +1 -1
- package/dist/client/router.d.ts +1 -4
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +1 -11
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +1 -3
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/use-cookie.d.ts.map +1 -1
- package/dist/config-types.d.ts +3 -1
- package/dist/config-types.d.ts.map +1 -1
- package/dist/config-validation.d.ts.map +1 -1
- package/dist/cookies/index.js +15 -11
- package/dist/cookies/index.js.map +1 -1
- package/dist/dev-tools/browser-logs.d.ts +28 -72
- package/dist/dev-tools/browser-logs.d.ts.map +1 -1
- package/dist/dev-tools/index.d.ts +1 -1
- package/dist/dev-tools/index.d.ts.map +1 -1
- package/dist/dev-tools/logger.d.ts.map +1 -1
- package/dist/dev-tools/terminal.d.ts.map +1 -1
- package/dist/fonts/transform.d.ts +4 -0
- package/dist/fonts/transform.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1269 -359
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts +36 -0
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/build-report.d.ts +21 -0
- package/dist/plugins/build-report.d.ts.map +1 -1
- package/dist/plugins/cache.d.ts +5 -14
- package/dist/plugins/cache.d.ts.map +1 -1
- package/dist/plugins/callsite-ast.d.ts +95 -0
- package/dist/plugins/callsite-ast.d.ts.map +1 -0
- package/dist/plugins/callsite-jsx.d.ts +35 -0
- package/dist/plugins/callsite-jsx.d.ts.map +1 -0
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/prebuilt-capture.d.ts +60 -0
- package/dist/plugins/prebuilt-capture.d.ts.map +1 -0
- package/dist/plugins/prebuilt.d.ts +82 -0
- package/dist/plugins/prebuilt.d.ts.map +1 -0
- package/dist/plugins/prerender-sugar.d.ts +42 -0
- package/dist/plugins/prerender-sugar.d.ts.map +1 -0
- package/dist/plugins/server-bundle.d.ts.map +1 -1
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/convention-lint.d.ts.map +1 -1
- package/dist/routing/export-detect.d.ts +56 -0
- package/dist/routing/export-detect.d.ts.map +1 -0
- package/dist/routing/index.js +2 -2
- package/dist/routing/manifest-codegen.d.ts.map +1 -1
- package/dist/rsc-runtime/rsc.d.ts +1 -1
- package/dist/rsc-runtime/rsc.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +0 -3
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +7 -0
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/cookie-parsing.d.ts +15 -1
- package/dist/server/cookie-parsing.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/html-injectors.d.ts +1 -0
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/internal.d.ts +6 -1
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +563 -74
- package/dist/server/internal.js.map +1 -1
- package/dist/server/prebuilt/cache-key.d.ts +58 -0
- package/dist/server/prebuilt/cache-key.d.ts.map +1 -0
- package/dist/server/prebuilt/capture-state.d.ts +57 -0
- package/dist/server/prebuilt/capture-state.d.ts.map +1 -0
- package/dist/server/prebuilt/manifest-tag-index.d.ts +27 -0
- package/dist/server/prebuilt/manifest-tag-index.d.ts.map +1 -0
- package/dist/server/prebuilt/overlay.d.ts +100 -0
- package/dist/server/prebuilt/overlay.d.ts.map +1 -0
- package/dist/server/prebuilt/payload-source.d.ts +75 -0
- package/dist/server/prebuilt/payload-source.d.ts.map +1 -0
- package/dist/server/prebuilt/synthetic-store.d.ts +64 -0
- package/dist/server/prebuilt/synthetic-store.d.ts.map +1 -0
- package/dist/server/prebuilt-builder.d.ts +110 -0
- package/dist/server/prebuilt-builder.d.ts.map +1 -0
- package/dist/server/prebuilt-runtime.d.ts +101 -0
- package/dist/server/prebuilt-runtime.d.ts.map +1 -0
- package/dist/server/route-element-builder.d.ts +10 -8
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +0 -5
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +10 -0
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +3 -4
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/sitemap-generator.d.ts +6 -6
- package/dist/server/sitemap-generator.d.ts.map +1 -1
- package/dist/server/tracing.d.ts +37 -3
- package/dist/server/tracing.d.ts.map +1 -1
- package/docs/api/32-api-cache.mdx +10 -4
- package/docs/learn/00-introduction.mdx +8 -13
- package/docs/learn/02-pages-and-layouts.mdx +9 -0
- package/docs/learn/06-forms-and-actions.mdx +0 -4
- package/docs/learn/07-typed-routes.mdx +127 -0
- package/docs/learn/09-caching.mdx +12 -1
- package/docs/learn/10-middleware.mdx +1 -0
- package/docs/learn/11-error-handling.mdx +9 -0
- package/docs/learn/13-configuration.mdx +28 -0
- package/docs/learn/14-deploying.mdx +52 -0
- package/docs/more/06-instrumentation.mdx +27 -1
- package/docs/more/07-security.mdx +8 -2
- package/docs/more/08-developer-experience.mdx +134 -0
- package/package.json +31 -2
- package/src/adapters/build-output-helper.ts +10 -0
- package/src/adapters/cloudflare.ts +178 -4
- package/src/cache/cache-api.ts +60 -0
- package/src/cache/handler-store.ts +8 -0
- package/src/cache/index.ts +4 -0
- package/src/cache/redis-handler.ts +25 -13
- package/src/cache/singleflight.ts +11 -11
- package/src/cache/store.ts +71 -0
- package/src/cache/stores/cloudflare-kv.ts +52 -0
- package/src/cache/stores/memory.ts +125 -0
- package/src/cache/stores/redis.ts +97 -0
- package/src/cache/stores/vercel.ts +59 -0
- package/src/cache/tag-aware-handler.ts +233 -0
- package/src/cache/timber-cache.ts +17 -7
- package/src/cdn/cloudflare-purge.ts +50 -0
- package/src/cdn/fastly-purge.ts +63 -0
- package/src/cdn/headers.ts +127 -0
- package/src/cdn/index.ts +16 -0
- package/src/cdn/purge-store.ts +34 -0
- package/src/client/browser-entry/action-dispatch.ts +1 -10
- package/src/client/browser-entry/post-hydration.ts +0 -1
- package/src/client/browser-entry/router-init.ts +0 -5
- package/src/client/history.ts +0 -4
- package/src/client/navigation-api.ts +5 -11
- package/src/client/router.ts +9 -43
- package/src/client/rsc-fetch.ts +5 -27
- package/src/client/segment-cache.ts +1 -4
- package/src/client/use-cookie.ts +18 -12
- package/src/config-types.ts +3 -1
- package/src/config-validation.ts +1 -0
- package/src/dev-tools/browser-logs.ts +37 -273
- package/src/dev-tools/index.ts +2 -2
- package/src/dev-tools/instrumentation.ts +1 -1
- package/src/dev-tools/logger.ts +33 -51
- package/src/dev-tools/terminal.ts +21 -22
- package/src/fonts/transform.ts +4 -43
- package/src/index.ts +16 -3
- package/src/plugin-context.ts +36 -0
- package/src/plugins/build-report.ts +133 -1
- package/src/plugins/cache.ts +32 -131
- package/src/plugins/callsite-ast.ts +167 -0
- package/src/plugins/callsite-jsx.ts +243 -0
- package/src/plugins/entries.ts +23 -4
- package/src/plugins/fonts.ts +16 -2
- package/src/plugins/prebuilt-capture.ts +208 -0
- package/src/plugins/prebuilt.ts +673 -0
- package/src/plugins/prerender-sugar.ts +329 -0
- package/src/plugins/server-bundle.ts +12 -0
- package/src/plugins/shims.ts +9 -0
- package/src/plugins/static-build.ts +0 -1
- package/src/routing/codegen.ts +1 -21
- package/src/routing/convention-lint.ts +85 -49
- package/src/routing/export-detect.ts +304 -0
- package/src/routing/manifest-codegen.ts +2 -11
- package/src/rsc-runtime/rsc.ts +7 -0
- package/src/server/action-handler.ts +11 -27
- package/src/server/actions.ts +3 -4
- package/src/server/als-registry.ts +7 -0
- package/src/server/cookie-parsing.ts +75 -59
- package/src/server/deny-renderer.ts +7 -15
- package/src/server/html-injectors.ts +11 -3
- package/src/server/internal.ts +17 -1
- package/src/server/prebuilt/cache-key.ts +224 -0
- package/src/server/prebuilt/capture-state.ts +89 -0
- package/src/server/prebuilt/manifest-tag-index.ts +46 -0
- package/src/server/prebuilt/overlay.ts +256 -0
- package/src/server/prebuilt/payload-source.ts +192 -0
- package/src/server/prebuilt/synthetic-store.ts +108 -0
- package/src/server/prebuilt-builder.ts +640 -0
- package/src/server/prebuilt-runtime.ts +706 -0
- package/src/server/route-element-builder.ts +40 -12
- package/src/server/rsc-entry/helpers.ts +0 -17
- package/src/server/rsc-entry/index.ts +83 -0
- package/src/server/rsc-entry/render-route.ts +6 -33
- package/src/server/rsc-entry/revalidate-renderer.ts +16 -11
- package/src/server/rsc-entry/rsc-payload.ts +5 -11
- package/src/server/sitemap-generator.ts +16 -10
- package/src/server/tracing.ts +88 -3
- package/dist/_chunks/cache-api-Cbyh3RW4.js +0 -401
- package/dist/_chunks/cache-api-Cbyh3RW4.js.map +0 -1
- package/dist/_chunks/logger-C5v1IMuh.js.map +0 -1
- package/dist/_chunks/plugin-context-DUl3dnCF.js.map +0 -1
- package/dist/_chunks/validation-CnKhGJsp.js +0 -113
- package/dist/_chunks/validation-CnKhGJsp.js.map +0 -1
- package/dist/_chunks/walkers-D5jCGPum.js.map +0 -1
- package/dist/cache/index.js.map +0 -1
- package/dist/client/head.d.ts +0 -16
- package/dist/client/head.d.ts.map +0 -1
- package/dist/client/navigation-api-types.d.ts +0 -90
- package/dist/client/navigation-api-types.d.ts.map +0 -1
- package/dist/server/client-module-map.d.ts +0 -47
- package/dist/server/client-module-map.d.ts.map +0 -1
- package/dist/shared/head-header.d.ts +0 -54
- package/dist/shared/head-header.d.ts.map +0 -1
- package/src/client/head.ts +0 -61
- package/src/client/navigation-api-types.ts +0 -112
- package/src/server/client-module-map.ts +0 -58
- package/src/shared/head-header.ts +0 -69
|
@@ -1,401 +0,0 @@
|
|
|
1
|
-
import { L as getWaitUntil, V as addSpanEventSync, d as logSwrRefetchFailed } from "./logger-C5v1IMuh.js";
|
|
2
|
-
import { t as fnv1aHash } from "./fast-hash-D6hIVt1Y.js";
|
|
3
|
-
//#region src/cache/stable-stringify.ts
|
|
4
|
-
/**
|
|
5
|
-
* Deterministic JSON serialization with sorted object keys.
|
|
6
|
-
* Used for cache key generation — ensures { a: 1, b: 2 } and { b: 2, a: 1 }
|
|
7
|
-
* produce the same string.
|
|
8
|
-
*
|
|
9
|
-
* Matches JSON.stringify semantics for toJSON: Date serializes to its ISO
|
|
10
|
-
* string, URL to its href, and class instances with a custom toJSON use it.
|
|
11
|
-
* Map and Set serialize their entries explicitly (insertion-order
|
|
12
|
-
* independent). Values that would serialize lossily — functions, symbols,
|
|
13
|
-
* and non-plain objects with no enumerable own state (RegExp, class
|
|
14
|
-
* instances holding data behind getters or internal slots) — throw instead:
|
|
15
|
-
* every such value would collapse to the same cache key, silently returning
|
|
16
|
-
* one argument's cached data for another (TIM-1020).
|
|
17
|
-
*/
|
|
18
|
-
function stableStringify(value) {
|
|
19
|
-
if (value === null || value === void 0) return String(value);
|
|
20
|
-
const type = typeof value;
|
|
21
|
-
if (type === "function" || type === "symbol") throw new TypeError(`stableStringify: cannot serialize a ${type} into a stable cache key — all such values would collide. Provide an explicit \`key\` option to cache().`);
|
|
22
|
-
if (type !== "object") return JSON.stringify(value);
|
|
23
|
-
if (Array.isArray(value)) return "[" + value.map((item) => stableStringify(item)).join(",") + "]";
|
|
24
|
-
const withToJSON = value;
|
|
25
|
-
if (typeof withToJSON.toJSON === "function") {
|
|
26
|
-
const json = withToJSON.toJSON();
|
|
27
|
-
if (json !== value) return stableStringify(json);
|
|
28
|
-
}
|
|
29
|
-
if (value instanceof Map) return "Map(" + [...value.entries()].map(([k, v]) => stableStringify(k) + "=>" + stableStringify(v)).sort().join(",") + ")";
|
|
30
|
-
if (value instanceof Set) return "Set(" + [...value].map((member) => stableStringify(member)).sort().join(",") + ")";
|
|
31
|
-
const obj = value;
|
|
32
|
-
const keys = Object.keys(obj).sort();
|
|
33
|
-
const pairs = [];
|
|
34
|
-
for (const key of keys) {
|
|
35
|
-
if (obj[key] === void 0) continue;
|
|
36
|
-
if (key === "toJSON" && typeof obj[key] === "function") continue;
|
|
37
|
-
pairs.push(JSON.stringify(key) + ":" + stableStringify(obj[key]));
|
|
38
|
-
}
|
|
39
|
-
if (pairs.length === 0) {
|
|
40
|
-
const proto = Object.getPrototypeOf(value);
|
|
41
|
-
if (proto !== Object.prototype && proto !== null) {
|
|
42
|
-
const name = value.constructor?.name || "Object";
|
|
43
|
-
throw new TypeError(`stableStringify: ${name} instance has no serializable own state — all instances would share one cache key. Provide an explicit \`key\` option to cache(), or add a toJSON() method.`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return "{" + pairs.join(",") + "}";
|
|
47
|
-
}
|
|
48
|
-
//#endregion
|
|
49
|
-
//#region src/cache/singleflight.ts
|
|
50
|
-
/**
|
|
51
|
-
* Error thrown when a singleflight call exceeds `timeoutMs`.
|
|
52
|
-
* Exported so callers can distinguish timeout from other errors.
|
|
53
|
-
*/
|
|
54
|
-
var SingleflightTimeoutError = class extends Error {
|
|
55
|
-
constructor(key, timeoutMs) {
|
|
56
|
-
super(`Singleflight timeout: key "${key}" exceeded ${timeoutMs}ms`);
|
|
57
|
-
this.name = "SingleflightTimeoutError";
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
function createSingleflight(opts) {
|
|
61
|
-
const inflight = /* @__PURE__ */ new Map();
|
|
62
|
-
const timeoutMs = opts?.timeoutMs;
|
|
63
|
-
return { do(key, fn) {
|
|
64
|
-
const existing = inflight.get(key);
|
|
65
|
-
if (existing) return existing;
|
|
66
|
-
let promise;
|
|
67
|
-
if (timeoutMs != null && timeoutMs > 0) promise = new Promise((resolve, reject) => {
|
|
68
|
-
const timer = setTimeout(() => reject(new SingleflightTimeoutError(key, timeoutMs)), timeoutMs);
|
|
69
|
-
try {
|
|
70
|
-
fn().then((value) => {
|
|
71
|
-
clearTimeout(timer);
|
|
72
|
-
resolve(value);
|
|
73
|
-
}, (err) => {
|
|
74
|
-
clearTimeout(timer);
|
|
75
|
-
reject(err);
|
|
76
|
-
});
|
|
77
|
-
} catch (err) {
|
|
78
|
-
clearTimeout(timer);
|
|
79
|
-
reject(err);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
else promise = fn();
|
|
83
|
-
const tracked = promise.finally(() => {
|
|
84
|
-
inflight.delete(key);
|
|
85
|
-
});
|
|
86
|
-
inflight.set(key, tracked);
|
|
87
|
-
return tracked;
|
|
88
|
-
} };
|
|
89
|
-
}
|
|
90
|
-
//#endregion
|
|
91
|
-
//#region src/cache/invalidation-epoch.ts
|
|
92
|
-
/**
|
|
93
|
-
* Per-process invalidation epoch tracking for timber.cache (TIM-1028).
|
|
94
|
-
*
|
|
95
|
-
* handler.set for a cache miss runs after `fn()` resolves. An explicit
|
|
96
|
-
* `cache.invalidate({ tag })` / `revalidateTag` that lands while fn is in
|
|
97
|
-
* flight would otherwise be silently undone — the just-invalidated value
|
|
98
|
-
* re-stored as fresh for the full TTL, resurrecting pre-mutation data.
|
|
99
|
-
*
|
|
100
|
-
* Every invalidation bumps a monotonic epoch and records it against the
|
|
101
|
-
* invalidated key/tag. Before storing a result, the cache wrapper checks
|
|
102
|
-
* whether its key or any of its tags were invalidated after fn started and
|
|
103
|
-
* skips the write if so. Skipping is always safe: the only cost is one
|
|
104
|
-
* extra cache miss on the next call.
|
|
105
|
-
*
|
|
106
|
-
* Scope: per-process, like the singleflight map. An invalidation issued by
|
|
107
|
-
* another instance against a shared handler (Redis/KV) during fn flight is
|
|
108
|
-
* not observable here — closing that window fully would require store-level
|
|
109
|
-
* versioning, which no CacheHandler backend offers.
|
|
110
|
-
*/
|
|
111
|
-
var epoch = 0;
|
|
112
|
-
/**
|
|
113
|
-
* Last-invalidated epoch per `key:<k>` / `tag:<t>` entry. Insertion order is
|
|
114
|
-
* epoch order (entries are re-inserted on update), so the oldest entry is
|
|
115
|
-
* always first — pruning evicts lowest epochs.
|
|
116
|
-
*/
|
|
117
|
-
var lastInvalidated = /* @__PURE__ */ new Map();
|
|
118
|
-
/**
|
|
119
|
-
* Bound on tracked entries so high-cardinality tag invalidation (e.g.
|
|
120
|
-
* per-user tags) cannot grow this map without limit.
|
|
121
|
-
*/
|
|
122
|
-
var MAX_TRACKED_ENTRIES = 1e4;
|
|
123
|
-
/**
|
|
124
|
-
* Highest epoch evicted from the map. Any execution that started at or
|
|
125
|
-
* before this epoch conservatively counts as invalidated — history was
|
|
126
|
-
* truncated, so we can no longer prove it wasn't.
|
|
127
|
-
*/
|
|
128
|
-
var evictedEpochFloor = 0;
|
|
129
|
-
/** The current epoch. Capture before running fn(), pass to wasInvalidatedSince. */
|
|
130
|
-
function currentInvalidationEpoch() {
|
|
131
|
-
return epoch;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Record an invalidation. Call BEFORE handler.invalidate() so an execution
|
|
135
|
-
* whose set-check races the invalidation errs toward skipping the write.
|
|
136
|
-
*/
|
|
137
|
-
function recordInvalidation(opts) {
|
|
138
|
-
epoch++;
|
|
139
|
-
if (opts.key) track(`key:${opts.key}`);
|
|
140
|
-
if (opts.tag) track(`tag:${opts.tag}`);
|
|
141
|
-
}
|
|
142
|
-
function track(entry) {
|
|
143
|
-
lastInvalidated.delete(entry);
|
|
144
|
-
lastInvalidated.set(entry, epoch);
|
|
145
|
-
while (lastInvalidated.size > MAX_TRACKED_ENTRIES) {
|
|
146
|
-
const oldest = lastInvalidated.entries().next().value;
|
|
147
|
-
if (oldest === void 0) break;
|
|
148
|
-
evictedEpochFloor = Math.max(evictedEpochFloor, oldest[1]);
|
|
149
|
-
lastInvalidated.delete(oldest[0]);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Whether the key or any tag was invalidated after `startEpoch` (the epoch
|
|
154
|
-
* captured when fn started executing).
|
|
155
|
-
*/
|
|
156
|
-
function wasInvalidatedSince(startEpoch, key, tags) {
|
|
157
|
-
return lastInvalidationEpochFor(key, tags) > startEpoch;
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* The highest epoch at which the key or any of the tags was invalidated
|
|
161
|
-
* (0 if never). Used to scope the singleflight key: callers arriving after
|
|
162
|
-
* an invalidation must not coalesce onto a flight that started before it —
|
|
163
|
-
* they would receive the pre-invalidation result the invalidation was
|
|
164
|
-
* issued to discard.
|
|
165
|
-
*/
|
|
166
|
-
function lastInvalidationEpochFor(key, tags) {
|
|
167
|
-
let last = evictedEpochFloor;
|
|
168
|
-
const keyEpoch = lastInvalidated.get(`key:${key}`);
|
|
169
|
-
if (keyEpoch !== void 0 && keyEpoch > last) last = keyEpoch;
|
|
170
|
-
for (const tag of tags) {
|
|
171
|
-
const tagEpoch = lastInvalidated.get(`tag:${tag}`);
|
|
172
|
-
if (tagEpoch !== void 0 && tagEpoch > last) last = tagEpoch;
|
|
173
|
-
}
|
|
174
|
-
return last;
|
|
175
|
-
}
|
|
176
|
-
//#endregion
|
|
177
|
-
//#region src/cache/timber-cache.ts
|
|
178
|
-
var defaultSingleflight = createSingleflight();
|
|
179
|
-
/**
|
|
180
|
-
* Set the timeout for the module-level default singleflight.
|
|
181
|
-
* Called at framework boot with renderTimeoutMs from timber.config.ts so that
|
|
182
|
-
* cache calls without a per-call timeoutMs still get deadlock protection.
|
|
183
|
-
* See design/06-caching.md §"Singleflight Timeout — Deadlock Risk".
|
|
184
|
-
*/
|
|
185
|
-
function setDefaultSingleflightTimeout(timeoutMs) {
|
|
186
|
-
defaultSingleflight = createSingleflight({ timeoutMs });
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* Generate a cache key from function identity and serialized args.
|
|
190
|
-
*
|
|
191
|
-
* Uses FNV-1a (fast non-crypto hash) instead of SHA-256. Cache keys don't
|
|
192
|
-
* need collision resistance — they need speed. The fnId prefix provides
|
|
193
|
-
* namespace isolation; the hash covers the args.
|
|
194
|
-
*
|
|
195
|
-
* See TIM-370 for perf motivation.
|
|
196
|
-
*/
|
|
197
|
-
function defaultKeyGenerator(fnId, args) {
|
|
198
|
-
const raw = fnId + ":" + stableStringify(args);
|
|
199
|
-
return fnId + ":" + fnv1aHash(raw);
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Resolve tags from the options — supports static array or function form.
|
|
203
|
-
*/
|
|
204
|
-
function resolveTags(opts, args) {
|
|
205
|
-
if (!opts.tags) return [];
|
|
206
|
-
if (Array.isArray(opts.tags)) return opts.tags;
|
|
207
|
-
return opts.tags(...args);
|
|
208
|
-
}
|
|
209
|
-
/** Per-process count of byte-identical fn sources seen by deriveFallbackFnId. */
|
|
210
|
-
var fallbackIdOccurrences = /* @__PURE__ */ new Map();
|
|
211
|
-
var hasWarnedFallbackId = false;
|
|
212
|
-
/**
|
|
213
|
-
* Derive a fallback fnId for callsites the timber-cache-transform Vite plugin
|
|
214
|
-
* could not statically detect (aliased bindings, user re-export wrappers).
|
|
215
|
-
*
|
|
216
|
-
* The base is fnv1a(fn.toString()) — deterministic across instances and cold
|
|
217
|
-
* starts of the same build, so two *different* functions can never swap fnIds
|
|
218
|
-
* by module-evaluation order (the TIM-997 poisoning scenario that the old
|
|
219
|
-
* process-local counter allowed; see TIM-1054). A per-process occurrence
|
|
220
|
-
* suffix disambiguates byte-identical duplicate sources. That residue is the
|
|
221
|
-
* one remaining hazard: closures produced by the same factory share source
|
|
222
|
-
* text but capture different values, and their occurrence order may differ
|
|
223
|
-
* across instances — such callsites need an explicit `key` option.
|
|
224
|
-
*/
|
|
225
|
-
function deriveFallbackFnId(fn) {
|
|
226
|
-
const sourceHash = fnv1aHash(fn.toString());
|
|
227
|
-
const occurrence = fallbackIdOccurrences.get(sourceHash) ?? 0;
|
|
228
|
-
fallbackIdOccurrences.set(sourceHash, occurrence + 1);
|
|
229
|
-
if (!hasWarnedFallbackId && typeof process !== "undefined" && process.env.NODE_ENV !== "test") {
|
|
230
|
-
hasWarnedFallbackId = true;
|
|
231
|
-
console.warn("[timber] cache() callsite has no build-time stable ID and no explicit `key` option — the call is not statically visible to the timber Vite plugin (e.g. an aliased binding like `const c = cache` or a re-export wrapper module). Falling back to a function-source hash for the cache key. With a shared CacheHandler (Redis/KV) this is safe for distinct functions, but byte-identical closures from a factory can swap cache entries across instances. Provide an explicit `key` option to make the cache key contract stable and auditable.");
|
|
232
|
-
}
|
|
233
|
-
return `timber-cache:fn:${sourceHash}:${occurrence}`;
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Creates a cached wrapper around an async function.
|
|
237
|
-
*
|
|
238
|
-
* - FNV-1a default keys with normalized JSON args
|
|
239
|
-
* - Singleflight: concurrent misses → single execution AND single
|
|
240
|
-
* handler.set — the write happens inside the coalesced callback (TIM-1028)
|
|
241
|
-
* - Writes are skipped when the key/tags were invalidated while fn was in
|
|
242
|
-
* flight, so explicit invalidation is never silently undone (TIM-1028)
|
|
243
|
-
* - SWR: serve stale immediately, background refetch
|
|
244
|
-
* - Tags as string[] or function of args
|
|
245
|
-
* - The handler is resolved per invocation via `getHandler`, never captured
|
|
246
|
-
* at wrapper-creation time — module-scope cache() wrappers created before
|
|
247
|
-
* the framework's boot wiring calls setCacheHandler() still use the
|
|
248
|
-
* configured handler once it arrives (TIM-1029)
|
|
249
|
-
* - No ALS dependency for correctness — cache() works outside a request
|
|
250
|
-
* context. The waitUntil ALS bridge is read optionally so the SWR
|
|
251
|
-
* background refetch survives handler return on Workers/Lambda.
|
|
252
|
-
*
|
|
253
|
-
* Cache hits/misses are recorded as OTEL span events on the enclosing
|
|
254
|
-
* span (not child spans). The DevSpanProcessor reads these for dev log output.
|
|
255
|
-
*/
|
|
256
|
-
function createCache(fn, opts, getHandler, stableId) {
|
|
257
|
-
const fnId = stableId ?? (opts.key ? void 0 : deriveFallbackFnId(fn));
|
|
258
|
-
const perCallSf = opts.timeoutMs !== void 0 ? createSingleflight({ timeoutMs: opts.timeoutMs }) : void 0;
|
|
259
|
-
return (async (...args) => {
|
|
260
|
-
const sf = perCallSf ?? defaultSingleflight;
|
|
261
|
-
const key = opts.key ? opts.key(...args) : defaultKeyGenerator(fnId, args);
|
|
262
|
-
const cacheStart = performance.now();
|
|
263
|
-
const cached = await getHandler().get(key);
|
|
264
|
-
if (cached && !cached.stale) {
|
|
265
|
-
addSpanEventSync("timber.cache.hit", {
|
|
266
|
-
key,
|
|
267
|
-
duration_ms: Math.round(performance.now() - cacheStart)
|
|
268
|
-
});
|
|
269
|
-
return cached.value;
|
|
270
|
-
}
|
|
271
|
-
const tags = resolveTags(opts, args);
|
|
272
|
-
const flightKey = `${key}\u0000${lastInvalidationEpochFor(key, tags)}`;
|
|
273
|
-
/**
|
|
274
|
-
* Run fn and store the result — exactly once per coalesced execution,
|
|
275
|
-
* inside the singleflight callback. The write is skipped when the key or
|
|
276
|
-
* any resolved tag was invalidated after fn started: storing would
|
|
277
|
-
* resurrect just-invalidated data as fresh for the full TTL.
|
|
278
|
-
*/
|
|
279
|
-
const executeAndStore = async () => {
|
|
280
|
-
const startEpoch = currentInvalidationEpoch();
|
|
281
|
-
const result = await fn(...args);
|
|
282
|
-
if (!wasInvalidatedSince(startEpoch, key, tags)) await getHandler().set(key, result, {
|
|
283
|
-
ttl: opts.ttl,
|
|
284
|
-
tags
|
|
285
|
-
});
|
|
286
|
-
return result;
|
|
287
|
-
};
|
|
288
|
-
if (cached && cached.stale && opts.staleWhileRevalidate) {
|
|
289
|
-
addSpanEventSync("timber.cache.hit", {
|
|
290
|
-
key,
|
|
291
|
-
duration_ms: Math.round(performance.now() - cacheStart),
|
|
292
|
-
stale: true
|
|
293
|
-
});
|
|
294
|
-
const refetch = sf.do(`swr:${flightKey}`, async () => {
|
|
295
|
-
try {
|
|
296
|
-
await executeAndStore();
|
|
297
|
-
} catch (err) {
|
|
298
|
-
logSwrRefetchFailed({
|
|
299
|
-
cacheKey: key,
|
|
300
|
-
error: err
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
}).catch(() => {});
|
|
304
|
-
getWaitUntil()?.(refetch);
|
|
305
|
-
return cached.value;
|
|
306
|
-
}
|
|
307
|
-
const result = await sf.do(flightKey, executeAndStore);
|
|
308
|
-
addSpanEventSync("timber.cache.miss", {
|
|
309
|
-
key,
|
|
310
|
-
duration_ms: Math.round(performance.now() - cacheStart)
|
|
311
|
-
});
|
|
312
|
-
return result;
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
//#endregion
|
|
316
|
-
//#region src/cache/handler-store.ts
|
|
317
|
-
var handler = null;
|
|
318
|
-
/** Replace the active cache handler. Called by the framework at boot. */
|
|
319
|
-
function setCacheHandler(h) {
|
|
320
|
-
handler = h;
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Get the active cache handler. Creates a default MemoryCacheHandler on
|
|
324
|
-
* first access if none has been set via setCacheHandler().
|
|
325
|
-
*/
|
|
326
|
-
function getCacheHandler() {
|
|
327
|
-
if (!handler) handler = createDefaultHandler();
|
|
328
|
-
return handler;
|
|
329
|
-
}
|
|
330
|
-
function createDefaultHandler() {
|
|
331
|
-
const store = /* @__PURE__ */ new Map();
|
|
332
|
-
const maxEntries = 1e3;
|
|
333
|
-
return {
|
|
334
|
-
async get(key) {
|
|
335
|
-
const entry = store.get(key);
|
|
336
|
-
if (!entry) return null;
|
|
337
|
-
store.delete(key);
|
|
338
|
-
store.set(key, entry);
|
|
339
|
-
const stale = Date.now() > entry.expiresAt;
|
|
340
|
-
return {
|
|
341
|
-
value: entry.value,
|
|
342
|
-
stale
|
|
343
|
-
};
|
|
344
|
-
},
|
|
345
|
-
async set(key, value, opts) {
|
|
346
|
-
if (store.has(key)) store.delete(key);
|
|
347
|
-
while (store.size >= maxEntries) {
|
|
348
|
-
const oldest = store.keys().next().value;
|
|
349
|
-
if (oldest !== void 0) store.delete(oldest);
|
|
350
|
-
else break;
|
|
351
|
-
}
|
|
352
|
-
store.set(key, {
|
|
353
|
-
value,
|
|
354
|
-
expiresAt: Date.now() + opts.ttl * 1e3,
|
|
355
|
-
tags: opts.tags
|
|
356
|
-
});
|
|
357
|
-
},
|
|
358
|
-
async invalidate(opts) {
|
|
359
|
-
if (opts.key) store.delete(opts.key);
|
|
360
|
-
if (opts.tag) {
|
|
361
|
-
for (const [key, entry] of store) if (entry.tags.includes(opts.tag)) store.delete(key);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
//#endregion
|
|
367
|
-
//#region src/cache/cache-api.ts
|
|
368
|
-
/**
|
|
369
|
-
* Public caching API: `cache(fn, opts)`.
|
|
370
|
-
*
|
|
371
|
-
* Wraps an async function with cross-request caching. Uses the configured
|
|
372
|
-
* cache handler (defaults to MemoryCacheHandler, overridable via timber.config.ts).
|
|
373
|
-
*
|
|
374
|
-
* ```ts
|
|
375
|
-
* import { cache } from '@timber-js/app/cache';
|
|
376
|
-
*
|
|
377
|
-
* const getUser = cache(
|
|
378
|
-
* async (id: string) => db.users.findUnique({ where: { id } }),
|
|
379
|
-
* { ttl: 60, tags: (id) => [`user:${id}`] }
|
|
380
|
-
* );
|
|
381
|
-
* ```
|
|
382
|
-
*/
|
|
383
|
-
function cache(fn, opts, stableId) {
|
|
384
|
-
return createCache(fn, opts, getCacheHandler, stableId);
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* Invalidate cache entries by tag or key.
|
|
388
|
-
*
|
|
389
|
-
* ```ts
|
|
390
|
-
* cache.invalidate({ tag: 'products' });
|
|
391
|
-
* cache.invalidate({ key: 'user:abc' });
|
|
392
|
-
* ```
|
|
393
|
-
*/
|
|
394
|
-
cache.invalidate = async function invalidate(opts) {
|
|
395
|
-
recordInvalidation(opts);
|
|
396
|
-
await getCacheHandler().invalidate(opts);
|
|
397
|
-
};
|
|
398
|
-
//#endregion
|
|
399
|
-
export { setDefaultSingleflightTimeout as i, getCacheHandler as n, setCacheHandler as r, cache as t };
|
|
400
|
-
|
|
401
|
-
//# sourceMappingURL=cache-api-Cbyh3RW4.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cache-api-Cbyh3RW4.js","names":[],"sources":["../../src/cache/stable-stringify.ts","../../src/cache/singleflight.ts","../../src/cache/invalidation-epoch.ts","../../src/cache/timber-cache.ts","../../src/cache/handler-store.ts","../../src/cache/cache-api.ts"],"sourcesContent":["/**\n * Deterministic JSON serialization with sorted object keys.\n * Used for cache key generation — ensures { a: 1, b: 2 } and { b: 2, a: 1 }\n * produce the same string.\n *\n * Matches JSON.stringify semantics for toJSON: Date serializes to its ISO\n * string, URL to its href, and class instances with a custom toJSON use it.\n * Map and Set serialize their entries explicitly (insertion-order\n * independent). Values that would serialize lossily — functions, symbols,\n * and non-plain objects with no enumerable own state (RegExp, class\n * instances holding data behind getters or internal slots) — throw instead:\n * every such value would collapse to the same cache key, silently returning\n * one argument's cached data for another (TIM-1020).\n */\nexport function stableStringify(value: unknown): string {\n if (value === null || value === undefined) return String(value);\n\n const type = typeof value;\n if (type === 'function' || type === 'symbol') {\n throw new TypeError(\n `stableStringify: cannot serialize a ${type} into a stable cache key — ` +\n 'all such values would collide. Provide an explicit `key` option to cache().'\n );\n }\n if (type !== 'object') return JSON.stringify(value);\n\n if (Array.isArray(value)) {\n return '[' + value.map((item) => stableStringify(item)).join(',') + ']';\n }\n\n // Honor toJSON like JSON.stringify does (Date → ISO string, URL → href,\n // custom class serialization). JSON.stringify does not re-invoke toJSON\n // on the value it returns, so a toJSON that returns `this` falls through\n // to plain own-property serialization instead of recursing forever.\n const withToJSON = value as { toJSON?: () => unknown };\n if (typeof withToJSON.toJSON === 'function') {\n const json = withToJSON.toJSON();\n if (json !== value) return stableStringify(json);\n }\n\n // Map/Set have no enumerable own properties — serialize entries explicitly.\n // Entries are sorted after serialization so insertion order doesn't change\n // the key. The Map(/Set( prefixes can't collide with object ('{'), array\n // ('['), or string ('\"') output.\n if (value instanceof Map) {\n const entries = [...value.entries()].map(\n ([k, v]) => stableStringify(k) + '=>' + stableStringify(v)\n );\n return 'Map(' + entries.sort().join(',') + ')';\n }\n if (value instanceof Set) {\n const members = [...value].map((member) => stableStringify(member));\n return 'Set(' + members.sort().join(',') + ')';\n }\n\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n const pairs: string[] = [];\n for (const key of keys) {\n if (obj[key] === undefined) continue;\n // A callable own toJSON only reaches this loop via the returns-`this`\n // fall-through above. It's the serialization protocol, not data —\n // JSON.stringify omits it too.\n if (key === 'toJSON' && typeof obj[key] === 'function') continue;\n pairs.push(JSON.stringify(key) + ':' + stableStringify(obj[key]));\n }\n\n // A non-plain object that serializes to '{}' carries its state behind\n // getters or internal slots (RegExp, sessions, ORM entities). Serializing\n // it would give every instance the same cache key.\n if (pairs.length === 0) {\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) {\n const name = (value as object).constructor?.name || 'Object';\n throw new TypeError(\n `stableStringify: ${name} instance has no serializable own state — ` +\n 'all instances would share one cache key. Provide an explicit `key` ' +\n 'option to cache(), or add a toJSON() method.'\n );\n }\n }\n\n return '{' + pairs.join(',') + '}';\n}\n","/**\n * Singleflight coalesces concurrent calls with the same key into a single\n * execution. All callers receive the same result (or error).\n *\n * Per-process, in-memory. Each process coalesces independently.\n *\n * An optional `timeoutMs` prevents hung `fn()` calls from permanently\n * blocking all future callers for that key. When set, `fn()` is raced\n * against a timeout — if the timeout fires first, the promise rejects\n * with `SingleflightTimeoutError`, `finally` cleans up the key, and\n * subsequent callers can retry. See TIM-518.\n */\n\nexport interface SingleflightOptions {\n /** Maximum time (ms) a coalesced call may run before being rejected. */\n timeoutMs?: number;\n}\n\nexport interface Singleflight {\n do<T>(key: string, fn: () => Promise<T>): Promise<T>;\n}\n\n/**\n * Error thrown when a singleflight call exceeds `timeoutMs`.\n * Exported so callers can distinguish timeout from other errors.\n */\nexport class SingleflightTimeoutError extends Error {\n constructor(key: string, timeoutMs: number) {\n super(`Singleflight timeout: key \"${key}\" exceeded ${timeoutMs}ms`);\n this.name = 'SingleflightTimeoutError';\n }\n}\n\nexport function createSingleflight(opts?: SingleflightOptions): Singleflight {\n const inflight = new Map<string, Promise<unknown>>();\n const timeoutMs = opts?.timeoutMs;\n\n return {\n do<T>(key: string, fn: () => Promise<T>): Promise<T> {\n const existing = inflight.get(key);\n if (existing) return existing as Promise<T>;\n\n let promise: Promise<T>;\n\n if (timeoutMs != null && timeoutMs > 0) {\n // Race fn() against a timeout to prevent hung calls from\n // permanently blocking the key. See TIM-518.\n promise = new Promise<T>((resolve, reject) => {\n const timer = setTimeout(\n () => reject(new SingleflightTimeoutError(key, timeoutMs)),\n timeoutMs\n );\n // Wrap in try/catch so a synchronous throw from fn()\n // (e.g. argument validation) still clears the timer.\n // Without this, the timer leaks until expiry.\n try {\n fn().then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (err) => {\n clearTimeout(timer);\n reject(err);\n }\n );\n } catch (err) {\n clearTimeout(timer);\n reject(err);\n }\n });\n } else {\n promise = fn();\n }\n\n const tracked = promise.finally(() => {\n inflight.delete(key);\n });\n\n inflight.set(key, tracked);\n return tracked as Promise<T>;\n },\n };\n}\n","/**\n * Per-process invalidation epoch tracking for timber.cache (TIM-1028).\n *\n * handler.set for a cache miss runs after `fn()` resolves. An explicit\n * `cache.invalidate({ tag })` / `revalidateTag` that lands while fn is in\n * flight would otherwise be silently undone — the just-invalidated value\n * re-stored as fresh for the full TTL, resurrecting pre-mutation data.\n *\n * Every invalidation bumps a monotonic epoch and records it against the\n * invalidated key/tag. Before storing a result, the cache wrapper checks\n * whether its key or any of its tags were invalidated after fn started and\n * skips the write if so. Skipping is always safe: the only cost is one\n * extra cache miss on the next call.\n *\n * Scope: per-process, like the singleflight map. An invalidation issued by\n * another instance against a shared handler (Redis/KV) during fn flight is\n * not observable here — closing that window fully would require store-level\n * versioning, which no CacheHandler backend offers.\n */\n\nlet epoch = 0;\n\n/**\n * Last-invalidated epoch per `key:<k>` / `tag:<t>` entry. Insertion order is\n * epoch order (entries are re-inserted on update), so the oldest entry is\n * always first — pruning evicts lowest epochs.\n */\nconst lastInvalidated = new Map<string, number>();\n\n/**\n * Bound on tracked entries so high-cardinality tag invalidation (e.g.\n * per-user tags) cannot grow this map without limit.\n */\nconst MAX_TRACKED_ENTRIES = 10_000;\n\n/**\n * Highest epoch evicted from the map. Any execution that started at or\n * before this epoch conservatively counts as invalidated — history was\n * truncated, so we can no longer prove it wasn't.\n */\nlet evictedEpochFloor = 0;\n\n/** The current epoch. Capture before running fn(), pass to wasInvalidatedSince. */\nexport function currentInvalidationEpoch(): number {\n return epoch;\n}\n\n/**\n * Record an invalidation. Call BEFORE handler.invalidate() so an execution\n * whose set-check races the invalidation errs toward skipping the write.\n */\nexport function recordInvalidation(opts: { key?: string; tag?: string }): void {\n epoch++;\n if (opts.key) track(`key:${opts.key}`);\n if (opts.tag) track(`tag:${opts.tag}`);\n}\n\nfunction track(entry: string): void {\n lastInvalidated.delete(entry);\n lastInvalidated.set(entry, epoch);\n while (lastInvalidated.size > MAX_TRACKED_ENTRIES) {\n const oldest = lastInvalidated.entries().next().value;\n if (oldest === undefined) break;\n evictedEpochFloor = Math.max(evictedEpochFloor, oldest[1]);\n lastInvalidated.delete(oldest[0]);\n }\n}\n\n/**\n * Whether the key or any tag was invalidated after `startEpoch` (the epoch\n * captured when fn started executing).\n */\nexport function wasInvalidatedSince(startEpoch: number, key: string, tags: string[]): boolean {\n return lastInvalidationEpochFor(key, tags) > startEpoch;\n}\n\n/**\n * The highest epoch at which the key or any of the tags was invalidated\n * (0 if never). Used to scope the singleflight key: callers arriving after\n * an invalidation must not coalesce onto a flight that started before it —\n * they would receive the pre-invalidation result the invalidation was\n * issued to discard.\n */\nexport function lastInvalidationEpochFor(key: string, tags: string[]): number {\n let last = evictedEpochFloor;\n const keyEpoch = lastInvalidated.get(`key:${key}`);\n if (keyEpoch !== undefined && keyEpoch > last) last = keyEpoch;\n for (const tag of tags) {\n const tagEpoch = lastInvalidated.get(`tag:${tag}`);\n if (tagEpoch !== undefined && tagEpoch > last) last = tagEpoch;\n }\n return last;\n}\n","import type { CacheHandler, CacheOptions } from './index';\nimport { stableStringify } from './stable-stringify';\nimport { createSingleflight } from './singleflight';\nimport {\n currentInvalidationEpoch,\n lastInvalidationEpochFor,\n wasInvalidatedSince,\n} from './invalidation-epoch';\nimport { addSpanEventSync } from '../server/tracing.js';\nimport { logSwrRefetchFailed } from '../server/logger.js';\nimport { getWaitUntil } from '../server/waituntil-bridge.js';\nimport { fnv1aHash } from './fast-hash.js';\n\nlet defaultSingleflight = createSingleflight();\n\n/**\n * Set the timeout for the module-level default singleflight.\n * Called at framework boot with renderTimeoutMs from timber.config.ts so that\n * cache calls without a per-call timeoutMs still get deadlock protection.\n * See design/06-caching.md §\"Singleflight Timeout — Deadlock Risk\".\n */\nexport function setDefaultSingleflightTimeout(timeoutMs: number): void {\n defaultSingleflight = createSingleflight({ timeoutMs });\n}\n\n/**\n * Generate a cache key from function identity and serialized args.\n *\n * Uses FNV-1a (fast non-crypto hash) instead of SHA-256. Cache keys don't\n * need collision resistance — they need speed. The fnId prefix provides\n * namespace isolation; the hash covers the args.\n *\n * See TIM-370 for perf motivation.\n */\nfunction defaultKeyGenerator(fnId: string, args: unknown[]): string {\n const raw = fnId + ':' + stableStringify(args);\n return fnId + ':' + fnv1aHash(raw);\n}\n\n/**\n * Resolve tags from the options — supports static array or function form.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction resolveTags<Fn extends (...args: any[]) => any>(\n opts: CacheOptions<Fn>,\n args: Parameters<Fn>\n): string[] {\n if (!opts.tags) return [];\n if (Array.isArray(opts.tags)) return opts.tags;\n return opts.tags(...args);\n}\n\n/** Per-process count of byte-identical fn sources seen by deriveFallbackFnId. */\nconst fallbackIdOccurrences = new Map<string, number>();\nlet hasWarnedFallbackId = false;\n\n/**\n * Derive a fallback fnId for callsites the timber-cache-transform Vite plugin\n * could not statically detect (aliased bindings, user re-export wrappers).\n *\n * The base is fnv1a(fn.toString()) — deterministic across instances and cold\n * starts of the same build, so two *different* functions can never swap fnIds\n * by module-evaluation order (the TIM-997 poisoning scenario that the old\n * process-local counter allowed; see TIM-1054). A per-process occurrence\n * suffix disambiguates byte-identical duplicate sources. That residue is the\n * one remaining hazard: closures produced by the same factory share source\n * text but capture different values, and their occurrence order may differ\n * across instances — such callsites need an explicit `key` option.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction deriveFallbackFnId(fn: (...args: any[]) => any): string {\n const sourceHash = fnv1aHash(fn.toString());\n const occurrence = fallbackIdOccurrences.get(sourceHash) ?? 0;\n fallbackIdOccurrences.set(sourceHash, occurrence + 1);\n\n if (!hasWarnedFallbackId && typeof process !== 'undefined' && process.env?.NODE_ENV !== 'test') {\n hasWarnedFallbackId = true;\n console.warn(\n '[timber] cache() callsite has no build-time stable ID and no explicit `key` option — ' +\n 'the call is not statically visible to the timber Vite plugin (e.g. an aliased ' +\n 'binding like `const c = cache` or a re-export wrapper module). Falling back to a ' +\n 'function-source hash for the cache key. With a shared CacheHandler (Redis/KV) this ' +\n 'is safe for distinct functions, but byte-identical closures from a factory can swap ' +\n 'cache entries across instances. Provide an explicit `key` option to make the cache ' +\n 'key contract stable and auditable.'\n );\n }\n\n return `timber-cache:fn:${sourceHash}:${occurrence}`;\n}\n\n/**\n * Creates a cached wrapper around an async function.\n *\n * - FNV-1a default keys with normalized JSON args\n * - Singleflight: concurrent misses → single execution AND single\n * handler.set — the write happens inside the coalesced callback (TIM-1028)\n * - Writes are skipped when the key/tags were invalidated while fn was in\n * flight, so explicit invalidation is never silently undone (TIM-1028)\n * - SWR: serve stale immediately, background refetch\n * - Tags as string[] or function of args\n * - The handler is resolved per invocation via `getHandler`, never captured\n * at wrapper-creation time — module-scope cache() wrappers created before\n * the framework's boot wiring calls setCacheHandler() still use the\n * configured handler once it arrives (TIM-1029)\n * - No ALS dependency for correctness — cache() works outside a request\n * context. The waitUntil ALS bridge is read optionally so the SWR\n * background refetch survives handler return on Workers/Lambda.\n *\n * Cache hits/misses are recorded as OTEL span events on the enclosing\n * span (not child spans). The DevSpanProcessor reads these for dev log output.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function createCache<Fn extends (...args: any[]) => Promise<any>>(\n fn: Fn,\n opts: CacheOptions<Fn>,\n getHandler: () => CacheHandler,\n stableId?: string\n): Fn {\n // With an explicit `key` option the fnId never reaches key generation, so\n // no fallback derivation (or warning) is needed in that case.\n const fnId = stableId ?? (opts.key ? undefined : deriveFallbackFnId(fn));\n\n // Per-call timeoutMs gets a dedicated singleflight instance. Otherwise\n // resolve defaultSingleflight per invocation (not captured at creation\n // time) so wrappers created before boot pick up the timeout once\n // setDefaultSingleflightTimeout is called (TIM-1032, same rationale as\n // getCacheHandler for TIM-1029).\n const perCallSf =\n opts.timeoutMs !== undefined ? createSingleflight({ timeoutMs: opts.timeoutMs }) : undefined;\n\n // Cast to Fn to preserve the original function's generic call signature.\n // Without this, generic type parameters (e.g. <T> in apiFetch<T>) are\n // erased and callers lose type safety on the return type.\n return (async (...args: Parameters<Fn>): Promise<Awaited<ReturnType<Fn>>> => {\n const sf = perCallSf ?? defaultSingleflight;\n const key = opts.key ? opts.key(...args) : defaultKeyGenerator(fnId as string, args);\n\n const cacheStart = performance.now();\n const cached = await getHandler().get(key);\n\n if (cached && !cached.stale) {\n // Record as OTEL span event on enclosing span (not a child span).\n // Fire-and-forget — no microtask overhead on the cache hot path.\n addSpanEventSync('timber.cache.hit', {\n key,\n duration_ms: Math.round(performance.now() - cacheStart),\n });\n return cached.value as Awaited<ReturnType<Fn>>;\n }\n\n // From here fn() will execute (SWR refetch or miss). Scope the\n // singleflight key by the last invalidation epoch affecting this\n // key/tags: a caller arriving after an invalidation must not coalesce\n // onto a flight that started before it — it would receive the\n // pre-invalidation result the invalidation was issued to discard.\n // NUL separator: cannot collide with user-provided key strings.\n const tags = resolveTags(opts, args);\n const flightKey = `${key}\\u0000${lastInvalidationEpochFor(key, tags)}`;\n\n /**\n * Run fn and store the result — exactly once per coalesced execution,\n * inside the singleflight callback. The write is skipped when the key or\n * any resolved tag was invalidated after fn started: storing would\n * resurrect just-invalidated data as fresh for the full TTL.\n */\n const executeAndStore = async (): Promise<Awaited<ReturnType<Fn>>> => {\n const startEpoch = currentInvalidationEpoch();\n const result = await fn(...args);\n if (!wasInvalidatedSince(startEpoch, key, tags)) {\n await getHandler().set(key, result, { ttl: opts.ttl, tags });\n }\n return result;\n };\n\n if (cached && cached.stale && opts.staleWhileRevalidate) {\n // Record stale cache hit as OTEL span event (fire-and-forget).\n addSpanEventSync('timber.cache.hit', {\n key,\n duration_ms: Math.round(performance.now() - cacheStart),\n stale: true,\n });\n // Serve stale immediately, trigger background refetch\n const refetch = sf\n .do(`swr:${flightKey}`, async () => {\n try {\n await executeAndStore();\n } catch (err) {\n // Failed refetch — stale entry continues to be served.\n logSwrRefetchFailed({ cacheKey: key, error: err });\n }\n })\n .catch(() => {\n // Singleflight promise rejection handled — stale continues.\n });\n // Platforms that cancel work when the handler returns (Cloudflare\n // Workers, Lambda) would otherwise kill the refetch mid-flight —\n // stale data re-served forever. Register the (never-rejecting)\n // refetch with the request's waitUntil; undefined outside a request\n // context or on platforms without lifecycle extension (Node servers\n // keep floating promises alive, so behavior there is unchanged).\n getWaitUntil()?.(refetch);\n return cached.value as Awaited<ReturnType<Fn>>;\n }\n\n // Cache miss (or stale without SWR) — execute with singleflight.\n // The handler.set lives inside the coalesced callback, so N concurrent\n // misses produce exactly one write (TIM-1028).\n const result = await sf.do(flightKey, executeAndStore);\n\n // Record cache miss as OTEL span event (fire-and-forget).\n addSpanEventSync('timber.cache.miss', {\n key,\n duration_ms: Math.round(performance.now() - cacheStart),\n });\n\n return result;\n }) as unknown as Fn;\n}\n","/**\n * Module-level cache handler singleton.\n *\n * Lazily initialized to MemoryCacheHandler on first access. The framework\n * replaces this at boot from timber.config.ts via setCacheHandler().\n *\n * This module avoids importing from ./index to prevent circular dependencies.\n */\n\n// Inline the interface to avoid circular import with index.ts\ninterface CacheHandlerLike {\n get(key: string): Promise<{ value: unknown; stale: boolean } | null>;\n set(key: string, value: unknown, opts: { ttl: number; tags: string[] }): Promise<void>;\n invalidate(opts: { key?: string; tag?: string }): Promise<void>;\n}\n\nlet handler: CacheHandlerLike | null = null;\n\n/** Replace the active cache handler. Called by the framework at boot. */\nexport function setCacheHandler(h: CacheHandlerLike): void {\n handler = h;\n}\n\n/**\n * Get the active cache handler. Creates a default MemoryCacheHandler on\n * first access if none has been set via setCacheHandler().\n */\nexport function getCacheHandler(): CacheHandlerLike {\n if (!handler) {\n // Inline a minimal LRU cache to avoid circular dep with index.ts.\n // In production, the framework always calls setCacheHandler() at boot.\n handler = createDefaultHandler();\n }\n return handler;\n}\n\nfunction createDefaultHandler(): CacheHandlerLike {\n const store = new Map<string, { value: unknown; expiresAt: number; tags: string[] }>();\n const maxEntries = 1000;\n\n return {\n async get(key) {\n const entry = store.get(key);\n if (!entry) return null;\n store.delete(key);\n store.set(key, entry);\n const stale = Date.now() > entry.expiresAt;\n return { value: entry.value, stale };\n },\n async set(key, value, opts) {\n if (store.has(key)) store.delete(key);\n while (store.size >= maxEntries) {\n const oldest = store.keys().next().value;\n if (oldest !== undefined) store.delete(oldest);\n else break;\n }\n store.set(key, { value, expiresAt: Date.now() + opts.ttl * 1000, tags: opts.tags });\n },\n async invalidate(opts) {\n if (opts.key) store.delete(opts.key);\n if (opts.tag) {\n for (const [key, entry] of store) {\n if (entry.tags.includes(opts.tag)) store.delete(key);\n }\n }\n },\n };\n}\n","import type { CacheOptions } from './index';\nimport { createCache } from './timber-cache';\nimport { getCacheHandler } from './handler-store';\nimport { recordInvalidation } from './invalidation-epoch';\n\n/**\n * Public caching API: `cache(fn, opts)`.\n *\n * Wraps an async function with cross-request caching. Uses the configured\n * cache handler (defaults to MemoryCacheHandler, overridable via timber.config.ts).\n *\n * ```ts\n * import { cache } from '@timber-js/app/cache';\n *\n * const getUser = cache(\n * async (id: string) => db.users.findUnique({ where: { id } }),\n * { ttl: 60, tags: (id) => [`user:${id}`] }\n * );\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function cache<Fn extends (...args: any[]) => Promise<any>>(\n fn: Fn,\n opts: CacheOptions<Fn>,\n stableId?: string\n): Fn {\n // Pass the getter, not a resolved handler: the wrapper re-resolves it on\n // every call, so module-scope wrappers created before the framework's boot\n // wiring calls setCacheHandler() still pick up the configured handler\n // instead of permanently binding the in-memory fallback (TIM-1029).\n return createCache(fn, opts, getCacheHandler, stableId);\n}\n\n/**\n * Invalidate cache entries by tag or key.\n *\n * ```ts\n * cache.invalidate({ tag: 'products' });\n * cache.invalidate({ key: 'user:abc' });\n * ```\n */\ncache.invalidate = async function invalidate(opts: { key?: string; tag?: string }): Promise<void> {\n // Record before the handler delete so a cached fn in flight skips its\n // post-resolve set instead of resurrecting the invalidated value (TIM-1028).\n recordInvalidation(opts);\n await getCacheHandler().invalidate(opts);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAcA,SAAgB,gBAAgB,OAAwB;CACtD,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,OAAO,KAAK;CAE9D,MAAM,OAAO,OAAO;CACpB,IAAI,SAAS,cAAc,SAAS,UAClC,MAAM,IAAI,UACR,uCAAuC,KAAK,yGAE9C;CAEF,IAAI,SAAS,UAAU,OAAO,KAAK,UAAU,KAAK;CAElD,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,MAAM,KAAK,SAAS,gBAAgB,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI;CAOtE,MAAM,aAAa;CACnB,IAAI,OAAO,WAAW,WAAW,YAAY;EAC3C,MAAM,OAAO,WAAW,OAAO;EAC/B,IAAI,SAAS,OAAO,OAAO,gBAAgB,IAAI;CACjD;CAMA,IAAI,iBAAiB,KAInB,OAAO,SAHS,CAAC,GAAG,MAAM,QAAQ,CAAC,CAAC,CAAC,KAClC,CAAC,GAAG,OAAO,gBAAgB,CAAC,IAAI,OAAO,gBAAgB,CAAC,CAE3C,CAAA,CAAQ,KAAK,CAAC,CAAC,KAAK,GAAG,IAAI;CAE7C,IAAI,iBAAiB,KAEnB,OAAO,SADS,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,WAAW,gBAAgB,MAAM,CACjD,CAAA,CAAQ,KAAK,CAAC,CAAC,KAAK,GAAG,IAAI;CAG7C,MAAM,MAAM;CACZ,MAAM,OAAO,OAAO,KAAK,GAAG,CAAC,CAAC,KAAK;CACnC,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,OAAO,MAAM;EACtB,IAAI,IAAI,SAAS,KAAA,GAAW;EAI5B,IAAI,QAAQ,YAAY,OAAO,IAAI,SAAS,YAAY;EACxD,MAAM,KAAK,KAAK,UAAU,GAAG,IAAI,MAAM,gBAAgB,IAAI,IAAI,CAAC;CAClE;CAKA,IAAI,MAAM,WAAW,GAAG;EACtB,MAAM,QAAQ,OAAO,eAAe,KAAK;EACzC,IAAI,UAAU,OAAO,aAAa,UAAU,MAAM;GAChD,MAAM,OAAQ,MAAiB,aAAa,QAAQ;GACpD,MAAM,IAAI,UACR,oBAAoB,KAAK,4JAG3B;EACF;CACF;CAEA,OAAO,MAAM,MAAM,KAAK,GAAG,IAAI;AACjC;;;;;;;ACzDA,IAAa,2BAAb,cAA8C,MAAM;CAClD,YAAY,KAAa,WAAmB;EAC1C,MAAM,8BAA8B,IAAI,aAAa,UAAU,GAAG;EAClE,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,mBAAmB,MAA0C;CAC3E,MAAM,2BAAW,IAAI,IAA8B;CACnD,MAAM,YAAY,MAAM;CAExB,OAAO,EACL,GAAM,KAAa,IAAkC;EACnD,MAAM,WAAW,SAAS,IAAI,GAAG;EACjC,IAAI,UAAU,OAAO;EAErB,IAAI;EAEJ,IAAI,aAAa,QAAQ,YAAY,GAGnC,UAAU,IAAI,SAAY,SAAS,WAAW;GAC5C,MAAM,QAAQ,iBACN,OAAO,IAAI,yBAAyB,KAAK,SAAS,CAAC,GACzD,SACF;GAIA,IAAI;IACF,GAAG,CAAC,CAAC,MACF,UAAU;KACT,aAAa,KAAK;KAClB,QAAQ,KAAK;IACf,IACC,QAAQ;KACP,aAAa,KAAK;KAClB,OAAO,GAAG;IACZ,CACF;GACF,SAAS,KAAK;IACZ,aAAa,KAAK;IAClB,OAAO,GAAG;GACZ;EACF,CAAC;OAED,UAAU,GAAG;EAGf,MAAM,UAAU,QAAQ,cAAc;GACpC,SAAS,OAAO,GAAG;EACrB,CAAC;EAED,SAAS,IAAI,KAAK,OAAO;EACzB,OAAO;CACT,EACF;AACF;;;;;;;;;;;;;;;;;;;;;;AC/DA,IAAI,QAAQ;;;;;;AAOZ,IAAM,kCAAkB,IAAI,IAAoB;;;;;AAMhD,IAAM,sBAAsB;;;;;;AAO5B,IAAI,oBAAoB;;AAGxB,SAAgB,2BAAmC;CACjD,OAAO;AACT;;;;;AAMA,SAAgB,mBAAmB,MAA4C;CAC7E;CACA,IAAI,KAAK,KAAK,MAAM,OAAO,KAAK,KAAK;CACrC,IAAI,KAAK,KAAK,MAAM,OAAO,KAAK,KAAK;AACvC;AAEA,SAAS,MAAM,OAAqB;CAClC,gBAAgB,OAAO,KAAK;CAC5B,gBAAgB,IAAI,OAAO,KAAK;CAChC,OAAO,gBAAgB,OAAO,qBAAqB;EACjD,MAAM,SAAS,gBAAgB,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC;EAChD,IAAI,WAAW,KAAA,GAAW;EAC1B,oBAAoB,KAAK,IAAI,mBAAmB,OAAO,EAAE;EACzD,gBAAgB,OAAO,OAAO,EAAE;CAClC;AACF;;;;;AAMA,SAAgB,oBAAoB,YAAoB,KAAa,MAAyB;CAC5F,OAAO,yBAAyB,KAAK,IAAI,IAAI;AAC/C;;;;;;;;AASA,SAAgB,yBAAyB,KAAa,MAAwB;CAC5E,IAAI,OAAO;CACX,MAAM,WAAW,gBAAgB,IAAI,OAAO,KAAK;CACjD,IAAI,aAAa,KAAA,KAAa,WAAW,MAAM,OAAO;CACtD,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,WAAW,gBAAgB,IAAI,OAAO,KAAK;EACjD,IAAI,aAAa,KAAA,KAAa,WAAW,MAAM,OAAO;CACxD;CACA,OAAO;AACT;;;AC/EA,IAAI,sBAAsB,mBAAmB;;;;;;;AAQ7C,SAAgB,8BAA8B,WAAyB;CACrE,sBAAsB,mBAAmB,EAAE,UAAU,CAAC;AACxD;;;;;;;;;;AAWA,SAAS,oBAAoB,MAAc,MAAyB;CAClE,MAAM,MAAM,OAAO,MAAM,gBAAgB,IAAI;CAC7C,OAAO,OAAO,MAAM,UAAU,GAAG;AACnC;;;;AAMA,SAAS,YACP,MACA,MACU;CACV,IAAI,CAAC,KAAK,MAAM,OAAO,CAAC;CACxB,IAAI,MAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,KAAK;CAC1C,OAAO,KAAK,KAAK,GAAG,IAAI;AAC1B;;AAGA,IAAM,wCAAwB,IAAI,IAAoB;AACtD,IAAI,sBAAsB;;;;;;;;;;;;;;AAgB1B,SAAS,mBAAmB,IAAqC;CAC/D,MAAM,aAAa,UAAU,GAAG,SAAS,CAAC;CAC1C,MAAM,aAAa,sBAAsB,IAAI,UAAU,KAAK;CAC5D,sBAAsB,IAAI,YAAY,aAAa,CAAC;CAEpD,IAAI,CAAC,uBAAuB,OAAO,YAAY,eAAA,QAAA,IAAA,aAAyC,QAAQ;EAC9F,sBAAsB;EACtB,QAAQ,KACN,khBAOF;CACF;CAEA,OAAO,mBAAmB,WAAW,GAAG;AAC1C;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,YACd,IACA,MACA,YACA,UACI;CAGJ,MAAM,OAAO,aAAa,KAAK,MAAM,KAAA,IAAY,mBAAmB,EAAE;CAOtE,MAAM,YACJ,KAAK,cAAc,KAAA,IAAY,mBAAmB,EAAE,WAAW,KAAK,UAAU,CAAC,IAAI,KAAA;CAKrF,QAAQ,OAAO,GAAG,SAA2D;EAC3E,MAAM,KAAK,aAAa;EACxB,MAAM,MAAM,KAAK,MAAM,KAAK,IAAI,GAAG,IAAI,IAAI,oBAAoB,MAAgB,IAAI;EAEnF,MAAM,aAAa,YAAY,IAAI;EACnC,MAAM,SAAS,MAAM,WAAW,CAAC,CAAC,IAAI,GAAG;EAEzC,IAAI,UAAU,CAAC,OAAO,OAAO;GAG3B,iBAAiB,oBAAoB;IACnC;IACA,aAAa,KAAK,MAAM,YAAY,IAAI,IAAI,UAAU;GACxD,CAAC;GACD,OAAO,OAAO;EAChB;EAQA,MAAM,OAAO,YAAY,MAAM,IAAI;EACnC,MAAM,YAAY,GAAG,IAAI,QAAQ,yBAAyB,KAAK,IAAI;;;;;;;EAQnE,MAAM,kBAAkB,YAA8C;GACpE,MAAM,aAAa,yBAAyB;GAC5C,MAAM,SAAS,MAAM,GAAG,GAAG,IAAI;GAC/B,IAAI,CAAC,oBAAoB,YAAY,KAAK,IAAI,GAC5C,MAAM,WAAW,CAAC,CAAC,IAAI,KAAK,QAAQ;IAAE,KAAK,KAAK;IAAK;GAAK,CAAC;GAE7D,OAAO;EACT;EAEA,IAAI,UAAU,OAAO,SAAS,KAAK,sBAAsB;GAEvD,iBAAiB,oBAAoB;IACnC;IACA,aAAa,KAAK,MAAM,YAAY,IAAI,IAAI,UAAU;IACtD,OAAO;GACT,CAAC;GAED,MAAM,UAAU,GACb,GAAG,OAAO,aAAa,YAAY;IAClC,IAAI;KACF,MAAM,gBAAgB;IACxB,SAAS,KAAK;KAEZ,oBAAoB;MAAE,UAAU;MAAK,OAAO;KAAI,CAAC;IACnD;GACF,CAAC,CAAC,CACD,YAAY,CAEb,CAAC;GAOH,aAAa,CAAC,GAAG,OAAO;GACxB,OAAO,OAAO;EAChB;EAKA,MAAM,SAAS,MAAM,GAAG,GAAG,WAAW,eAAe;EAGrD,iBAAiB,qBAAqB;GACpC;GACA,aAAa,KAAK,MAAM,YAAY,IAAI,IAAI,UAAU;EACxD,CAAC;EAED,OAAO;CACT;AACF;;;AC1MA,IAAI,UAAmC;;AAGvC,SAAgB,gBAAgB,GAA2B;CACzD,UAAU;AACZ;;;;;AAMA,SAAgB,kBAAoC;CAClD,IAAI,CAAC,SAGH,UAAU,qBAAqB;CAEjC,OAAO;AACT;AAEA,SAAS,uBAAyC;CAChD,MAAM,wBAAQ,IAAI,IAAmE;CACrF,MAAM,aAAa;CAEnB,OAAO;EACL,MAAM,IAAI,KAAK;GACb,MAAM,QAAQ,MAAM,IAAI,GAAG;GAC3B,IAAI,CAAC,OAAO,OAAO;GACnB,MAAM,OAAO,GAAG;GAChB,MAAM,IAAI,KAAK,KAAK;GACpB,MAAM,QAAQ,KAAK,IAAI,IAAI,MAAM;GACjC,OAAO;IAAE,OAAO,MAAM;IAAO;GAAM;EACrC;EACA,MAAM,IAAI,KAAK,OAAO,MAAM;GAC1B,IAAI,MAAM,IAAI,GAAG,GAAG,MAAM,OAAO,GAAG;GACpC,OAAO,MAAM,QAAQ,YAAY;IAC/B,MAAM,SAAS,MAAM,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,WAAW,KAAA,GAAW,MAAM,OAAO,MAAM;SACxC;GACP;GACA,MAAM,IAAI,KAAK;IAAE;IAAO,WAAW,KAAK,IAAI,IAAI,KAAK,MAAM;IAAM,MAAM,KAAK;GAAK,CAAC;EACpF;EACA,MAAM,WAAW,MAAM;GACrB,IAAI,KAAK,KAAK,MAAM,OAAO,KAAK,GAAG;GACnC,IAAI,KAAK;SACF,MAAM,CAAC,KAAK,UAAU,OACzB,IAAI,MAAM,KAAK,SAAS,KAAK,GAAG,GAAG,MAAM,OAAO,GAAG;GAAA;EAGzD;CACF;AACF;;;;;;;;;;;;;;;;;;AC9CA,SAAgB,MACd,IACA,MACA,UACI;CAKJ,OAAO,YAAY,IAAI,MAAM,iBAAiB,QAAQ;AACxD;;;;;;;;;AAUA,MAAM,aAAa,eAAe,WAAW,MAAqD;CAGhG,mBAAmB,IAAI;CACvB,MAAM,gBAAgB,CAAC,CAAC,WAAW,IAAI;AACzC"}
|