@ultimat3/render 27.7.0 → 27.8.0

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.
@@ -0,0 +1,102 @@
1
+ /**
2
+ * The ISR controller's contract: what a render hands it, what a request gets back, what it can be
3
+ * built with and what it offers. Declarations only — `render-isr.ts` is the controller.
4
+ */
5
+
6
+ import type { CacheFence, CacheTag, FenceScope } from '@ultimat3/cache';
7
+ import type { Scheduler } from '@ultimat3/core';
8
+ import type { RouteDescriptor } from './registry';
9
+ import type { IsrEntry, IsrState, IsrStore } from './render-isr-store';
10
+ import type { RenderResult } from './route';
11
+
12
+ /**
13
+ * A render that also answers a status — what a loader's `withStatus(404, …)` becomes once the
14
+ * document is built. A bare string is the 200 every render before this one was: the union is
15
+ * additive, and a render function that never learned the object shape keeps compiling.
16
+ */
17
+ export interface IsrRendered {
18
+ readonly html: string;
19
+ readonly status: number;
20
+ /**
21
+ * The render's own "do not keep this answer" — what a loader's `noStore(data)` becomes. Served to
22
+ * the request that asked, `private, no-store`, and never written to the store. A 5xx needs no
23
+ * flag: it is never stored whatever this says.
24
+ */
25
+ readonly noStore?: boolean;
26
+ }
27
+
28
+ /** One regeneration's outcome: the entry it rendered, and whether that entry was kept. */
29
+ export interface Generation {
30
+ readonly entry: IsrEntry;
31
+ readonly cacheable: boolean;
32
+ /**
33
+ * A page that WOULD have been kept, refused because something invalidated it while it rendered.
34
+ * The request that led the render predates that and is answered it, uncached; a request that
35
+ * merely joined may have arrived after, so it renders for itself (`serve`).
36
+ */
37
+ readonly fenced: boolean;
38
+ }
39
+
40
+ /**
41
+ * The process fence a controller samples and marks — `@ultimat3/cache`'s, by default. A seam so
42
+ * two controllers in one test can stand for two PROCESSES: sharing the real one made every
43
+ * shared-store test pass on a fence two replicas never share.
44
+ */
45
+ export interface IsrFence {
46
+ sample(scope: FenceScope): CacheFence;
47
+ mark(scope: FenceScope): void;
48
+ }
49
+
50
+ export type IsrRenderFn = (path: string) => string | IsrRendered | Promise<string | IsrRendered>;
51
+
52
+ export interface IsrServeResult {
53
+ readonly state: IsrState;
54
+ readonly entry: IsrEntry;
55
+ readonly result: RenderResult;
56
+ /** True when this request started a background regeneration. */
57
+ readonly regenerating: boolean;
58
+ }
59
+
60
+ export interface IsrControllerOptions {
61
+ readonly store?: IsrStore;
62
+ readonly buildId?: string;
63
+ readonly now?: () => number;
64
+ /** Route table provider — defaults to the real registry. */
65
+ readonly routes?: () => readonly RouteDescriptor[];
66
+ /** ISR-route dependents for a tag set; defaults to `@ultimat3/cache`'s graph. */
67
+ readonly isrDependents?: (tags: readonly CacheTag[]) => readonly string[];
68
+ /**
69
+ * How long one regeneration may hold its path, in whole ms (≥ 1). Defaults to
70
+ * `DEFAULT_ISR_REGENERATE_DEADLINE_MS`. There is no "forever": that is the bug it bounds.
71
+ */
72
+ readonly regenerateDeadlineMs?: number | undefined;
73
+ /** Injected so the deadline is provable without waiting one out. */
74
+ readonly schedule?: Scheduler | undefined;
75
+ /** See `IsrFence`. Defaults to the process's own. */
76
+ readonly fence?: IsrFence | undefined;
77
+ /**
78
+ * How long a 5xx render answers the requests behind it instead of each rendering again, in
79
+ * whole ms. Defaults to `DEFAULT_ISR_FAILURE_COOLDOWN_MS`; `0` renders on every request.
80
+ */
81
+ readonly failureCooldownMs?: number | undefined;
82
+ }
83
+
84
+ export interface IsrController {
85
+ serve(path: string, render: IsrRenderFn): Promise<IsrServeResult>;
86
+ /** Single-flight: concurrent callers for the same path share one render. */
87
+ regenerate(path: string, render: IsrRenderFn): Promise<IsrEntry>;
88
+ markStale(path: string): boolean;
89
+ /**
90
+ * Invalidate every ISR page carrying one of these tags — the ones the cache graph knows AND the
91
+ * ones only the store holds — as each page's route declared: marked stale, or purged.
92
+ */
93
+ revalidateByTags(tags: readonly CacheTag[]): readonly string[];
94
+ inflight(): number;
95
+ store(): IsrStore;
96
+ /**
97
+ * Register this controller as the framework's revalidator, so
98
+ * `action({ cache: { invalidates: [tag.post] } })` reaches ISR in the same hop as
99
+ * memo, LRU, Redis and the CDN. Returns a detach function for tests and reloads.
100
+ */
101
+ attach(): () => void;
102
+ }