@forumone/throughline-workflows 0.4.0 → 0.5.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # @forumone/throughline-workflows
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ab623e1: The `payload` peer range moves from `^3.0.0` to `^3.89.0` for every package that has one. **A site on Payload older than 3.89.0 must upgrade Payload before upgrading these packages.**
8
+
9
+ Before 3.89.0, the `payload-mcp-api-keys` collection that `@payloadcms/plugin-mcp` adds registered Payload's API-key strategy on every REST route. Any key could then become `req.user` outside `/api/mcp` and pass access rules written as `Boolean(req.user)`. Every Throughline site runs that plugin, so the floor is the same for every package. No package's code changes with this bump.
10
+
11
+ - 653817e: Revalidation no longer guesses paths, and covers the changes a publish event never announces.
12
+
13
+ - **workflows (breaking)**: `createRevalidateOnPublishFunction` has no built-in URL
14
+ builders. They mapped `pages` to `/<slug>`, `posts` to `/blog/<slug>` and any other
15
+ collection to `/<slug>`, so a site whose routes differed revalidated the wrong path
16
+ without a word. `urlBuilders` is now required; a collection with no entry has its
17
+ tags dropped and no path revalidated, and the run logs a warning. To keep the old
18
+ behaviour, pass the old builders:
19
+
20
+ ```ts
21
+ urlBuilders: {
22
+ pages: (slug) => (slug === 'home' || slug === '' ? '/' : `/${slug}`),
23
+ posts: (slug) => `/blog/${slug}`,
24
+ }
25
+ ```
26
+
27
+ and then check them against your routes.
28
+
29
+ - **workflows**: `createTagRevalidationHooks` — collection `afterChange` and
30
+ `afterDelete`, and global `afterChange`, hooks that call
31
+ `revalidateTag(tag, { expire: 0 })`. Draft saves and autosave drop nothing (via
32
+ publishing's `isDraftWrite`); an unpublish still does. A hook never throws: outside a
33
+ Next request (seeds, migrations, the CLI) it logs at `debug`, and any other failure
34
+ at `error` with the tag and the cause.
35
+ - **workflows**: `createCacheTags` builds every tag string, so the hooks, the publish
36
+ workflow (new `cacheTags` option) and cached reads cannot name different tags.
37
+ Defaults: the bare slug for a collection, `global_<slug>` for a global. Also on the
38
+ dependency-free `@forumone/throughline-workflows/cache-tags` subpath for frontend code.
39
+ `@forumone/throughline-publishing` is now a dependency of this package.
40
+ - **create-throughline**: the scaffold adds `apps/web/src/lib/cache-tags.ts`, attaches
41
+ the tag hooks to `Pages`, and passes explicit `urlBuilders` and the shared
42
+ `cacheTags` to the publish workflow.
43
+
44
+ - 70385c4: `onTerminalFailure` has a handler to pass it. `createTerminalFailureHandler({ payload })` logs a run that exhausted its retries, writes a `job-failures` row through core's `jobFailuresPlugin` when it is registered, and posts the report to `ERROR_WEBHOOK_URL`. `createHealthcheckFailureHandler({ payload })` does the same for the healthcheck's per-run `onFailure`. Neither throws, because a throwing `onFailure` is retried by Inngest. The terminal handler also works as the `onFailure` of any Inngest function.
45
+
46
+ Recording rows needs core's `jobFailuresPlugin` and its migration. Without the plugin, the handlers still log and report.
47
+
48
+ ### Patch Changes
49
+
50
+ - Updated dependencies [006ae30]
51
+ - Updated dependencies [549d292]
52
+ - Updated dependencies [70385c4]
53
+ - Updated dependencies [c8a86bf]
54
+ - Updated dependencies [ab623e1]
55
+ - Updated dependencies [ab623e1]
56
+ - Updated dependencies [36728c4]
57
+ - @forumone/throughline-core@0.10.0
58
+ - @forumone/throughline-publishing@0.11.0
59
+
3
60
  ## 0.4.0
4
61
 
5
62
  ### Minor Changes
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @forumone/throughline-workflows
2
2
 
3
- Composable Inngest function factories for the Throughline framework. Client apps import the factories they need and merge the functions into their Inngest endpoint.
3
+ Composable Inngest function factories for the Throughline framework, and the Payload hooks that keep a Next cache honest. Client apps import the factories they need and merge the functions into their Inngest endpoint.
4
4
 
5
- This package has **no Payload plugin** — it exports factories only. Workflows subscribe to events the server packages emit; they don't modify Payload configuration.
5
+ This package has **no Payload plugin** — it exports factories only. Workflows subscribe to events the server packages emit; they don't modify Payload configuration. The cache-revalidation hooks are factories too: you attach them to the collections and globals you choose.
6
6
 
7
7
  ## What this package provides
8
8
 
@@ -14,6 +14,18 @@ This package has **no Payload plugin** — it exports factories only. Workflows
14
14
  | `createExpireStaleApprovalsFunction` | Daily cron that flips pending approvals past `expiresAt` to `expired` and fires an `approval/expired` event |
15
15
  | `createAuditEventEchoFunction` | Fan-out point: turns `audit/event.recorded` into `notification/send-approval-*` events plus custom handlers |
16
16
  | `createHealthcheckFunction` | Periodic health monitoring with configurable checks |
17
+ | `createTagRevalidationHooks` | Payload `afterChange` / `afterDelete` hooks that drop Next cache tags when a collection or global changes |
18
+ | `createCacheTags` | The one tag scheme hooks, workflow and readers all build tags from (also on `@forumone/throughline-workflows/cache-tags`) |
19
+
20
+ Plus two failure handlers, for the `onTerminalFailure` every factory accepts and the healthcheck's own `onFailure`:
21
+
22
+ - `createTerminalFailureHandler({ payload })` — logs a run that exhausted its retries, writes a `job-failures` row (when core's `jobFailuresPlugin` is registered) and posts it to `ERROR_WEBHOOK_URL`. Never throws. Works as any Inngest function's `onFailure`.
23
+ - `createHealthcheckFailureHandler({ payload })` — the same, once per healthcheck run with failing checks.
24
+
25
+ ```ts
26
+ const onTerminalFailure = createTerminalFailureHandler({ payload })
27
+ createExpireStaleApprovalsFunction({ inngest, payload, onTerminalFailure })
28
+ ```
17
29
 
18
30
  Plus two reusable check helpers used with the healthcheck factory:
19
31
 
@@ -26,7 +38,7 @@ Plus two reusable check helpers used with the healthcheck factory:
26
38
  pnpm add @forumone/throughline-workflows
27
39
  ```
28
40
 
29
- Peers: `payload@^3.0.0`, `inngest@^4.0.0`. `next` is an **optional** peer — install it only if you use `createRevalidateOnPublishFunction` with the default revalidator.
41
+ Peers: `payload@^3.89.0`, `inngest@^4.0.0`. `next` is an **optional** peer — install it only if you use `createRevalidateOnPublishFunction` or `createTagRevalidationHooks` with their default revalidators.
30
42
 
31
43
  ## Usage
32
44
 
@@ -52,7 +64,16 @@ const payload = await getPayload({ config })
52
64
  export const { GET, POST, PUT } = serve({
53
65
  client: inngest,
54
66
  functions: [
55
- createRevalidateOnPublishFunction({ inngest, payload }),
67
+ createRevalidateOnPublishFunction({
68
+ inngest,
69
+ payload,
70
+ // Required. Where each publishable collection is served.
71
+ urlBuilders: {
72
+ pages: (slug) => (slug === 'home' ? '/' : `/${slug}`),
73
+ posts: (slug) => `/news/${slug}`,
74
+ },
75
+ cacheTags, // the same scheme your readers use — see below
76
+ }),
56
77
  createExecuteScheduledPublishesFunction({
57
78
  inngest,
58
79
  payload,
@@ -77,14 +98,116 @@ By C10, every server package fires Inngest events when consequential things happ
77
98
 
78
99
  The factories-only shape (no Payload plugin) is the same reasoning. A workflow that wants to revalidate Next.js pages doesn't need to register a Payload collection. A scheduled-publish executor doesn't need a Payload hook. They're cron / event handlers that happen to read from Payload.
79
100
 
101
+ The tag-revalidation hooks are the one place this package reaches into Payload configuration, and only because you put them there: they are plain hook functions, not a plugin. They live here rather than in `publishing` because this package already owns Next cache invalidation — the optional `next` peer, the Next 16 `revalidateTag` profile, and the tag `createRevalidateOnPublishFunction` fires — and one tag scheme has to serve both. They use publishing's `isDraftWrite`, which makes `publishing` a dependency of this package. Publishing depends on nothing here, so there is no cycle.
102
+
103
+ ## Cache tags: one scheme, both ends
104
+
105
+ A cache tag has two ends: a reader that caches under it and a writer that drops it. Nothing in the type system connects them. Name them differently and everything compiles, every test passes, the hook runs, and the page never refreshes. So build both from one object:
106
+
107
+ ```typescript
108
+ // src/lib/cache-tags.ts — imported by payload.config.ts, the Inngest route and readers
109
+ import { createCacheTags } from '@forumone/throughline-workflows/cache-tags'
110
+
111
+ export const cacheTags = createCacheTags()
112
+ // or name them your way:
113
+ // createCacheTags({ collection: (slug) => `c:${slug}`, global: (slug) => `g:${slug}` })
114
+ ```
115
+
116
+ The defaults are the bare slug for a collection (`pages`, which is also what `createRevalidateOnPublishFunction` has always fired) and `global_<slug>` for a global. The `/cache-tags` subpath imports nothing, so frontend code can use it without pulling in Payload.
117
+
118
+ A reader tags its cached read from the same object:
119
+
120
+ ```typescript
121
+ import { unstable_cache } from 'next/cache'
122
+ import { cacheTags } from '@/lib/cache-tags'
123
+
124
+ export const getNavigation = unstable_cache(
125
+ async () => (await getPayload({ config })).findGlobal({ slug: 'navigation' }),
126
+ ['navigation'],
127
+ { tags: [cacheTags.global('navigation')] },
128
+ )
129
+ // With Cache Components: cacheTag(cacheTags.global('navigation')) inside a 'use cache' function.
130
+ ```
131
+
132
+ ## Tag-revalidation hooks
133
+
134
+ `createRevalidateOnPublishFunction` reacts to publishing events, so it covers publishes, unpublishes and rollbacks of publishable collections — and nothing else. A global (navigation, footer, settings) is read on every page and usually cached, and nothing publishes it. A collection an integration writes emits no publishing event. A delete is not a publish. Each of those leaves the cached copy standing until the next deploy unless a hook drops it:
135
+
136
+ ```typescript
137
+ import { createTagRevalidationHooks } from '@forumone/throughline-workflows'
138
+ import { cacheTags } from './lib/cache-tags'
139
+
140
+ const revalidation = createTagRevalidationHooks({ cacheTags })
141
+
142
+ const Pages: CollectionConfig = {
143
+ slug: 'pages',
144
+ hooks: {
145
+ // Navigation links to pages and caches their slugs, so a page change drops it too.
146
+ afterChange: [
147
+ revalidation.afterCollectionChange({
148
+ tags: (t, slug) => [t.collection(slug), t.global('navigation')],
149
+ }),
150
+ ],
151
+ afterDelete: [revalidation.afterCollectionDelete()],
152
+ },
153
+ // ...
154
+ }
155
+
156
+ const Media: CollectionConfig = {
157
+ slug: 'media',
158
+ upload: true,
159
+ hooks: {
160
+ // An upload cannot be referenced by anything cached yet.
161
+ afterChange: [revalidation.afterCollectionChange({ operations: ['update'] })],
162
+ afterDelete: [revalidation.afterCollectionDelete()],
163
+ },
164
+ // ...
165
+ }
166
+
167
+ const Navigation: GlobalConfig = {
168
+ slug: 'navigation',
169
+ hooks: { afterChange: [revalidation.afterGlobalChange()] },
170
+ // ...
171
+ }
172
+ ```
173
+
174
+ What they do:
175
+
176
+ - **Drop tags, not paths.** Each hook calls `revalidateTag(tag, { expire: 0 })`, the immediate expiry Next 16 requires a profile for. Paths are the workflow's job. A collection hook defaults to `cacheTags.collection(slug)`, a global hook to `cacheTags.global(slug)`; `tags` replaces that list.
177
+ - **Skip draft writes.** On `update`, a collection hook asks publishing's `isDraftWrite`, so a draft save or an autosave tick drops nothing, while an unpublish still does. That answer is recorded by `publishingPlugin` for the collections it manages; on any other collection every write counts as visible, which is the safe direction. A `create` that lands as a draft is skipped too: nothing published existed a moment ago. Global hooks drop on every save.
178
+ - **Never throw.** A throwing `afterChange` fails the editor's save over a cache miss. Outside a Next request — seeds, migrations, the Payload CLI under `tsx` — Next throws `static generation store missing`; there is no server to be stale, so that is logged at `debug`. Any other failure means the cache did not clear while a server was running, and is logged at `error` with the cause and the tag.
179
+ - **Other frontends.** Pass `revalidateTag: (tag) => myCdn.purgeTag(tag)` to drop tags somewhere other than Next.
180
+
181
+ These overlap with `createRevalidateOnPublishFunction` on a publish — both drop the collection tag — which is harmless: dropping a tag twice is dropping it once. Use the workflow for page paths and the sitemap, and the hooks for everything a publish event never announces.
182
+
183
+ ## Migrating from the built-in URL builders
184
+
185
+ Before 0.5, `createRevalidateOnPublishFunction` guessed paths: `pages` → `/<slug>` (`home` → `/`), `posts` → `/blog/<slug>`, and any other collection → `/<slug>`. A site whose routes differed revalidated the wrong path without a word. `urlBuilders` is now required and has no built-in entries. A collection with no entry still has its tags dropped, but no path is revalidated and the run logs a warning.
186
+
187
+ If you relied on the defaults, pass them explicitly — the same behaviour as before:
188
+
189
+ ```typescript
190
+ createRevalidateOnPublishFunction({
191
+ inngest,
192
+ payload,
193
+ urlBuilders: {
194
+ pages: (slug) => (slug === 'home' || slug === '' ? '/' : `/${slug}`),
195
+ posts: (slug) => `/blog/${slug}`,
196
+ },
197
+ })
198
+ ```
199
+
200
+ Then check them against your routes: if your posts are not under `/blog`, they never were being revalidated.
201
+
80
202
  ## Non-Next.js frontends
81
203
 
82
- The default `revalidate` function dynamically imports `next/cache` and is a no-op anywhere `revalidatePath` / `revalidateTag` aren't available. For other frameworks supply your own:
204
+ The default `revalidate` function dynamically imports `next/cache`, so the package installs and imports without Next. For other frameworks supply your own:
83
205
 
84
206
  ```typescript
85
207
  createRevalidateOnPublishFunction({
86
208
  inngest,
87
209
  payload,
210
+ urlBuilders,
88
211
  revalidate: async ({ path, tags }) => {
89
212
  if (path) await myCdn.purge(path)
90
213
  for (const tag of tags) await myCdn.purgeTag(tag)
@@ -192,9 +315,10 @@ Each handler runs in its own `step.run`, so failures isolate.
192
315
 
193
316
  ## Options reference
194
317
 
195
- Every factory takes a typed options object. See `src/types.ts` for the full surface — defaults documented there:
318
+ Every factory takes a typed options object. See `src/types.ts` for the full surface — defaults documented there. All of them also accept `onTerminalFailure?` and `concurrency?`:
196
319
 
197
- - `RevalidateOnPublishOptions` — `revalidate?`, `urlBuilders?`, `collectionTags?`, `id?`
320
+ - `RevalidateOnPublishOptions` — `urlBuilders` (required), `cacheTags?`, `collectionTags?`, `revalidate?`, `id?`
321
+ - `TagRevalidationOptions` — `cacheTags?`, `revalidateTag?`; per hook, `tags?` and (collection `afterChange`) `operations?`
198
322
  - `PublishAtScheduledTimeOptions` — `collections[]`, `publish`, `maxSleepMs?` (default six days), `id?`
199
323
  - `ExecuteScheduledPublishesOptions` — `collections[]`, `publish`, `schedule?` (default `*/5 * * * *`), `overdueByMs?` (default 0), `id?`
200
324
  - `ExpireStaleApprovalsOptions` — `collectionSlug?` (default `approvals`), `schedule?` (default `0 2 * * *`), `id?`
@@ -204,6 +328,6 @@ Every factory takes a typed options object. See `src/types.ts` for the full surf
204
328
  ## Related packages
205
329
 
206
330
  - `@forumone/throughline-core` — required peer; provides the audit writer the approval-expiration cron uses
207
- - `@forumone/throughline-publishing` — emits the publishing events the revalidation cron subscribes to
331
+ - `@forumone/throughline-publishing` — emits the publishing events the revalidation workflow subscribes to, and provides the `isDraftWrite` predicate the tag hooks use (a dependency)
208
332
  - `@forumone/throughline-approvals` — owns the approvals collection the expiration cron reads
209
333
  - `@forumone/throughline-email` (C11) — will subscribe to `notification/send-approval-*`
@@ -0,0 +1,31 @@
1
+ /** How a site names its tags. Each builder is optional; omitted ones use the default. */
2
+ export interface CacheTagScheme {
3
+ /**
4
+ * The tag for cached reads derived from a collection's documents.
5
+ * Default: the bare slug (`pages`), which is also what
6
+ * `createRevalidateOnPublishFunction` has always fired.
7
+ */
8
+ collection?: (slug: string) => string;
9
+ /** The tag for a cached global. Default: `global_<slug>`. */
10
+ global?: (slug: string) => string;
11
+ }
12
+ /** The built scheme. Pass the same instance to writers and readers. */
13
+ export interface CacheTags {
14
+ collection(slug: string): string;
15
+ global(slug: string): string;
16
+ }
17
+ /**
18
+ * Builds the tag scheme both ends of the cache use.
19
+ *
20
+ * ```ts
21
+ * // src/lib/cache-tags.ts — imported by payload.config.ts and by readers
22
+ * export const cacheTags = createCacheTags()
23
+ *
24
+ * // a reader
25
+ * unstable_cache(load, ['navigation'], { tags: [cacheTags.global('navigation')] })
26
+ * ```
27
+ */
28
+ export declare function createCacheTags(scheme?: CacheTagScheme): CacheTags;
29
+ /** The scheme every writer uses when it is given none. */
30
+ export declare const defaultCacheTags: CacheTags;
31
+ //# sourceMappingURL=cache-tags.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-tags.d.ts","sourceRoot":"","sources":["../src/cache-tags.ts"],"names":[],"mappings":"AAqBA,yFAAyF;AACzF,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACrC,6DAA6D;IAC7D,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;CAClC;AAED,uEAAuE;AACvE,MAAM,WAAW,SAAS;IACxB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;IAChC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;CAC7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,MAAM,GAAE,cAAmB,GAAG,SAAS,CAOtE;AAED,0DAA0D;AAC1D,eAAO,MAAM,gBAAgB,EAAE,SAA6B,CAAA"}
@@ -0,0 +1,53 @@
1
+ /*
2
+ Next cache tags, named in one place.
3
+
4
+ A tag is one string with two ends: the reader that caches under it
5
+ (`unstable_cache(..., { tags })`, `cacheTag()`, `fetch(..., { next: { tags } })`)
6
+ and the writer that drops it (the hooks in `revalidate-tag-hooks.ts`, and
7
+ `createRevalidateOnPublishFunction`). Nothing in the type system connects the two
8
+ ends. Name them differently and everything compiles, every test passes, the hook
9
+ runs, and the page never refreshes — the first site on this suite shipped a hook
10
+ that had never invalidated anything for exactly that reason.
11
+
12
+ So both ends build tags from one object. A site makes it once, in a module its
13
+ `payload.config.ts` and its frontend both import, and hands it to the hooks and
14
+ the readers alike.
15
+
16
+ Deliberately dependency-free, and published on its own subpath
17
+ (`@forumone/throughline-workflows/cache-tags`): one of its importers is
18
+ `payload.config.ts` and the other is frontend code, and neither should pull in
19
+ the other's world to name a string.
20
+ */
21
+ /**
22
+ * Builds the tag scheme both ends of the cache use.
23
+ *
24
+ * ```ts
25
+ * // src/lib/cache-tags.ts — imported by payload.config.ts and by readers
26
+ * export const cacheTags = createCacheTags()
27
+ *
28
+ * // a reader
29
+ * unstable_cache(load, ['navigation'], { tags: [cacheTags.global('navigation')] })
30
+ * ```
31
+ */
32
+ export function createCacheTags(scheme = {}) {
33
+ const collection = scheme.collection ?? ((slug) => slug);
34
+ const global = scheme.global ?? ((slug) => `global_${slug}`);
35
+ return {
36
+ collection: (slug) => nonEmpty(collection(slug), 'collection', slug),
37
+ global: (slug) => nonEmpty(global(slug), 'global', slug),
38
+ };
39
+ }
40
+ /** The scheme every writer uses when it is given none. */
41
+ export const defaultCacheTags = createCacheTags();
42
+ /*
43
+ An empty tag is the silent version of the mismatch above: `revalidateTag('')`
44
+ does nothing and says nothing. A builder that produces one is a bug in the
45
+ site's configuration, and the place to hear about it is the first call.
46
+ */
47
+ function nonEmpty(tag, kind, slug) {
48
+ if (typeof tag !== 'string' || tag.length === 0) {
49
+ throw new Error(`The cache tag scheme built an empty ${kind} tag for "${slug}".`);
50
+ }
51
+ return tag;
52
+ }
53
+ //# sourceMappingURL=cache-tags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-tags.js","sourceRoot":"","sources":["../src/cache-tags.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;EAmBE;AAoBF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,SAAyB,EAAE;IACzD,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,CAAA;IAChE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,UAAU,IAAI,EAAE,CAAC,CAAA;IACpE,OAAO;QACL,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC;QACpE,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC;KACzD,CAAA;AACH,CAAC;AAED,0DAA0D;AAC1D,MAAM,CAAC,MAAM,gBAAgB,GAAc,eAAe,EAAE,CAAA;AAE5D;;;;EAIE;AACF,SAAS,QAAQ,CAAC,GAAW,EAAE,IAAY,EAAE,IAAY;IACvD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,uCAAuC,IAAI,aAAa,IAAI,IAAI,CAAC,CAAA;IACnF,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
@@ -0,0 +1,38 @@
1
+ import type { Payload } from 'payload';
2
+ import type { Logger } from '@forumone/throughline-core';
3
+ import { type ErrorReporter, type JobFailureWriter } from '@forumone/throughline-core';
4
+ import type { HealthcheckOptions, WorkflowFailureHandler } from './types.js';
5
+ export interface FailureHandlerOptions {
6
+ /**
7
+ * The Payload instance whose `jobFailuresPlugin` writer receives the row.
8
+ * Without one — or without the plugin — the failure is logged and reported
9
+ * but not recorded.
10
+ */
11
+ payload?: Payload | undefined;
12
+ /** Replaces the writer found on `payload`. */
13
+ writer?: JobFailureWriter | undefined;
14
+ /** Default: `reportError` from core, which posts to `ERROR_WEBHOOK_URL`. Pass `false` to not report. */
15
+ report?: ErrorReporter | false | undefined;
16
+ /** Default: core's console logger. */
17
+ logger?: Logger | undefined;
18
+ }
19
+ export interface HealthcheckFailureHandlerOptions extends FailureHandlerOptions {
20
+ /** The healthcheck function's id, recorded as the failure's source. Default: `'healthcheck'`. */
21
+ functionId?: string | undefined;
22
+ }
23
+ /**
24
+ * An `onTerminalFailure` handler for any workflow factory here — or for any
25
+ * Inngest function, as its `onFailure`.
26
+ *
27
+ * ```ts
28
+ * const onTerminalFailure = createTerminalFailureHandler({ payload })
29
+ * createExpireStaleApprovalsFunction({ inngest, payload, onTerminalFailure })
30
+ * ```
31
+ */
32
+ export declare function createTerminalFailureHandler(options?: FailureHandlerOptions): WorkflowFailureHandler;
33
+ /**
34
+ * A `HealthcheckOptions.onFailure` handler: once per run with failing checks,
35
+ * to the same three places.
36
+ */
37
+ export declare function createHealthcheckFailureHandler(options?: HealthcheckFailureHandlerOptions): NonNullable<HealthcheckOptions['onFailure']>;
38
+ //# sourceMappingURL=failure-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failure-handler.d.ts","sourceRoot":"","sources":["../src/failure-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AACxD,OAAO,EAOL,KAAK,aAAa,EAGlB,KAAK,gBAAgB,EACtB,MAAM,4BAA4B,CAAA;AACnC,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAuB5E,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,8CAA8C;IAC9C,MAAM,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAA;IACrC,wGAAwG;IACxG,MAAM,CAAC,EAAE,aAAa,GAAG,KAAK,GAAG,SAAS,CAAA;IAC1C,sCAAsC;IACtC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC5B;AAED,MAAM,WAAW,gCAAiC,SAAQ,qBAAqB;IAC7E,iGAAiG;IACjG,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAChC;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,GAAE,qBAA0B,GAClC,sBAAsB,CAgBxB;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,gCAAqC,GAC7C,WAAW,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC,CAa9C"}
@@ -0,0 +1,60 @@
1
+ import { buildHealthcheckFailureReport, buildJobFailureReport, defaultLogger, getJobFailureWriter, reportError, summariseReport, } from '@forumone/throughline-core';
2
+ /**
3
+ * An `onTerminalFailure` handler for any workflow factory here — or for any
4
+ * Inngest function, as its `onFailure`.
5
+ *
6
+ * ```ts
7
+ * const onTerminalFailure = createTerminalFailureHandler({ payload })
8
+ * createExpireStaleApprovalsFunction({ inngest, payload, onTerminalFailure })
9
+ * ```
10
+ */
11
+ export function createTerminalFailureHandler(options = {}) {
12
+ return async ({ error, event }) => {
13
+ let report;
14
+ try {
15
+ const data = event?.data;
16
+ report = buildJobFailureReport({
17
+ functionId: data?.function_id,
18
+ runId: data?.run_id,
19
+ triggerEvent: data?.event?.name,
20
+ error,
21
+ });
22
+ }
23
+ catch {
24
+ return;
25
+ }
26
+ await dispatch(report, '[job-failed]', options);
27
+ };
28
+ }
29
+ /**
30
+ * A `HealthcheckOptions.onFailure` handler: once per run with failing checks,
31
+ * to the same three places.
32
+ */
33
+ export function createHealthcheckFailureHandler(options = {}) {
34
+ return async (failures) => {
35
+ let report;
36
+ try {
37
+ report = buildHealthcheckFailureReport(failures, options.functionId ? { functionId: options.functionId } : {});
38
+ }
39
+ catch {
40
+ return;
41
+ }
42
+ await dispatch(report, '[healthcheck-failed]', options);
43
+ };
44
+ }
45
+ async function dispatch(report, tag, options) {
46
+ const logger = options.logger ?? defaultLogger;
47
+ try {
48
+ // The stack goes to the log and the webhook; the row keeps the message.
49
+ logger.error(`${tag} ${summariseReport(report)}`, { report });
50
+ }
51
+ catch {
52
+ // Carry on to the other two.
53
+ }
54
+ const writer = options.writer ?? (options.payload ? getJobFailureWriter(options.payload) : undefined);
55
+ const reporter = options.report === false ? undefined : (options.report ?? reportError);
56
+ // Core's writer and reporter never reject. These wrappers are for a host's
57
+ // own, which may not keep that promise — or may throw before returning one.
58
+ await Promise.allSettled([(async () => writer?.(report))(), (async () => reporter?.(report))()]);
59
+ }
60
+ //# sourceMappingURL=failure-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failure-handler.js","sourceRoot":"","sources":["../src/failure-handler.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,6BAA6B,EAC7B,qBAAqB,EACrB,aAAa,EACb,mBAAmB,EACnB,WAAW,EACX,eAAe,GAKhB,MAAM,4BAA4B,CAAA;AA4CnC;;;;;;;;GAQG;AACH,MAAM,UAAU,4BAA4B,CAC1C,UAAiC,EAAE;IAEnC,OAAO,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;QAChC,IAAI,MAAwB,CAAA;QAC5B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,KAAK,EAAE,IAAI,CAAA;YACxB,MAAM,GAAG,qBAAqB,CAAC;gBAC7B,UAAU,EAAE,IAAI,EAAE,WAAW;gBAC7B,KAAK,EAAE,IAAI,EAAE,MAAM;gBACnB,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI;gBAC/B,KAAK;aACN,CAAC,CAAA;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,OAAM;QACR,CAAC;QACD,MAAM,QAAQ,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,CAAC,CAAA;IACjD,CAAC,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,+BAA+B,CAC7C,UAA4C,EAAE;IAE9C,OAAO,KAAK,EAAE,QAAQ,EAAE,EAAE;QACxB,IAAI,MAAgC,CAAA;QACpC,IAAI,CAAC;YACH,MAAM,GAAG,6BAA6B,CACpC,QAAQ,EACR,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAC7D,CAAA;QACH,CAAC;QAAC,MAAM,CAAC;YACP,OAAM;QACR,CAAC;QACD,MAAM,QAAQ,CAAC,MAAM,EAAE,sBAAsB,EAAE,OAAO,CAAC,CAAA;IACzD,CAAC,CAAA;AACH,CAAC;AAED,KAAK,UAAU,QAAQ,CACrB,MAAmD,EACnD,GAAW,EACX,OAA8B;IAE9B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,aAAa,CAAA;IAC9C,IAAI,CAAC;QACH,wEAAwE;QACxE,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,6BAA6B;IAC/B,CAAC;IAED,MAAM,MAAM,GACV,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;IACxF,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,IAAI,WAAW,CAAC,CAAA;IAEvF,2EAA2E;IAC3E,4EAA4E;IAC5E,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AAClG,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,9 +1,15 @@
1
1
  export { createRevalidateOnPublishFunction } from './revalidate-on-publish.js';
2
+ export { createTagRevalidationHooks } from './revalidate-tag-hooks.js';
3
+ export type { CollectionTagHookOptions, GlobalTagHookOptions, RevalidateTagFn, TagRevalidationHooks, TagRevalidationOptions, TagSelector, } from './revalidate-tag-hooks.js';
4
+ export { createCacheTags, defaultCacheTags } from './cache-tags.js';
5
+ export type { CacheTags, CacheTagScheme } from './cache-tags.js';
2
6
  export { createExecuteScheduledPublishesFunction } from './execute-scheduled-publishes.js';
3
7
  export { createPublishAtScheduledTimeFunction } from './publish-at-scheduled-time.js';
4
8
  export { createExpireStaleApprovalsFunction } from './expire-stale-approvals.js';
5
9
  export { createAuditEventEchoFunction } from './audit-event-echo.js';
6
10
  export { createHealthcheckFunction, createPayloadReachableCheck, createManifestReachableCheck, } from './healthcheck.js';
7
11
  export { failureOptions } from './types.js';
12
+ export { createHealthcheckFailureHandler, createTerminalFailureHandler, } from './failure-handler.js';
13
+ export type { FailureHandlerOptions, HealthcheckFailureHandlerOptions, } from './failure-handler.js';
8
14
  export type { BaseWorkflowOptions, WorkflowFailureHandler, FailureAwareOptions, RevalidateFn, RevalidatePathsInput, RevalidateOnPublishOptions, ScheduledCollectionConfig, ExecuteScheduledPublishesOptions, PublishAtScheduledTimeOptions, ScheduledPublishRequest, ScheduledPublishResult, ExpireStaleApprovalsOptions, AuditEchoEvent, AuditEchoHandler, AuditEventEchoOptions, HealthcheckDefinition, HealthcheckOptions, HealthcheckResult, } from './types.js';
9
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iCAAiC,EAAE,MAAM,4BAA4B,CAAA;AAC9E,OAAO,EAAE,uCAAuC,EAAE,MAAM,kCAAkC,CAAA;AAC1F,OAAO,EAAE,oCAAoC,EAAE,MAAM,gCAAgC,CAAA;AACrF,OAAO,EAAE,kCAAkC,EAAE,MAAM,6BAA6B,CAAA;AAChF,OAAO,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE3C,YAAY,EACV,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACnB,YAAY,EACZ,oBAAoB,EACpB,0BAA0B,EAC1B,yBAAyB,EACzB,gCAAgC,EAChC,6BAA6B,EAC7B,uBAAuB,EACvB,sBAAsB,EACtB,2BAA2B,EAC3B,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iCAAiC,EAAE,MAAM,4BAA4B,CAAA;AAC9E,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAA;AACtE,YAAY,EACV,wBAAwB,EACxB,oBAAoB,EACpB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,WAAW,GACZ,MAAM,2BAA2B,CAAA;AAKlC,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AACnE,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChE,OAAO,EAAE,uCAAuC,EAAE,MAAM,kCAAkC,CAAA;AAC1F,OAAO,EAAE,oCAAoC,EAAE,MAAM,gCAAgC,CAAA;AACrF,OAAO,EAAE,kCAAkC,EAAE,MAAM,6BAA6B,CAAA;AAChF,OAAO,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EACL,+BAA+B,EAC/B,4BAA4B,GAC7B,MAAM,sBAAsB,CAAA;AAC7B,YAAY,EACV,qBAAqB,EACrB,gCAAgC,GACjC,MAAM,sBAAsB,CAAA;AAE7B,YAAY,EACV,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACnB,YAAY,EACZ,oBAAoB,EACpB,0BAA0B,EAC1B,yBAAyB,EACzB,gCAAgC,EAChC,6BAA6B,EAC7B,uBAAuB,EACvB,sBAAsB,EACtB,2BAA2B,EAC3B,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -1,9 +1,15 @@
1
- // Other factory exports land in subsequent commits.
2
1
  export { createRevalidateOnPublishFunction } from './revalidate-on-publish.js';
2
+ export { createTagRevalidationHooks } from './revalidate-tag-hooks.js';
3
+ /*
4
+ Also on `@forumone/throughline-workflows/cache-tags`, which imports nothing —
5
+ readers in frontend code should import it from there.
6
+ */
7
+ export { createCacheTags, defaultCacheTags } from './cache-tags.js';
3
8
  export { createExecuteScheduledPublishesFunction } from './execute-scheduled-publishes.js';
4
9
  export { createPublishAtScheduledTimeFunction } from './publish-at-scheduled-time.js';
5
10
  export { createExpireStaleApprovalsFunction } from './expire-stale-approvals.js';
6
11
  export { createAuditEventEchoFunction } from './audit-event-echo.js';
7
12
  export { createHealthcheckFunction, createPayloadReachableCheck, createManifestReachableCheck, } from './healthcheck.js';
8
13
  export { failureOptions } from './types.js';
14
+ export { createHealthcheckFailureHandler, createTerminalFailureHandler, } from './failure-handler.js';
9
15
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oDAAoD;AAEpD,OAAO,EAAE,iCAAiC,EAAE,MAAM,4BAA4B,CAAA;AAC9E,OAAO,EAAE,uCAAuC,EAAE,MAAM,kCAAkC,CAAA;AAC1F,OAAO,EAAE,oCAAoC,EAAE,MAAM,gCAAgC,CAAA;AACrF,OAAO,EAAE,kCAAkC,EAAE,MAAM,6BAA6B,CAAA;AAChF,OAAO,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iCAAiC,EAAE,MAAM,4BAA4B,CAAA;AAC9E,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAA;AAStE;;;EAGE;AACF,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAEnE,OAAO,EAAE,uCAAuC,EAAE,MAAM,kCAAkC,CAAA;AAC1F,OAAO,EAAE,oCAAoC,EAAE,MAAM,gCAAgC,CAAA;AACrF,OAAO,EAAE,kCAAkC,EAAE,MAAM,6BAA6B,CAAA;AAChF,OAAO,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EACL,+BAA+B,EAC/B,4BAA4B,GAC7B,MAAM,sBAAsB,CAAA"}
@@ -0,0 +1,43 @@
1
+ import type * as NextCacheModule from 'next/cache';
2
+ type NextCache = typeof NextCacheModule;
3
+ /**
4
+ * How stale a caller may find a tag after it is dropped: not at all.
5
+ *
6
+ * Next 16 made `revalidateTag`'s second argument required — it is a cache-life
7
+ * profile — and one-argument calls log a deprecation warning on every call.
8
+ * `{ expire: 0 }` is the immediate expiry the one-argument form used to mean,
9
+ * and it is what an editor pressing Publish or Save expects.
10
+ *
11
+ * Passed unconditionally rather than behind a version check. The peer range is
12
+ * `next >= 15`, and Next 15's `revalidateTag` takes one parameter and ignores a
13
+ * second, so the two-argument call is correct on both.
14
+ *
15
+ * `updateTag`, which Next offers as the other way out of the deprecation, is
16
+ * not usable here: it throws outside a Server Action, and these run in an
17
+ * Inngest step or in Payload's route handler.
18
+ */
19
+ export declare const IMMEDIATE: {
20
+ readonly expire: 0;
21
+ };
22
+ /**
23
+ * `next/cache`, loaded on first use.
24
+ *
25
+ * Dynamic so the package can be imported where Next is not installed (test
26
+ * runners, a non-Next frontend that passes its own revalidator). `next` is an
27
+ * optional peer.
28
+ */
29
+ export declare function loadNextCache(): Promise<NextCache>;
30
+ /**
31
+ * Whether `error` is Next saying there is no request to revalidate for.
32
+ *
33
+ * Seeds, migrations and the Payload CLI write through the same hooks under
34
+ * `tsx` rather than inside a Next server. `revalidateTag` then throws
35
+ * `Invariant: static generation store missing in revalidateTag ...`. Nothing is
36
+ * stale in that case — there is no server to be stale — so it is the one
37
+ * failure that is not a fault.
38
+ *
39
+ * Matched on the message, the only stable surface Next gives it.
40
+ */
41
+ export declare function isOutsideNextRequest(error: unknown): boolean;
42
+ export {};
43
+ //# sourceMappingURL=next-revalidate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next-revalidate.d.ts","sourceRoot":"","sources":["../src/next-revalidate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,eAAe,MAAM,YAAY,CAAA;AAElD,KAAK,SAAS,GAAG,OAAO,eAAe,CAAA;AAEvC;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,SAAS;;CAAyB,CAAA;AAE/C;;;;;;GAMG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,SAAS,CAAC,CAExD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE5D"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * How stale a caller may find a tag after it is dropped: not at all.
3
+ *
4
+ * Next 16 made `revalidateTag`'s second argument required — it is a cache-life
5
+ * profile — and one-argument calls log a deprecation warning on every call.
6
+ * `{ expire: 0 }` is the immediate expiry the one-argument form used to mean,
7
+ * and it is what an editor pressing Publish or Save expects.
8
+ *
9
+ * Passed unconditionally rather than behind a version check. The peer range is
10
+ * `next >= 15`, and Next 15's `revalidateTag` takes one parameter and ignores a
11
+ * second, so the two-argument call is correct on both.
12
+ *
13
+ * `updateTag`, which Next offers as the other way out of the deprecation, is
14
+ * not usable here: it throws outside a Server Action, and these run in an
15
+ * Inngest step or in Payload's route handler.
16
+ */
17
+ export const IMMEDIATE = { expire: 0 };
18
+ /**
19
+ * `next/cache`, loaded on first use.
20
+ *
21
+ * Dynamic so the package can be imported where Next is not installed (test
22
+ * runners, a non-Next frontend that passes its own revalidator). `next` is an
23
+ * optional peer.
24
+ */
25
+ export async function loadNextCache() {
26
+ return await import('next/cache');
27
+ }
28
+ /**
29
+ * Whether `error` is Next saying there is no request to revalidate for.
30
+ *
31
+ * Seeds, migrations and the Payload CLI write through the same hooks under
32
+ * `tsx` rather than inside a Next server. `revalidateTag` then throws
33
+ * `Invariant: static generation store missing in revalidateTag ...`. Nothing is
34
+ * stale in that case — there is no server to be stale — so it is the one
35
+ * failure that is not a fault.
36
+ *
37
+ * Matched on the message, the only stable surface Next gives it.
38
+ */
39
+ export function isOutsideNextRequest(error) {
40
+ return error instanceof Error && error.message.includes('static generation store missing');
41
+ }
42
+ //# sourceMappingURL=next-revalidate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next-revalidate.js","sourceRoot":"","sources":["../src/next-revalidate.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,MAAM,EAAE,CAAC,EAAW,CAAA;AAE/C;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,OAAO,MAAM,MAAM,CAAC,YAAY,CAAC,CAAA;AACnC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,OAAO,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,iCAAiC,CAAC,CAAA;AAC5F,CAAC"}
@@ -5,6 +5,16 @@ import type { RevalidateOnPublishOptions } from './types.js';
5
5
  * publishing taxonomy and revalidates Next.js cache entries for the affected
6
6
  * page, the listing routes, and the sitemap.
7
7
  *
8
+ * The page path comes from `options.urlBuilders`, which is required: only the
9
+ * site knows where a collection's documents are served. A collection with no
10
+ * builder gets its tags dropped and no path revalidated, with a warning —
11
+ * there used to be built-in builders (`pages` → `/<slug>`, `posts` →
12
+ * `/blog/<slug>`, anything else → `/<slug>`), and a site whose routes differed
13
+ * revalidated the wrong path without a word.
14
+ *
15
+ * Tags default to `cacheTags.collection(<collection>)`. Pass the same
16
+ * `cacheTags` your readers and your tag hooks use.
17
+ *
8
18
  * The default revalidate function dynamically imports `next/cache`, so the
9
19
  * package is safe to install in non-Next.js contexts. Pass `options.revalidate`
10
20
  * to use a different cache invalidation strategy.
@@ -1 +1 @@
1
- {"version":3,"file":"revalidate-on-publish.d.ts","sourceRoot":"","sources":["../src/revalidate-on-publish.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,KAAK,EAAgB,0BAA0B,EAAwB,MAAM,YAAY,CAAA;AAOhG;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,0BAA0B,GAClC,eAAe,CAAC,GAAG,CA6CrB"}
1
+ {"version":3,"file":"revalidate-on-publish.d.ts","sourceRoot":"","sources":["../src/revalidate-on-publish.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAG9C,OAAO,KAAK,EAAgB,0BAA0B,EAAwB,MAAM,YAAY,CAAA;AAEhG;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,0BAA0B,GAClC,eAAe,CAAC,GAAG,CAqDrB"}
@@ -1,19 +1,28 @@
1
1
  import { failureOptions } from './types.js';
2
- const DEFAULT_URL_BUILDERS = {
3
- pages: (slug) => (slug === 'home' || slug === '' ? '/' : `/${slug}`),
4
- posts: (slug) => `/blog/${slug}`,
5
- };
2
+ import { defaultCacheTags } from './cache-tags.js';
3
+ import { IMMEDIATE, loadNextCache } from './next-revalidate.js';
6
4
  /**
7
5
  * Builds the `revalidate-on-publish` Inngest function. Subscribes to the
8
6
  * publishing taxonomy and revalidates Next.js cache entries for the affected
9
7
  * page, the listing routes, and the sitemap.
10
8
  *
9
+ * The page path comes from `options.urlBuilders`, which is required: only the
10
+ * site knows where a collection's documents are served. A collection with no
11
+ * builder gets its tags dropped and no path revalidated, with a warning —
12
+ * there used to be built-in builders (`pages` → `/<slug>`, `posts` →
13
+ * `/blog/<slug>`, anything else → `/<slug>`), and a site whose routes differed
14
+ * revalidated the wrong path without a word.
15
+ *
16
+ * Tags default to `cacheTags.collection(<collection>)`. Pass the same
17
+ * `cacheTags` your readers and your tag hooks use.
18
+ *
11
19
  * The default revalidate function dynamically imports `next/cache`, so the
12
20
  * package is safe to install in non-Next.js contexts. Pass `options.revalidate`
13
21
  * to use a different cache invalidation strategy.
14
22
  */
15
23
  export function createRevalidateOnPublishFunction(options) {
16
- const urlBuilders = { ...DEFAULT_URL_BUILDERS, ...options.urlBuilders };
24
+ const urlBuilders = options.urlBuilders;
25
+ const cacheTags = options.cacheTags ?? defaultCacheTags;
17
26
  const collectionTags = options.collectionTags ?? {};
18
27
  const revalidate = options.revalidate ?? defaultRevalidate;
19
28
  return options.inngest.createFunction({
@@ -34,9 +43,13 @@ export function createRevalidateOnPublishFunction(options) {
34
43
  const data = (event.data ?? {});
35
44
  const collection = data.collection ?? 'pages';
36
45
  const slug = data.slug ?? data.id ?? '';
37
- const tags = collectionTags[collection] ?? [collection];
46
+ const tags = collectionTags[collection] ?? [cacheTags.collection(collection)];
38
47
  await step.run('revalidate-page-path', async () => {
39
- const builder = urlBuilders[collection] ?? ((s) => `/${s}`);
48
+ const builder = Object.hasOwn(urlBuilders, collection) ? urlBuilders[collection] : undefined;
49
+ if (!builder) {
50
+ logger.warn('No urlBuilders entry for this collection, so no page path was revalidated. Add one to createRevalidateOnPublishFunction.', { collection, slug });
51
+ return;
52
+ }
40
53
  const path = builder(slug);
41
54
  await revalidate({ path, tags });
42
55
  logger.info('Revalidated page path', { path, tags });
@@ -50,23 +63,6 @@ export function createRevalidateOnPublishFunction(options) {
50
63
  return { collection, slug, revalidated: true };
51
64
  });
52
65
  }
53
- /**
54
- * How stale a caller may find a tag after this runs: not at all.
55
- *
56
- * Next 16 made `revalidateTag`'s second argument required — it is a cache-life
57
- * profile — and one-argument calls log a deprecation warning on every publish.
58
- * `{ expire: 0 }` is the immediate expiry the one-argument form used to mean,
59
- * and it is what an editor pressing Publish expects.
60
- *
61
- * Passed unconditionally rather than behind a version check. The peer range is
62
- * `next >= 15`, and Next 15's `revalidateTag` takes one parameter and ignores a
63
- * second, so the two-argument call is correct on both.
64
- *
65
- * `updateTag`, which Next offers as the other way out of the deprecation, is
66
- * not usable here: it throws outside a Server Action, and this runs in an
67
- * Inngest step behind a route handler.
68
- */
69
- const IMMEDIATE = { expire: 0 };
70
66
  /**
71
67
  * Default revalidator: dynamic-imports `next/cache` so the package can be
72
68
  * imported in environments without Next.js (e.g. test runners that don't
@@ -74,7 +70,7 @@ const IMMEDIATE = { expire: 0 };
74
70
  * because `revalidatePath('')` triggers a noisy Next.js warning.
75
71
  */
76
72
  const defaultRevalidate = async ({ path, tags }) => {
77
- const { revalidatePath, revalidateTag } = (await import('next/cache'));
73
+ const { revalidatePath, revalidateTag } = await loadNextCache();
78
74
  if (path)
79
75
  revalidatePath(path);
80
76
  for (const tag of tags)
@@ -1 +1 @@
1
- {"version":3,"file":"revalidate-on-publish.js","sourceRoot":"","sources":["../src/revalidate-on-publish.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"revalidate-on-publish.js","sourceRoot":"","sources":["../src/revalidate-on-publish.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAG/D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,iCAAiC,CAC/C,OAAmC;IAEnC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAA;IACvC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,gBAAgB,CAAA;IACvD,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,EAAE,CAAA;IACnD,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,iBAAiB,CAAA;IAE1D,OAAO,OAAO,CAAC,OAAO,CAAC,cAAc,CACnC;QACE,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,uBAAuB;QACzC,OAAO,EAAE,CAAC;QACV;;;;UAIE;QACF,GAAG,cAAc,CAAC,OAAO,CAAC;QAC1B,QAAQ,EAAE;YACR,EAAE,KAAK,EAAE,wBAAwB,EAAE;YACnC,EAAE,KAAK,EAAE,0BAA0B,EAAE;YACrC,EAAE,KAAK,EAAE,0BAA0B,EAAE;SACtC;KACF,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE;QAChC,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAwD,CAAA;QACtF,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,OAAO,CAAA;QAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,IAAI,EAAE,CAAA;QACvC,MAAM,IAAI,GAAG,cAAc,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAA;QAE7E,MAAM,IAAI,CAAC,GAAG,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE;YAChD,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;YAC5F,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CACT,0HAA0H,EAC1H,EAAE,UAAU,EAAE,IAAI,EAAE,CACrB,CAAA;gBACD,OAAM;YACR,CAAC;YACD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;YAC1B,MAAM,UAAU,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;YAChC,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;QACtD,CAAC,CAAC,CAAA;QAEF,MAAM,IAAI,CAAC,GAAG,CAAC,qBAAqB,EAAE,KAAK,IAAI,EAAE;YAC/C,MAAM,UAAU,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;QACtC,CAAC,CAAC,CAAA;QAEF,MAAM,IAAI,CAAC,GAAG,CAAC,oBAAoB,EAAE,KAAK,IAAI,EAAE;YAC9C,MAAM,UAAU,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAA;QAC/D,CAAC,CAAC,CAAA;QAEF,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAA;IAChD,CAAC,CACF,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,iBAAiB,GAAiB,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAwB,EAAE,EAAE;IACrF,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,GAAG,MAAM,aAAa,EAAE,CAAA;IAC/D,IAAI,IAAI;QAAE,cAAc,CAAC,IAAI,CAAC,CAAA;IAC9B,KAAK,MAAM,GAAG,IAAI,IAAI;QAAE,aAAa,CAAC,GAAG,EAAE,SAAS,CAAC,CAAA;AACvD,CAAC,CAAA"}
@@ -0,0 +1,80 @@
1
+ import type { CollectionAfterChangeHook, CollectionAfterDeleteHook, GlobalAfterChangeHook } from 'payload';
2
+ import { type CacheTags } from './cache-tags.js';
3
+ /** Drops one tag. Return a promise to have it awaited. */
4
+ export type RevalidateTagFn = (tag: string) => void | Promise<void>;
5
+ export interface TagRevalidationOptions {
6
+ /**
7
+ * The tag scheme. Pass the same object your readers build their tags from —
8
+ * that is the whole point of it. Default: `defaultCacheTags`.
9
+ */
10
+ cacheTags?: CacheTags;
11
+ /**
12
+ * How to drop a tag. Default: Next's `revalidateTag(tag, { expire: 0 })`,
13
+ * loaded from `next/cache` on first use. Supply your own for a non-Next
14
+ * frontend or a CDN.
15
+ */
16
+ revalidateTag?: RevalidateTagFn;
17
+ }
18
+ /** Which tags a hook drops, built from the scheme. */
19
+ export type TagSelector = (cacheTags: CacheTags, slug: string) => readonly string[];
20
+ export interface CollectionTagHookOptions {
21
+ /**
22
+ * The tags to drop. Default: the collection's own tag,
23
+ * `cacheTags.collection(slug)`. Use it to drop a tag a *different* reader
24
+ * owns — a navigation global that links to pages caches the pages' slugs:
25
+ *
26
+ * ```ts
27
+ * tags: (t, slug) => [t.collection(slug), t.global('navigation')]
28
+ * ```
29
+ */
30
+ tags?: TagSelector;
31
+ /**
32
+ * Which `afterChange` operations drop. Default: both. An upload collection
33
+ * can pass `['update']`: nothing can reference a document that did not exist
34
+ * a moment ago, so creating one makes no cached page untrue.
35
+ */
36
+ operations?: ReadonlyArray<'create' | 'update'>;
37
+ }
38
+ export interface GlobalTagHookOptions {
39
+ /** The tags to drop. Default: the global's own tag, `cacheTags.global(slug)`. */
40
+ tags?: TagSelector;
41
+ }
42
+ export interface TagRevalidationHooks {
43
+ /** `afterChange` for a collection. Skips draft writes. */
44
+ afterCollectionChange(options?: CollectionTagHookOptions): CollectionAfterChangeHook;
45
+ /** `afterDelete` for a collection. */
46
+ afterCollectionDelete(options?: Pick<CollectionTagHookOptions, 'tags'>): CollectionAfterDeleteHook;
47
+ /** `afterChange` for a global. */
48
+ afterGlobalChange(options?: GlobalTagHookOptions): GlobalAfterChangeHook;
49
+ }
50
+ /**
51
+ * Builds the tag-revalidation hooks for one tag scheme.
52
+ *
53
+ * ```ts
54
+ * import { createTagRevalidationHooks } from '@forumone/throughline-workflows'
55
+ * import { cacheTags } from './lib/cache-tags'
56
+ *
57
+ * const revalidation = createTagRevalidationHooks({ cacheTags })
58
+ *
59
+ * const Pages: CollectionConfig = {
60
+ * slug: 'pages',
61
+ * hooks: {
62
+ * afterChange: [revalidation.afterCollectionChange()],
63
+ * afterDelete: [revalidation.afterCollectionDelete()],
64
+ * },
65
+ * ...
66
+ * }
67
+ * const Navigation: GlobalConfig = {
68
+ * slug: 'navigation',
69
+ * hooks: { afterChange: [revalidation.afterGlobalChange()] },
70
+ * ...
71
+ * }
72
+ * ```
73
+ *
74
+ * A hook never throws. Failing it would roll back the editor's save over a
75
+ * cache miss. A failure is logged at `error` instead, and the one expected
76
+ * failure — running outside a Next request, as seeds, migrations and the CLI
77
+ * do — at `debug`.
78
+ */
79
+ export declare function createTagRevalidationHooks(options?: TagRevalidationOptions): TagRevalidationHooks;
80
+ //# sourceMappingURL=revalidate-tag-hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revalidate-tag-hooks.d.ts","sourceRoot":"","sources":["../src/revalidate-tag-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,yBAAyB,EACzB,qBAAqB,EAEtB,MAAM,SAAS,CAAA;AAEhB,OAAO,EAAE,KAAK,SAAS,EAAoB,MAAM,iBAAiB,CAAA;AAgBlE,0DAA0D;AAC1D,MAAM,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAEnE,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB;;;;OAIG;IACH,aAAa,CAAC,EAAE,eAAe,CAAA;CAChC;AAED,sDAAsD;AACtD,MAAM,MAAM,WAAW,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,KAAK,SAAS,MAAM,EAAE,CAAA;AAEnF,MAAM,WAAW,wBAAwB;IACvC;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC,QAAQ,GAAG,QAAQ,CAAC,CAAA;CAChD;AAED,MAAM,WAAW,oBAAoB;IACnC,iFAAiF;IACjF,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,qBAAqB,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,yBAAyB,CAAA;IACpF,sCAAsC;IACtC,qBAAqB,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,wBAAwB,EAAE,MAAM,CAAC,GAAG,yBAAyB,CAAA;IAClG,kCAAkC;IAClC,iBAAiB,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,qBAAqB,CAAA;CACzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,GAAE,sBAA2B,GACnC,oBAAoB,CA2DtB"}
@@ -0,0 +1,116 @@
1
+ import { isDraftWrite } from '@forumone/throughline-publishing';
2
+ import { defaultCacheTags } from './cache-tags.js';
3
+ import { IMMEDIATE, isOutsideNextRequest, loadNextCache } from './next-revalidate.js';
4
+ /**
5
+ * Builds the tag-revalidation hooks for one tag scheme.
6
+ *
7
+ * ```ts
8
+ * import { createTagRevalidationHooks } from '@forumone/throughline-workflows'
9
+ * import { cacheTags } from './lib/cache-tags'
10
+ *
11
+ * const revalidation = createTagRevalidationHooks({ cacheTags })
12
+ *
13
+ * const Pages: CollectionConfig = {
14
+ * slug: 'pages',
15
+ * hooks: {
16
+ * afterChange: [revalidation.afterCollectionChange()],
17
+ * afterDelete: [revalidation.afterCollectionDelete()],
18
+ * },
19
+ * ...
20
+ * }
21
+ * const Navigation: GlobalConfig = {
22
+ * slug: 'navigation',
23
+ * hooks: { afterChange: [revalidation.afterGlobalChange()] },
24
+ * ...
25
+ * }
26
+ * ```
27
+ *
28
+ * A hook never throws. Failing it would roll back the editor's save over a
29
+ * cache miss. A failure is logged at `error` instead, and the one expected
30
+ * failure — running outside a Next request, as seeds, migrations and the CLI
31
+ * do — at `debug`.
32
+ */
33
+ export function createTagRevalidationHooks(options = {}) {
34
+ const cacheTags = options.cacheTags ?? defaultCacheTags;
35
+ const revalidateTag = options.revalidateTag ?? nextRevalidateTag;
36
+ async function drop(tags, req) {
37
+ for (const tag of new Set(tags)) {
38
+ try {
39
+ await revalidateTag(tag);
40
+ }
41
+ catch (error) {
42
+ /*
43
+ Outside Next there is no server to be stale, so that one is noise.
44
+ Anything else means the cache did *not* clear while a server was
45
+ running — a real fault, so it is logged loudly. It is still not
46
+ thrown: an `afterChange` that throws fails the editor's save.
47
+ */
48
+ if (isOutsideNextRequest(error)) {
49
+ req.payload.logger.debug({ tag }, 'Skipped cache revalidation: not inside a Next request.');
50
+ }
51
+ else {
52
+ req.payload.logger.error({ err: error, tag }, 'Failed to revalidate a cache tag; the cached copy is stale.');
53
+ }
54
+ }
55
+ }
56
+ }
57
+ return {
58
+ afterCollectionChange(hookOptions = {}) {
59
+ const select = hookOptions.tags ?? ((t, slug) => [t.collection(slug)]);
60
+ const operations = new Set(hookOptions.operations ?? ['create', 'update']);
61
+ return async ({ collection, doc, operation, req }) => {
62
+ if (!operations.has(operation))
63
+ return doc;
64
+ if (isUnpublishedWrite(req, collection.slug, doc, operation))
65
+ return doc;
66
+ await drop(select(cacheTags, collection.slug), req);
67
+ return doc;
68
+ };
69
+ },
70
+ afterCollectionDelete(hookOptions = {}) {
71
+ const select = hookOptions.tags ?? ((t, slug) => [t.collection(slug)]);
72
+ return async ({ collection, doc, req }) => {
73
+ await drop(select(cacheTags, collection.slug), req);
74
+ return doc;
75
+ };
76
+ },
77
+ afterGlobalChange(hookOptions = {}) {
78
+ const select = hookOptions.tags ?? ((t, slug) => [t.global(slug)]);
79
+ return async ({ doc, global, req }) => {
80
+ await drop(select(cacheTags, global.slug), req);
81
+ return doc;
82
+ };
83
+ },
84
+ };
85
+ }
86
+ /*
87
+ A write the public cannot see changes nothing cached, so there is nothing to
88
+ drop — and with autosave on, "a draft save" is every few seconds of an open
89
+ editor. Without this, one editing session evicts the tag over and over and every
90
+ request in between pays a cold read for content that did not change.
91
+
92
+ `isDraftWrite` is publishing's own predicate, the one its trust boundary uses.
93
+ An `afterChange` hook cannot answer this for itself: Payload sets
94
+ `data._status = 'draft'` on any `draft: true` update before hooks run, and
95
+ `previousDoc` is the latest *version*, so a draft save of a published document
96
+ and an unpublish of it look the same from in here. The publishing plugin records
97
+ the operation's real `draft` argument in `beforeOperation`. That is recorded for
98
+ the collections the plugin manages; on any other collection `isDraftWrite` is
99
+ false and the hook drops, which is the safe direction.
100
+
101
+ An unpublish is not a draft write and still drops — it is the case that would
102
+ otherwise leave a cached link pointing at a 404.
103
+
104
+ A *create* that lands as a draft is unambiguous on its own: nothing published
105
+ existed a moment ago, so nothing cached can be about it.
106
+ */
107
+ function isUnpublishedWrite(req, collectionSlug, doc, operation) {
108
+ if (operation === 'create')
109
+ return doc?._status === 'draft';
110
+ return isDraftWrite(req, collectionSlug, doc?.id);
111
+ }
112
+ async function nextRevalidateTag(tag) {
113
+ const { revalidateTag } = await loadNextCache();
114
+ revalidateTag(tag, IMMEDIATE);
115
+ }
116
+ //# sourceMappingURL=revalidate-tag-hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revalidate-tag-hooks.js","sourceRoot":"","sources":["../src/revalidate-tag-hooks.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Inngest } from 'inngest';
2
2
  import type { Payload } from 'payload';
3
+ import type { CacheTags } from './cache-tags.js';
3
4
  /**
4
5
  * Common dependencies for workflow factories. Every factory takes an
5
6
  * Inngest client and a Payload instance — the workflows-as-factories shape
@@ -92,15 +93,30 @@ export interface RevalidateOnPublishOptions extends BaseWorkflowOptions {
92
93
  */
93
94
  revalidate?: RevalidateFn;
94
95
  /**
95
- * Per-collection URL builders. Built-in defaults: pages → /slug
96
- * (with `home` mapped to `/`), posts → /blog/slug. Other collections
97
- * fall back to /<slug> unless you provide a builder.
96
+ * Per-collection URL builders: where a document of each collection is
97
+ * served, given its slug. Required, and there are no built-in entries —
98
+ * only the site knows its routes. A collection with no entry has its tags
99
+ * dropped and no path revalidated, and the run logs a warning.
100
+ *
101
+ * ```ts
102
+ * urlBuilders: {
103
+ * pages: (slug) => (slug === 'home' ? '/' : `/${slug}`),
104
+ * posts: (slug) => `/news/${slug}`,
105
+ * }
106
+ * ```
107
+ */
108
+ urlBuilders: Record<string, (slug: string) => string>;
109
+ /**
110
+ * The tag scheme, shared with readers and with
111
+ * `createTagRevalidationHooks`. A collection with no `collectionTags` entry
112
+ * drops `[cacheTags.collection(<collection>)]`. Default: `defaultCacheTags`,
113
+ * whose collection tag is the bare slug.
98
114
  */
99
- urlBuilders?: Record<string, (slug: string) => string>;
115
+ cacheTags?: CacheTags;
100
116
  /**
101
- * Per-collection cache tags. Defaults to `[<collection>]`. Multiple tags
102
- * supported for collections that participate in shared listings (e.g.
103
- * `programs` → `['programs', 'sitemap']`).
117
+ * Per-collection cache tags, overriding the scheme for that collection.
118
+ * Multiple tags supported for collections that participate in shared
119
+ * listings (e.g. `programs` → `['programs', 'sitemap']`).
104
120
  */
105
121
  collectionTags?: Record<string, string[]>;
106
122
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAoEA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAC5B,OAA4B,EAC5B,kBAA2B;IAE3B,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,kBAAkB,CAAA;IAE7D,OAAO;QACL,wEAAwE;QACxE,oEAAoE;QACpE,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9E,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtD,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAqEA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAC5B,OAA4B,EAC5B,kBAA2B;IAE3B,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,kBAAkB,CAAA;IAE7D,OAAO;QACL,wEAAwE;QACxE,oEAAoE;QACpE,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9E,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtD,CAAA;AACH,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@forumone/throughline-workflows",
3
- "version": "0.4.0",
4
- "description": "Composable Inngest function factories for the Throughline framework — revalidation, scheduled publishing, approval expiration, audit fan-out, healthcheck.",
3
+ "version": "0.5.0",
4
+ "description": "Composable Inngest function factories and Payload cache-revalidation hooks for the Throughline framework — revalidation, scheduled publishing, approval expiration, audit fan-out, healthcheck.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -9,6 +9,10 @@
9
9
  ".": {
10
10
  "types": "./dist/index.d.ts",
11
11
  "default": "./dist/index.js"
12
+ },
13
+ "./cache-tags": {
14
+ "types": "./dist/cache-tags.d.ts",
15
+ "default": "./dist/cache-tags.js"
12
16
  }
13
17
  },
14
18
  "files": [
@@ -38,7 +42,7 @@
38
42
  "peerDependencies": {
39
43
  "inngest": "^4.0.0",
40
44
  "next": ">=15.0.0",
41
- "payload": "^3.0.0"
45
+ "payload": "^3.89.0"
42
46
  },
43
47
  "peerDependenciesMeta": {
44
48
  "next": {
@@ -46,7 +50,8 @@
46
50
  }
47
51
  },
48
52
  "dependencies": {
49
- "@forumone/throughline-core": "0.9.1"
53
+ "@forumone/throughline-core": "0.10.0",
54
+ "@forumone/throughline-publishing": "0.11.0"
50
55
  },
51
56
  "devDependencies": {
52
57
  "@types/node": "^24.13.2",