@silverassist/agents-toolkit 2.5.0 → 2.5.1
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@silverassist/agents-toolkit",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"description": "Reusable AI agent prompts for development workflows with Jira integration — supports GitHub Copilot, Claude Code, and Codex",
|
|
5
5
|
"author": "Santiago Ramirez",
|
|
6
6
|
"license": "PolyForm-Noncommercial-1.0.0",
|
package/src/index.js
CHANGED
|
@@ -22,10 +22,26 @@ the others.
|
|
|
22
22
|
That leaves POST reads uncached and forces the whole route into dynamic rendering
|
|
23
23
|
(`cache-control: private, no-store`).
|
|
24
24
|
|
|
25
|
-
2. **A `POST`
|
|
26
|
-
Next.js does not cache `POST` by default
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
2. **A `POST` read caches its DATA with `next` options — but the ROUTE still renders dynamically.**
|
|
26
|
+
Next.js does not cache `POST` by default; any read `fetch` (GET or POST) to CCDS or the WP GraphQL
|
|
27
|
+
endpoint must set `next: { revalidate, tags }` (the body is part of the cache key, so different
|
|
28
|
+
filter bodies cache independently). **However** (WEB-1069, Next 16) a page whose data comes from a
|
|
29
|
+
POST read still prerenders as `ƒ` (Dynamic) and serves `cache-control: private, no-store` even with
|
|
30
|
+
those options — the data-fetch cache and the route's static/dynamic classification are independent.
|
|
31
|
+
So `next: { revalidate, tags }` is **necessary but not sufficient** to make a POST-read page
|
|
32
|
+
CDN-cacheable; you must also apply a rendering/edge strategy (rule 2b). GET reads ISR natively and
|
|
33
|
+
need nothing extra.
|
|
34
|
+
|
|
35
|
+
2b. **Make a POST-read page cacheable at the rendering/edge layer.** Pick one (lowest risk first):
|
|
36
|
+
- **CDN edge override (current/default):** `src/proxy.ts` matches city/community paths and sets
|
|
37
|
+
`Cache-Control: public, s-maxage=2592000, stale-while-revalidate=2592000` — the same header ISR
|
|
38
|
+
pages emit (see rule 7 `expireTime`), so the CDN policy is uniform. Origin stays dynamic; the CDN
|
|
39
|
+
caches the deterministic-per-URL response. No build-time risk. This is what WEB-1069 shipped.
|
|
40
|
+
- **`export const dynamic = "force-static"` (interim):** prerenders the route as real ISR, but a
|
|
41
|
+
bad CCDS record/response at build time caches a blank page (the WEB-1058 regression) — use only
|
|
42
|
+
when the page is fully null-safe and reads throw on 5xx at runtime. Validate per repo.
|
|
43
|
+
- **`cacheComponents: true` + `use cache` (strategic):** PPR migration; do not mix ad hoc with
|
|
44
|
+
route-segment `export const revalidate`.
|
|
29
45
|
|
|
30
46
|
3. **Always pair `tags` with a `revalidate` duration.** `next: { tags }` alone either holds stale data
|
|
31
47
|
indefinitely or (Next 16 default) does not cache at runtime at all. Use a `CACHE_DURATIONS`-style
|
|
@@ -34,18 +50,26 @@ the others.
|
|
|
34
50
|
4. **`React.cache()` is request dedup only.** It collapses duplicate calls within a single render. It
|
|
35
51
|
does **not** cache across requests and is never a substitute for `next: { revalidate }`.
|
|
36
52
|
|
|
37
|
-
5. **Every cacheable route exports `revalidate`.**
|
|
38
|
-
(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
53
|
+
5. **Every cacheable route exports `revalidate`.** CCDS/WP data changes rarely and is refreshed
|
|
54
|
+
on-demand (webhooks + tags), so the default is intentionally **long: `2592000` (30d)** for CCDS
|
|
55
|
+
reads, `WP_CACHE_DURATIONS`, and page segments alike (WEB-1069). Shorter tiers (24h/7d) are fine
|
|
56
|
+
per-route for volatile sources. Do **not** add `revalidate` to mutation/personalized routes (forms,
|
|
57
|
+
thank-you, wizards). Default to `export const revalidate` over `dynamic = "force-static"` so a
|
|
58
|
+
failed ISR revalidation preserves the last good cache instead of overwriting it with a broken page
|
|
59
|
+
(force-static is still a valid POST-read option per rule 2b once null-safety + throw-on-5xx exist).
|
|
42
60
|
|
|
43
61
|
6. **On-demand revalidation dual-invalidates.** `/api/revalidate` must call `revalidateTag`/
|
|
44
62
|
`revalidatePath` **and** the CDN invalidation (e.g. `invalidateCloudFrontPaths`) in the same
|
|
45
63
|
request, otherwise the CDN keeps serving stale until its own TTL.
|
|
46
64
|
|
|
47
|
-
7.
|
|
48
|
-
`
|
|
65
|
+
7. **`next.config` cache config.** `expireTime` sets the CDN stale window: Next emits
|
|
66
|
+
`s-maxage=<revalidate>, stale-while-revalidate=<expireTime − revalidate>` for ISR pages, so
|
|
67
|
+
`expireTime` **must be ≥ the largest page `revalidate`**. Set `expireTime: 5184000` (60d) so a 30d
|
|
68
|
+
page yields `s-maxage=2592000, stale-while-revalidate=2592000` — the same header the proxy sets on
|
|
69
|
+
dynamic pages (rule 2b). (WEB-1069 bug: `expireTime: 86400` under a 30d revalidate = invalid stale
|
|
70
|
+
window.) Also set image `minimumCacheTTL: 31536000` (1y — optimized images are content-hashed and
|
|
71
|
+
never change), `qualities`, and `formats: ["image/avif", "image/webp"]`. No malformed
|
|
72
|
+
`remotePatterns` hostnames.
|
|
49
73
|
|
|
50
74
|
8. **Asset proxy TTLs** are type-keyed (image/css/font) at `365d + SWR 30d`.
|
|
51
75
|
|
|
@@ -67,7 +91,7 @@ export async function fetchData<T>({
|
|
|
67
91
|
method = "GET",
|
|
68
92
|
body = null,
|
|
69
93
|
revalidateTag,
|
|
70
|
-
revalidate =
|
|
94
|
+
revalidate = 2592000, // 30d time-based fallback; pair with tags for surgical invalidation
|
|
71
95
|
mutation = false,
|
|
72
96
|
}: FetchOptions): Promise<T | null> {
|
|
73
97
|
const isMutation = mutation || method === "PUT" || method === "DELETE";
|
|
@@ -91,7 +115,7 @@ export async function fetchData<T>({
|
|
|
91
115
|
|
|
92
116
|
```ts
|
|
93
117
|
export const WP_CACHE_DURATIONS = {
|
|
94
|
-
pages:
|
|
118
|
+
pages: 2592000, posts: 2592000, menus: 2592000, staticPages: 2592000, default: 2592000, // 30d
|
|
95
119
|
} as const;
|
|
96
120
|
|
|
97
121
|
export async function fetchWPAPI<T>(
|
|
@@ -18,8 +18,14 @@ route — and when diagnosing "why isn't this page cached / why is it serving st
|
|
|
18
18
|
## Core principle
|
|
19
19
|
|
|
20
20
|
**Cache by intent (read vs. mutation), never by the HTTP method.** Some reads must use `POST` because
|
|
21
|
-
they take a body (e.g. CCDS `geo-search`).
|
|
22
|
-
|
|
21
|
+
they take a body (e.g. CCDS `geo-search`). Their *data* still has to cache like a `GET` — send
|
|
22
|
+
`next: { revalidate, tags }`, never `no-store`. Mutations must never cache.
|
|
23
|
+
|
|
24
|
+
**But caching the data is not the same as making the page static.** A POST read caches its response
|
|
25
|
+
cross-request, yet Next.js still renders the *route* dynamically (`private, no-store`). The data-fetch
|
|
26
|
+
cache (layer 2) and the route's static/dynamic classification (layers 1/5) are **independent** — see
|
|
27
|
+
the two sections below. This is the WEB-1069 gotcha and the reason city/community pages needed an edge
|
|
28
|
+
override even though their fetches were already cached.
|
|
23
29
|
|
|
24
30
|
---
|
|
25
31
|
|
|
@@ -41,20 +47,44 @@ whole route into **dynamic rendering**, which emits `cache-control: private, no-
|
|
|
41
47
|
|
|
42
48
|
---
|
|
43
49
|
|
|
44
|
-
##
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
**
|
|
57
|
-
|
|
50
|
+
## POST reads: the data caches, but the route stays dynamic (the key gotcha)
|
|
51
|
+
|
|
52
|
+
Two independent facts, both true:
|
|
53
|
+
|
|
54
|
+
1. **The data fetch caches.** From `next/dist/server/lib/patch-fetch.js`: a `fetch` is uncacheable
|
|
55
|
+
only when it has no `next`/`cache` options *and* the segment `revalidate` is `0`. So a POST **with**
|
|
56
|
+
`next: { revalidate }` IS cached cross-request — the **request body is part of the cache key**
|
|
57
|
+
(`generateCacheKey` hashes `init.body`), so different filter bodies cache independently. This is
|
|
58
|
+
real and bounds origin/CCDS load; it is why the WordPress GraphQL POST caches fine.
|
|
59
|
+
|
|
60
|
+
2. **The route still renders dynamically.** Empirically (WEB-1069, Next 16), a page whose data comes
|
|
61
|
+
from a POST read prerenders as `ƒ` (Dynamic) and serves `cache-control: private, no-store` — **even
|
|
62
|
+
with** `next: { revalidate, tags }` on the fetch. Next treats the POST as request-time data for
|
|
63
|
+
prerendering, so ISR (layer 1) never engages. GET reads do not have this problem — they ISR
|
|
64
|
+
natively (`s-maxage`, `x-nextjs-cache: HIT`).
|
|
65
|
+
|
|
66
|
+
**Implication:** `next: { revalidate, tags }` on a POST read is **necessary** (data cache) but **not
|
|
67
|
+
sufficient** to make the page CDN-cacheable. You must additionally pick a rendering/edge strategy
|
|
68
|
+
(next section). `revalidate` + `tags` alone will NOT flip a POST-read page from `private, no-store` to
|
|
69
|
+
`public`.
|
|
70
|
+
|
|
71
|
+
## Making a POST-read page cacheable (rendering / edge layer)
|
|
72
|
+
|
|
73
|
+
Pick one (ordered by risk, lowest first):
|
|
74
|
+
|
|
75
|
+
1. **CDN edge override (current, lowest risk).** `src/proxy.ts` matches the city/community paths and
|
|
76
|
+
sets `Cache-Control: public, s-maxage=2592000, stale-while-revalidate=2592000` — the **same**
|
|
77
|
+
header the ISR pages emit (see `expireTime` under ISR tiers), so the CDN policy is uniform across
|
|
78
|
+
static and dynamic routes. The origin stays dynamic; the CDN caches the deterministic-per-URL
|
|
79
|
+
response. Per-repo regex, no build-time risk — this is what WEB-1069 shipped.
|
|
80
|
+
2. **`export const dynamic = "force-static"` (interim).** Forces the POST read to `force-cache` and
|
|
81
|
+
prerenders the route as real ISR (confirmed via `prerender-manifest.json`). Removed in WEB-1058
|
|
82
|
+
because a CCDS failure at build time cached a **blank page**; safer now that reads throw on 5xx at
|
|
83
|
+
runtime (a failed revalidation keeps the last good cache), but full prerender is sensitive to
|
|
84
|
+
null/bad records — keep the page fully null-safe and validate per repo before adopting.
|
|
85
|
+
3. **`cacheComponents: true` + `use cache` (strategic).** Wrap the POST read in `use cache` so its data
|
|
86
|
+
lands in the static shell (PPR). Next-recommended long term; larger migration — do **not** mix ad
|
|
87
|
+
hoc with route-segment `export const revalidate`.
|
|
58
88
|
|
|
59
89
|
---
|
|
60
90
|
|
|
@@ -76,7 +106,7 @@ export async function fetchData<T>({
|
|
|
76
106
|
method = "GET",
|
|
77
107
|
body = null,
|
|
78
108
|
revalidateTag,
|
|
79
|
-
revalidate =
|
|
109
|
+
revalidate = 2592000, // 30d time-based fallback; pair with tags for surgical invalidation
|
|
80
110
|
mutation = false,
|
|
81
111
|
}: FetchOptions): Promise<T | null> {
|
|
82
112
|
const isMutation = mutation || method === "PUT" || method === "DELETE";
|
|
@@ -116,18 +146,31 @@ await fetch(WP_API_URL, {
|
|
|
116
146
|
|
|
117
147
|
## ISR revalidate tiers
|
|
118
148
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
|
123
|
-
|
|
124
|
-
|
|
|
149
|
+
CCDS/WP data changes rarely and is refreshed on-demand via webhooks + tags, so the time-based default
|
|
150
|
+
is intentionally **long** (WEB-1069):
|
|
151
|
+
|
|
152
|
+
| Route / config | Value |
|
|
153
|
+
|----------------|-------|
|
|
154
|
+
| CCDS reads (client default) | `2592000` (30d) |
|
|
155
|
+
| WordPress GraphQL reads (`WP_CACHE_DURATIONS`) | `2592000` (30d) |
|
|
156
|
+
| Listing / detail / city / community / state / landing / WP catch-all | `2592000` (30d) |
|
|
157
|
+
| `next.config` `expireTime` (CDN stale window) | `5184000` (60d) → emits `s-maxage=2592000, stale-while-revalidate=2592000` |
|
|
158
|
+
| `next.config` image `minimumCacheTTL` | `31536000` (1y) — optimized images are content-hashed, never change |
|
|
125
159
|
|
|
126
160
|
- Every cacheable route exports `revalidate`. Do **not** add it to form/personalized routes.
|
|
127
|
-
-
|
|
161
|
+
- **`expireTime` sets the CDN stale window.** Next emits `s-maxage=<revalidate>,
|
|
162
|
+
stale-while-revalidate=<expireTime − revalidate>` for ISR pages, so `expireTime` **must be ≥ the
|
|
163
|
+
largest `revalidate`** or the stale window is invalid (the WEB-1069 bug: `expireTime: 86400` under a
|
|
164
|
+
30d revalidate). Set it to `5184000` (60d) so a 30d page yields the same header the proxy sets on
|
|
165
|
+
dynamic pages: `s-maxage=2592000, stale-while-revalidate=2592000`.
|
|
166
|
+
- The long default is safe because a missed webhook still self-heals within 30d; use `tags` for
|
|
167
|
+
immediate surgical invalidation. Shorter tiers (24h/7d) are fine per-route if a source is volatile.
|
|
168
|
+
- Default to `export const revalidate` over `dynamic = "force-static"`: a failed ISR revalidation then
|
|
128
169
|
preserves the last good cache instead of overwriting it with a broken page. Pair with a client that
|
|
129
170
|
**throws on 5xx at runtime** (so ISR keeps the previous version) but returns an error during the
|
|
130
|
-
build phase (so `generateStaticParams` can skip a bad entry without failing the build).
|
|
171
|
+
build phase (so `generateStaticParams` can skip a bad entry without failing the build). `force-static`
|
|
172
|
+
is still a valid POST-read option (see "Making a POST-read page cacheable") **once** the page is
|
|
173
|
+
fully null-safe and the throw-on-5xx guard is in place.
|
|
131
174
|
|
|
132
175
|
---
|
|
133
176
|
|
|
@@ -146,13 +189,16 @@ if (invalidateCDN) await invalidateCloudFrontPaths([path]);
|
|
|
146
189
|
|
|
147
190
|
## Anti-patterns
|
|
148
191
|
|
|
149
|
-
- ❌ `next: method === "GET" ? {...} : { revalidate: 0 }` — leaves POST reads uncached
|
|
150
|
-
|
|
192
|
+
- ❌ `next: method === "GET" ? {...} : { revalidate: 0 }` — leaves POST reads uncached (origin hit
|
|
193
|
+
every render). Note: even a *correctly* cached POST read still renders the route dynamically — that
|
|
194
|
+
needs an edge override, not just `next` options (see "Making a POST-read page cacheable").
|
|
151
195
|
- ❌ Bare `fetch(url, { method: "POST", body })` for a read — refetched from origin every render.
|
|
152
196
|
- ❌ `next: { tags }` with no `revalidate` — holds stale data or doesn't cache at runtime.
|
|
153
197
|
- ❌ Caching a mutation (`submit`, lead, `PUT`/`DELETE`/`PATCH`).
|
|
154
198
|
- ❌ Treating `React.cache()` as cross-request caching (it's request-scoped dedup only).
|
|
155
|
-
- ❌ `dynamic = "force-static"` on a page
|
|
199
|
+
- ❌ `dynamic = "force-static"` on a page that is **not** fully null-safe or lacks throw-on-5xx — a bad
|
|
200
|
+
CCDS record/response at build time caches a broken/blank page (the WEB-1058 regression). It is a
|
|
201
|
+
valid option only once those guards exist.
|
|
156
202
|
- ❌ Mixing `cacheComponents: true` (`"use cache"`) with route-segment `export const revalidate` —
|
|
157
203
|
that is a separate, deliberate migration; do not introduce it ad hoc.
|
|
158
204
|
|