kizlo 0.30.0 → 0.31.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.
@@ -1,4 +1,4 @@
1
- //#region src/integrations/tanstack-start/react/cloudflare/cache-policy.ts
1
+ //#region src/integrations/tanstack-start/cloudflare/cache-policy.ts
2
2
  const EDGE_HEADERS = [
3
3
  "Cloudflare-CDN-Cache-Control",
4
4
  "CDN-Cache-Control",
@@ -6,7 +6,6 @@ const EDGE_HEADERS = [
6
6
  ];
7
7
  function validateTags(tags) {
8
8
  if (!Array.isArray(tags)) throw new TypeError("CDN tags must be an array of strings.");
9
- if (tags.length > 1e3) throw new RangeError("At most 1000 explicit CDN tags are supported per response.");
10
9
  for (const tag of tags) if (typeof tag !== "string" || !tag.length || tag.length > 1024 || /[^\x21-\x2b\x2d-\x7e]/.test(tag)) throw new TypeError("CDN tags must contain 1-1024 printable ASCII characters without spaces or commas.");
11
10
  return [...new Set(tags)];
12
11
  }
@@ -1,6 +1,7 @@
1
1
  import { Duration } from "@kizlo/shared";
2
2
 
3
- //#region src/integrations/tanstack-start/react/cloudflare/cache.d.ts
3
+ //#region src/integrations/tanstack-start/cloudflare/cache.d.ts
4
+
4
5
  interface CloudflareCacheResult {
5
6
  success: boolean;
6
7
  errors?: {
@@ -14,6 +15,9 @@ interface CloudflareCacheBinding {
14
15
  tags?: string[];
15
16
  pathPrefixes?: string[];
16
17
  }): Promise<CloudflareCacheResult>;
18
+ purge?(options: {
19
+ purgeEverything: true;
20
+ }): Promise<CloudflareCacheResult>;
17
21
  }
18
22
  interface CdnHeadersOptions {
19
23
  /** Freshness in seconds at the edge. Browser reuse requires revalidation. */
@@ -30,5 +34,7 @@ declare function createCdnHeaders(options: CdnHeadersOptions): Record<string, st
30
34
  declare function revalidateUrl(url: string | URL): Promise<void>;
31
35
  /** Mark responses carrying this host-scoped tag stale under their stored cache policy. */
32
36
  declare function revalidateTag(tag: string): Promise<void>;
37
+ /** Remove every cached response owned by the calling Worker entrypoint. */
38
+ declare function purgeCache(): Promise<void>;
33
39
  //#endregion
34
- export { CdnHeadersOptions, CloudflareCacheBinding, CloudflareCacheResult, createCdnHeaders, revalidateTag, revalidateUrl };
40
+ export { CdnHeadersOptions, CloudflareCacheBinding, CloudflareCacheResult, createCdnHeaders, purgeCache, revalidateTag, revalidateUrl };
@@ -3,7 +3,7 @@ import { EDGE_HEADERS, validateTags } from "./cache-policy.js";
3
3
  import { seconds } from "@kizlo/shared";
4
4
  import { createHash } from "node:crypto";
5
5
 
6
- //#region src/integrations/tanstack-start/react/cloudflare/cache.ts
6
+ //#region src/integrations/tanstack-start/cloudflare/cache.ts
7
7
  function getCacheContext() {
8
8
  const context = getStartRequestContext();
9
9
  const cache = (context?.values)?.executionCtx?.cache;
@@ -47,13 +47,25 @@ async function revalidateUrl(url) {
47
47
  }
48
48
  /** Mark responses carrying this host-scoped tag stale under their stored cache policy. */
49
49
  async function revalidateTag(tag) {
50
+ await revalidateTags([tag]);
51
+ }
52
+ /** Submit every dependency, independently of the response storage budget. */
53
+ async function revalidateTags(tags) {
54
+ const unique = validateTags(tags);
50
55
  const state = getCacheContext();
51
- validateTags([tag]);
52
- await invalidate(state.cache, { tags: [resourceTag(state.url, tag)] });
56
+ for (let index = 0; index < unique.length; index += 100) await invalidate(state.cache, { tags: unique.slice(index, index + 100).map((tag) => resourceTag(state.url, tag)) });
57
+ }
58
+ /** Remove every cached response owned by the calling Worker entrypoint. */
59
+ async function purgeCache() {
60
+ const { cache } = getCacheContext();
61
+ if (typeof cache.purge !== "function") throw new Error("The Worker runtime does not support cache.purge().");
62
+ assertCacheResult(await cache.purge({ purgeEverything: true }));
53
63
  }
54
64
  async function invalidate(cache, targets) {
55
65
  if (typeof cache.invalidate !== "function") throw new Error("The Worker runtime does not support cache.invalidate().");
56
- const result = await cache.invalidate(targets);
66
+ assertCacheResult(await cache.invalidate(targets));
67
+ }
68
+ function assertCacheResult(result) {
57
69
  if (result?.success !== true) {
58
70
  const detail = result?.errors?.map(({ code, message }) => `${code}: ${message}`).join("; ");
59
71
  throw new Error(`Cloudflare CDN revalidation failed${detail ? `: ${detail}` : "."}`);
@@ -67,8 +79,26 @@ function applyCloudflareHeaders(request, response) {
67
79
  const authoredTags = result.get("Cache-Tag");
68
80
  if (authoredTags !== null) {
69
81
  const url = new URL(request.url);
70
- const tagHeader = validateTags(authoredTags.split(",").map((tag) => tag.trim())).map((tag) => resourceTag(url, tag)).join(",");
71
- if (tagHeader.length > MAX_TAG_HEADER_BYTES) throw new RangeError("Encoded CDN tags exceed Cloudflare's 16 KB header limit.");
82
+ const resources = validateTags(authoredTags.split(",").map((tag) => tag.trim()));
83
+ const encoded = resources.map((tag) => resourceTag(url, tag));
84
+ const retained = [];
85
+ let bytes = 0;
86
+ for (const tag of encoded) {
87
+ const next = bytes + tag.length + (retained.length ? 1 : 0);
88
+ if (retained.length === 1e3 || next > MAX_TAG_HEADER_BYTES) break;
89
+ retained.push(tag);
90
+ bytes = next;
91
+ }
92
+ if (retained.length < resources.length) console.warn({
93
+ code: "KIZLO_CDN_TAGS_TRIMMED",
94
+ pathname: url.pathname,
95
+ requested: resources.length,
96
+ retained: retained.length,
97
+ dropped: resources.length - retained.length,
98
+ maxBytes: MAX_TAG_HEADER_BYTES,
99
+ maxTags: 1e3
100
+ });
101
+ const tagHeader = retained.join(",");
72
102
  result.set("Cache-Tag", tagHeader);
73
103
  }
74
104
  return new Response(response.body, {
@@ -79,4 +109,4 @@ function applyCloudflareHeaders(request, response) {
79
109
  }
80
110
 
81
111
  //#endregion
82
- export { applyCloudflareHeaders, createCdnHeaders, revalidateTag, revalidateUrl };
112
+ export { applyCloudflareHeaders, createCdnHeaders, getCacheContext, purgeCache, revalidateTag, revalidateTags, revalidateUrl };
@@ -0,0 +1,35 @@
1
+ import { PostEventData, SETTINGS_INDEXED_EVENT_TYPES, SettingsEventType, TermDependencies, TermEventData } from "../../../webhook/schema.js";
2
+ import { Integration } from "../../../shared/integration.js";
3
+ import "../../../index.js";
4
+ import z from "zod/v4";
5
+
6
+ //#region src/integrations/tanstack-start/cloudflare/revalidator.d.ts
7
+ type RevalidationPostData = z.output<typeof PostEventData> & {
8
+ affected_terms: TermDependencies;
9
+ };
10
+ type RevalidationTermData = z.output<typeof TermEventData> & {
11
+ affected_terms: TermDependencies;
12
+ };
13
+ interface RevalidationPostRule {
14
+ resourceTag(id: number): string;
15
+ collectionTags?(data: RevalidationPostData): readonly string[];
16
+ }
17
+ interface RevalidationTaxonomyRule {
18
+ resourceTag(id: number): string;
19
+ collectionTags?(id: number, data: RevalidationTermData): readonly string[];
20
+ }
21
+ type RevalidationSettingsEvent<K$1 extends SettingsEventType> = {
22
+ type: K$1;
23
+ data: K$1 extends (typeof SETTINGS_INDEXED_EVENT_TYPES)[number] ? {
24
+ key: string;
25
+ } : null;
26
+ };
27
+ interface RevalidatorOptions {
28
+ postTypes?: Record<string, RevalidationPostRule>;
29
+ taxonomies?: Record<string, RevalidationTaxonomyRule>;
30
+ settings?: "purge" | { [K in SettingsEventType]?: (event: RevalidationSettingsEvent<K>) => readonly string[] };
31
+ }
32
+ /** Optional content-change rules; does not install a response caching policy. */
33
+ declare function revalidator(options?: RevalidatorOptions): Integration<"revalidator", Record<never, never>>;
34
+ //#endregion
35
+ export { RevalidationPostData, RevalidationPostRule, RevalidationSettingsEvent, RevalidationTaxonomyRule, RevalidationTermData, RevalidatorOptions, revalidator };
@@ -0,0 +1,130 @@
1
+ import { KizloError } from "../../../shared/error.js";
2
+ import { createIntegration } from "../../../shared/integration.js";
3
+ import { POST_EVENT_TYPES, PostEventData, SETTINGS_EVENT_TYPES, SETTINGS_INDEXED_EVENT_TYPES, SettingsIndexedEventData, SettingsSimpleEventData, TERM_EVENT_TYPES, TermDependencies, TermEventData } from "../../../webhook/schema.js";
4
+ import { createEventHandler } from "../../../webhook/index.js";
5
+ import { validateTags } from "./cache-policy.js";
6
+ import { getCacheContext, purgeCache, revalidateTags } from "./cache.js";
7
+ import z from "zod/v4";
8
+
9
+ //#region src/integrations/tanstack-start/cloudflare/revalidator.ts
10
+ const rule = z.object({
11
+ resourceTag: z.custom((value) => typeof value === "function"),
12
+ collectionTags: z.custom((value) => typeof value === "function").optional()
13
+ }).strict();
14
+ const rules = z.record(z.string().regex(/^[a-zA-Z0-9_-]+$/), rule);
15
+ const optionsSchema = z.object({
16
+ postTypes: rules.optional(),
17
+ taxonomies: rules.optional(),
18
+ settings: z.union([z.literal("purge"), z.partialRecord(z.enum(SETTINGS_EVENT_TYPES), z.custom((value) => typeof value === "function"))]).optional()
19
+ }).strict();
20
+ /** Optional content-change rules; does not install a response caching policy. */
21
+ function revalidator(options = {}) {
22
+ optionsSchema.parse(options);
23
+ const purgeSettings = options.settings === "purge";
24
+ const settings = typeof options.settings === "object" ? options.settings : {};
25
+ const postSchema = z.unknown().transform((data, ctx) => {
26
+ if (!(data !== null && typeof data === "object" && "post_type" in data && typeof data.post_type === "string" && Object.hasOwn(options.postTypes ?? {}, data.post_type))) {
27
+ if (Object.keys(options.postTypes ?? {}).length && (data === null || typeof data !== "object" || !("post_type" in data) || typeof data.post_type !== "string" || !data.post_type.length)) {
28
+ ctx.addIssue({
29
+ code: "custom",
30
+ message: "A post_type is required to select configured rules"
31
+ });
32
+ return z.NEVER;
33
+ }
34
+ return data;
35
+ }
36
+ const result = PostEventData.extend({ affected_terms: TermDependencies }).safeParse(data);
37
+ if (!result.success) {
38
+ ctx.addIssue({
39
+ code: "custom",
40
+ message: "Complete affected_terms is required for configured post types"
41
+ });
42
+ return z.NEVER;
43
+ }
44
+ return result.data;
45
+ });
46
+ const termSchema = z.unknown().transform((data, ctx) => {
47
+ if (!(data !== null && typeof data === "object" && "taxonomy" in data && typeof data.taxonomy === "string" && Object.hasOwn(options.taxonomies ?? {}, data.taxonomy))) {
48
+ if (Object.keys(options.taxonomies ?? {}).length && (data === null || typeof data !== "object" || !("taxonomy" in data) || typeof data.taxonomy !== "string" || !data.taxonomy.length)) {
49
+ ctx.addIssue({
50
+ code: "custom",
51
+ message: "A taxonomy is required to select configured rules"
52
+ });
53
+ return z.NEVER;
54
+ }
55
+ return data;
56
+ }
57
+ const result = TermEventData.extend({ affected_terms: TermDependencies }).safeParse(data);
58
+ if (!result.success) {
59
+ ctx.addIssue({
60
+ code: "custom",
61
+ message: "Complete affected_terms is required for configured taxonomies"
62
+ });
63
+ return z.NEVER;
64
+ }
65
+ return result.data;
66
+ });
67
+ return createIntegration({
68
+ id: "revalidator",
69
+ events: [{
70
+ ...createEventHandler([
71
+ {
72
+ types: [...POST_EVENT_TYPES],
73
+ data: postSchema
74
+ },
75
+ {
76
+ types: [...TERM_EVENT_TYPES],
77
+ data: termSchema
78
+ },
79
+ ...(purgeSettings ? SETTINGS_EVENT_TYPES : Object.keys(settings)).map((type) => ({
80
+ types: [type],
81
+ data: SETTINGS_INDEXED_EVENT_TYPES.includes(type) ? SettingsIndexedEventData : SettingsSimpleEventData
82
+ }))
83
+ ], async (event, context) => {
84
+ if (!event) return;
85
+ const selected = [];
86
+ const addResource = (tag) => {
87
+ selected.push(...validateTags([tag]));
88
+ };
89
+ const addCollection = (tags) => {
90
+ selected.push(...validateTags(tags));
91
+ };
92
+ if (POST_EVENT_TYPES.includes(event.type)) {
93
+ const data = event.data;
94
+ const rule$1 = data && typeof data.post_type === "string" && Object.hasOwn(options.postTypes ?? {}, data.post_type) ? options.postTypes?.[data.post_type] : void 0;
95
+ if (!rule$1 || !data) return;
96
+ addResource(rule$1.resourceTag(data.post_id));
97
+ if (rule$1.collectionTags) addCollection(rule$1.collectionTags(data));
98
+ } else if (TERM_EVENT_TYPES.includes(event.type)) {
99
+ const data = event.data;
100
+ const rule$1 = data && typeof data.taxonomy === "string" && Object.hasOwn(options.taxonomies ?? {}, data.taxonomy) ? options.taxonomies?.[data.taxonomy] : void 0;
101
+ if (!rule$1 || !data) return;
102
+ for (const id of new Set([data.term_id, ...data.affected_terms[data.taxonomy] ?? []])) {
103
+ addResource(rule$1.resourceTag(id));
104
+ if (rule$1.collectionTags) addCollection(rule$1.collectionTags(id, data));
105
+ }
106
+ } else if (!purgeSettings) {
107
+ const setting = settings[event.type];
108
+ if (!setting) return;
109
+ addCollection(setting(event));
110
+ }
111
+ const purge = purgeSettings && SETTINGS_EVENT_TYPES.includes(event.type);
112
+ if (!purge && !selected.length) return;
113
+ const active = getCacheContext();
114
+ const expected = new URL(context.config.baseUrl ?? "").origin;
115
+ if (active.url.origin !== expected || !context.request || new URL(context.request.url).origin !== expected) throw new KizloError("UNPROCESSABLE_CONTENT", { message: "Revalidation webhook must use the configured public origin and cache-owning Worker entrypoint." });
116
+ try {
117
+ if (purge) await purgeCache();
118
+ else await revalidateTags(selected);
119
+ } catch (error) {
120
+ if (error !== null && typeof error === "object" && "status" in error && error.status === 429) throw new KizloError("TOO_MANY_REQUESTS", { cause: error });
121
+ throw error;
122
+ }
123
+ }),
124
+ failureMode: "retry"
125
+ }]
126
+ });
127
+ }
128
+
129
+ //#endregion
130
+ export { revalidator };
@@ -3,7 +3,7 @@ import { EventHandler } from "../../../webhook/index.js";
3
3
  import { EnvSource, IntegrationRequirements } from "../../../shared/integration.js";
4
4
  import "../../../index.js";
5
5
  import { TanstackStartOptions } from "../integration.js";
6
- import { getStartRequestContext } from "./request-context.js";
6
+ import { getStartRequestContext } from "../request-context.js";
7
7
 
8
8
  //#region src/integrations/tanstack-start/react/integration.d.ts
9
9
  /** Add React Start's native request context and cookies to the shared environment mapping. */
@@ -1,6 +1,6 @@
1
1
  import { tanstackStart } from "../integration.js";
2
2
  import { startCookies } from "./cookies.js";
3
- import { getStartRequestContext } from "./request-context.js";
3
+ import { getStartRequestContext } from "../request-context.js";
4
4
 
5
5
  //#region src/integrations/tanstack-start/react/integration.ts
6
6
  /** Add React Start's native request context and cookies to the shared environment mapping. */
@@ -1,9 +1,9 @@
1
- import * as _tanstack_react_start0 from "@tanstack/react-start";
1
+ import * as _tanstack_start_client_core0 from "@tanstack/start-client-core";
2
2
  import { AnyRequestMiddleware } from "@tanstack/react-start";
3
3
 
4
4
  //#region src/integrations/tanstack-start/react/middleware.d.ts
5
5
  type MiddlewareMatcher = string | readonly string[] | ((request: Request) => boolean);
6
6
  /** Wrap native request middleware with a path-to-regexp pattern or synchronous request predicate. */
7
- declare function matchMiddleware<Middleware extends AnyRequestMiddleware>(middleware: Middleware, matcher: MiddlewareMatcher): Pick<_tanstack_react_start0.RequestMiddlewareAfterServer<{}, readonly [Middleware], undefined>, "~types" | "options">;
7
+ declare function matchMiddleware<Middleware extends AnyRequestMiddleware>(middleware: Middleware, matcher: MiddlewareMatcher): Pick<_tanstack_start_client_core0.RequestMiddlewareAfterServer<{}, readonly [Middleware], undefined>, "options" | "~types">;
8
8
  //#endregion
9
9
  export { MiddlewareMatcher, matchMiddleware };
@@ -1,4 +1,4 @@
1
- //#region src/integrations/tanstack-start/react/request-context.d.ts
1
+ //#region src/integrations/tanstack-start/request-context.d.ts
2
2
  /** Read Start's native request scope; no application-owned async storage is needed. */
3
3
  declare function getStartRequestContext(): {
4
4
  request: Request;
@@ -1,7 +1,7 @@
1
- import { getRequest } from "@tanstack/react-start/server";
2
- import { getGlobalStartContext } from "@tanstack/react-start";
1
+ import { getGlobalStartContext } from "@tanstack/start-client-core";
2
+ import { getRequest } from "@tanstack/start-server-core/request-response";
3
3
 
4
- //#region src/integrations/tanstack-start/react/request-context.ts
4
+ //#region src/integrations/tanstack-start/request-context.ts
5
5
  /** Read Start's native request scope; no application-owned async storage is needed. */
6
6
  function getStartRequestContext() {
7
7
  let request;
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.30.0";
2
+ var version = "0.31.0";
3
3
 
4
4
  //#endregion
5
5
  export { version };
@@ -26,13 +26,13 @@ declare const Post: z.ZodObject<{
26
26
  private: "private";
27
27
  }>;
28
28
  format: z.ZodEnum<{
29
+ image: "image";
29
30
  link: "link";
30
31
  status: "status";
31
32
  standard: "standard";
32
33
  aside: "aside";
33
34
  chat: "chat";
34
35
  gallery: "gallery";
35
- image: "image";
36
36
  quote: "quote";
37
37
  video: "video";
38
38
  audio: "audio";
@@ -150,13 +150,13 @@ declare const PostList: z.ZodObject<{
150
150
  private: "private";
151
151
  }>;
152
152
  format: z.ZodEnum<{
153
+ image: "image";
153
154
  link: "link";
154
155
  status: "status";
155
156
  standard: "standard";
156
157
  aside: "aside";
157
158
  chat: "chat";
158
159
  gallery: "gallery";
159
- image: "image";
160
160
  quote: "quote";
161
161
  video: "video";
162
162
  audio: "audio";
@@ -44,7 +44,7 @@ declare const CORE_PROCEDURES: {
44
44
  include?: string | number | (string | number)[] | undefined;
45
45
  offset?: string | number | undefined;
46
46
  order?: "asc" | "desc" | undefined;
47
- orderby?: "date" | "id" | "slug" | "parent" | "title" | "author" | "include" | "modified" | "relevance" | "include_slugs" | undefined;
47
+ orderby?: "id" | "slug" | "parent" | "title" | "author" | "date" | "include" | "include_slugs" | "modified" | "relevance" | undefined;
48
48
  searchColumns?: ("post_title" | "post_content" | "post_excerpt")[] | undefined;
49
49
  slug?: string | string[] | undefined;
50
50
  taxRelation?: "AND" | "OR" | undefined;
@@ -99,7 +99,7 @@ declare const CORE_PROCEDURES: {
99
99
  include?: string | number | (string | number)[] | undefined;
100
100
  offset?: string | number | undefined;
101
101
  order?: "asc" | "desc" | undefined;
102
- orderby?: "date" | "id" | "slug" | "parent" | "title" | "author" | "include" | "modified" | "relevance" | "include_slugs" | "menu_order" | undefined;
102
+ orderby?: "id" | "slug" | "parent" | "title" | "author" | "date" | "include" | "include_slugs" | "modified" | "relevance" | "menu_order" | undefined;
103
103
  searchColumns?: ("post_title" | "post_content" | "post_excerpt")[] | undefined;
104
104
  slug?: string | string[] | undefined;
105
105
  parent?: string | number | (string | number)[] | undefined;
@@ -136,7 +136,7 @@ declare const CORE_PROCEDURES: {
136
136
  exclude?: string | number | (string | number)[] | undefined;
137
137
  include?: string | number | (string | number)[] | undefined;
138
138
  order?: "asc" | "desc" | undefined;
139
- orderBy?: "description" | "count" | "id" | "slug" | "name" | "include" | "include_slugs" | "term_group" | undefined;
139
+ orderBy?: "id" | "name" | "slug" | "description" | "include" | "include_slugs" | "term_group" | "count" | undefined;
140
140
  hideEmpty?: string | boolean | undefined;
141
141
  parent?: string | number | undefined;
142
142
  post?: string | number | undefined;
@@ -172,7 +172,7 @@ declare const CORE_PROCEDURES: {
172
172
  exclude?: string | number | (string | number)[] | undefined;
173
173
  include?: string | number | (string | number)[] | undefined;
174
174
  order?: "asc" | "desc" | undefined;
175
- orderBy?: "description" | "count" | "id" | "slug" | "name" | "include" | "include_slugs" | "term_group" | undefined;
175
+ orderBy?: "id" | "name" | "slug" | "description" | "include" | "include_slugs" | "term_group" | "count" | undefined;
176
176
  hideEmpty?: string | boolean | undefined;
177
177
  post?: string | number | undefined;
178
178
  slug?: string | string[] | undefined;
@@ -241,7 +241,7 @@ declare const CORE_PROCEDURES: {
241
241
  sitemaps: {
242
242
  list: Procedure<"internal", {}, {
243
243
  key: string;
244
- type: "post_type" | "taxonomy" | "author";
244
+ type: "author" | "post_type" | "taxonomy";
245
245
  pages: number;
246
246
  lastmod: string;
247
247
  }[], DefinedErrorMapLike>;
@@ -249,7 +249,7 @@ declare const CORE_PROCEDURES: {
249
249
  origin: string;
250
250
  sitemaps: {
251
251
  key: string;
252
- type: "post_type" | "taxonomy" | "author";
252
+ type: "author" | "post_type" | "taxonomy";
253
253
  pages: number;
254
254
  lastmod: string;
255
255
  }[];
@@ -292,7 +292,7 @@ declare const CORE_PROCEDURES: {
292
292
  include?: string | number | (string | number)[] | undefined;
293
293
  offset?: string | number | undefined;
294
294
  order?: "asc" | "desc" | undefined;
295
- orderby?: "date" | "id" | "slug" | "parent" | "title" | "author" | "include" | "relevance" | "include_slugs" | "menu_order" | undefined;
295
+ orderby?: "id" | "slug" | "parent" | "title" | "author" | "date" | "include" | "include_slugs" | "relevance" | "menu_order" | undefined;
296
296
  searchColumns?: ("post_title" | "post_content" | "post_excerpt")[] | undefined;
297
297
  slug?: string | string[] | undefined;
298
298
  taxRelation?: "AND" | "OR" | undefined;
@@ -371,7 +371,7 @@ declare const CORE_PROCEDURES: {
371
371
  include?: string | number | (string | number)[] | undefined;
372
372
  offset?: string | number | undefined;
373
373
  order?: "asc" | "desc" | undefined;
374
- orderby?: "date" | "id" | "slug" | "parent" | "title" | "author" | "include" | "relevance" | "include_slugs" | "menu_order" | undefined;
374
+ orderby?: "id" | "slug" | "parent" | "title" | "author" | "date" | "include" | "include_slugs" | "relevance" | "menu_order" | undefined;
375
375
  searchColumns?: ("post_title" | "post_content" | "post_excerpt")[] | undefined;
376
376
  slug?: string | string[] | undefined;
377
377
  taxRelation?: "AND" | "OR" | undefined;
@@ -507,7 +507,7 @@ declare const CORE_PROCEDURES: {
507
507
  include?: string | number | (string | number)[] | undefined;
508
508
  offset?: string | number | undefined;
509
509
  order?: "asc" | "desc" | undefined;
510
- orderby?: "date" | "id" | "parent" | "include" | "post" | undefined;
510
+ orderby?: "id" | "parent" | "date" | "include" | "post" | undefined;
511
511
  parent?: string | number | (string | number)[] | undefined;
512
512
  parentExclude?: string | number | (string | number)[] | undefined;
513
513
  post?: string | number | (string | number)[] | undefined;
@@ -635,8 +635,8 @@ declare const CORE_PROCEDURES: {
635
635
  get: Procedure<"internal", {}, _kizlo_shared11.Settings, DefinedErrorMapLike>;
636
636
  site: {
637
637
  update: Procedure<"internal", {
638
- url?: string | null | undefined;
639
638
  name?: string | null | undefined;
639
+ url?: string | null | undefined;
640
640
  backend_url?: string | null | undefined;
641
641
  secret?: string | null | undefined;
642
642
  alternate_name?: string | null | undefined;
@@ -108,17 +108,17 @@ declare const Seo: z.ZodObject<{
108
108
  type Seo = z.infer<typeof Seo>;
109
109
  declare const CONTENT_TYPES: readonly ["post_type", "taxonomy", "author"];
110
110
  declare const ContentType: z.ZodEnum<{
111
+ author: "author";
111
112
  post_type: "post_type";
112
113
  taxonomy: "taxonomy";
113
- author: "author";
114
114
  }>;
115
115
  type ContentType = z.infer<typeof ContentType>;
116
116
  declare const Sitemap: z.ZodObject<{
117
117
  key: z.ZodString;
118
118
  type: z.ZodEnum<{
119
+ author: "author";
119
120
  post_type: "post_type";
120
121
  taxonomy: "taxonomy";
121
- author: "author";
122
122
  }>;
123
123
  pages: z.ZodNumber;
124
124
  lastmod: z.ZodString;
@@ -127,9 +127,9 @@ type Sitemap = z.infer<typeof Sitemap>;
127
127
  declare const SitemapList: z.ZodArray<z.ZodObject<{
128
128
  key: z.ZodString;
129
129
  type: z.ZodEnum<{
130
+ author: "author";
130
131
  post_type: "post_type";
131
132
  taxonomy: "taxonomy";
132
- author: "author";
133
133
  }>;
134
134
  pages: z.ZodNumber;
135
135
  lastmod: z.ZodString;
@@ -140,9 +140,9 @@ declare const SitemapIndex: z.ZodObject<{
140
140
  sitemaps: z.ZodArray<z.ZodObject<{
141
141
  key: z.ZodString;
142
142
  type: z.ZodEnum<{
143
+ author: "author";
143
144
  post_type: "post_type";
144
145
  taxonomy: "taxonomy";
145
- author: "author";
146
146
  }>;
147
147
  pages: z.ZodNumber;
148
148
  lastmod: z.ZodString;
@@ -174,9 +174,9 @@ declare const SitemapUrlList: z.ZodArray<z.ZodObject<{
174
174
  type SitemapUrlList = z.infer<typeof SitemapUrlList>;
175
175
  declare const ListSitemapUrlInputBase: z.ZodObject<{
176
176
  type: z.ZodEnum<{
177
+ author: "author";
177
178
  post_type: "post_type";
178
179
  taxonomy: "taxonomy";
179
- author: "author";
180
180
  }>;
181
181
  page: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodCoercedNumber<string | number>>>;
182
182
  }, z.core.$strip>;
@@ -1,9 +1,10 @@
1
- import { GeoAdapter } from "../../adapters/geo.js";
2
- import { Integration } from "../../shared/integration.js";
3
- import "../../index.js";
4
- import { CdnHeadersOptions, CloudflareCacheBinding, CloudflareCacheResult, createCdnHeaders, revalidateTag, revalidateUrl } from "../../integrations/tanstack-start/react/cloudflare/cache.js";
1
+ import { GeoAdapter } from "../adapters/geo.js";
2
+ import { Integration } from "../shared/integration.js";
3
+ import "../index.js";
4
+ import { CdnHeadersOptions, CloudflareCacheBinding, CloudflareCacheResult, createCdnHeaders, purgeCache, revalidateTag, revalidateUrl } from "../integrations/tanstack-start/cloudflare/cache.js";
5
+ import { RevalidationPostData, RevalidationPostRule, RevalidationSettingsEvent, RevalidationTaxonomyRule, RevalidationTermData, RevalidatorOptions, revalidator } from "../integrations/tanstack-start/cloudflare/revalidator.js";
5
6
 
6
- //#region src/integrations/tanstack-start/react/cloudflare.d.ts
7
+ //#region src/integrations/tanstack-start/cloudflare.d.ts
7
8
  interface ExecutionContext {
8
9
  cache?: CloudflareCacheBinding;
9
10
  waitUntil(promise: Promise<unknown>): void;
@@ -24,4 +25,4 @@ declare function withCloudflare<Env, Context extends ExecutionContext = Executio
24
25
  fetch(request: Request, env: Env, executionCtx: Context): Promise<Response>;
25
26
  };
26
27
  //#endregion
27
- export { type CdnHeadersOptions, type CloudflareCacheBinding, type CloudflareCacheResult, cloudflare, createCdnHeaders, geoCloudflare, revalidateTag, revalidateUrl, withCloudflare };
28
+ export { type CdnHeadersOptions, type CloudflareCacheBinding, type CloudflareCacheResult, type RevalidationPostData, type RevalidationPostRule, type RevalidationSettingsEvent, type RevalidationTaxonomyRule, type RevalidationTermData, type RevalidatorOptions, cloudflare, createCdnHeaders, geoCloudflare, purgeCache, revalidateTag, revalidateUrl, revalidator, withCloudflare };
@@ -1,10 +1,11 @@
1
- import { cloudflareConnInfo } from "../../adapters/cloudflare-connection.js";
2
- import { FRAMEWORK_INTEGRATION_ORDER } from "../../shared/constants.js";
3
- import { createIntegration } from "../../shared/integration.js";
4
- import { getStartRequestContext } from "../../integrations/tanstack-start/react/request-context.js";
5
- import { applyCloudflareHeaders, createCdnHeaders, revalidateTag, revalidateUrl } from "../../integrations/tanstack-start/react/cloudflare/cache.js";
1
+ import { cloudflareConnInfo } from "../adapters/cloudflare-connection.js";
2
+ import { FRAMEWORK_INTEGRATION_ORDER } from "../shared/constants.js";
3
+ import { createIntegration } from "../shared/integration.js";
4
+ import { getStartRequestContext } from "../integrations/tanstack-start/request-context.js";
5
+ import { applyCloudflareHeaders, createCdnHeaders, purgeCache, revalidateTag, revalidateUrl } from "../integrations/tanstack-start/cloudflare/cache.js";
6
+ import { revalidator } from "../integrations/tanstack-start/cloudflare/revalidator.js";
6
7
 
7
- //#region src/integrations/tanstack-start/react/cloudflare.ts
8
+ //#region src/integrations/tanstack-start/cloudflare.ts
8
9
  /** Cloudflare connection metadata, using TanStack Start's existing request scope. */
9
10
  function geoCloudflare() {
10
11
  return { getConnInfo(request) {
@@ -30,4 +31,4 @@ function withCloudflare(handler) {
30
31
  }
31
32
 
32
33
  //#endregion
33
- export { cloudflare, createCdnHeaders, geoCloudflare, revalidateTag, revalidateUrl, withCloudflare };
34
+ export { cloudflare, createCdnHeaders, geoCloudflare, purgeCache, revalidateTag, revalidateUrl, revalidator, withCloudflare };
@@ -22,6 +22,8 @@ interface WebhookOptions {
22
22
  events?: EventHandler[];
23
23
  }
24
24
  interface EventHandler {
25
+ /** Required handlers fail acknowledgement so the sender can retry. Defaults to log-only. */
26
+ failureMode?: "log" | "retry";
25
27
  events?: AnyEvents;
26
28
  handler: EventHandlerFn<AnyEvents>;
27
29
  }
@@ -31,12 +31,19 @@ function createWebhookRouter(options) {
31
31
  headers
32
32
  }));
33
33
  if (err) throw new KizloError("FORBIDDEN", { message: "Signature verification failed." });
34
- const results = await Promise.allSettled((options?.events ?? []).map(async ({ events, handler }) => {
35
- const event = await validateEvent(events, input.body);
34
+ const results = await Promise.allSettled((options?.events ?? []).map(async ({ events, handler, failureMode }) => {
35
+ const event = await validateEvent(events, input.body, failureMode);
36
36
  if (!event) return;
37
37
  await handler(event, context);
38
38
  }));
39
39
  for (const result of results) if (result.status === "rejected") context.logger.error("Webhook handler failed", result.reason);
40
+ const required = results.find((result, index) => result.status === "rejected" && options?.events?.[index]?.failureMode === "retry");
41
+ if (required?.status === "rejected") {
42
+ if (required.reason instanceof KizloError && required.reason.status === 422) throw required.reason;
43
+ const limited = required.reason instanceof KizloError && required.reason.status === 429;
44
+ if (limited) context.headers?.set("Retry-After", "60");
45
+ throw new KizloError(limited ? "TOO_MANY_REQUESTS" : "SERVICE_UNAVAILABLE", { message: "Required webhook work failed. Retry delivery." });
46
+ }
40
47
  }) };
41
48
  }
42
49
 
@@ -2,6 +2,38 @@ import z from "zod/v4";
2
2
 
3
3
  //#region src/webhook/schema.d.ts
4
4
 
5
+ declare const SETTINGS_INDEXED_EVENT_TYPES: readonly ["settings.post_type.updated", "settings.taxonomy.updated"];
6
+ declare const SettingsEventType: z.ZodEnum<{
7
+ "settings.site.updated": "settings.site.updated";
8
+ "settings.brand.updated": "settings.brand.updated";
9
+ "settings.identity.updated": "settings.identity.updated";
10
+ "settings.authors.updated": "settings.authors.updated";
11
+ "settings.crawling.updated": "settings.crawling.updated";
12
+ "settings.headless.updated": "settings.headless.updated";
13
+ "settings.integration.updated": "settings.integration.updated";
14
+ "settings.woocommerce.updated": "settings.woocommerce.updated";
15
+ "settings.post_type.updated": "settings.post_type.updated";
16
+ "settings.taxonomy.updated": "settings.taxonomy.updated";
17
+ }>;
18
+ type SettingsEventType = z.infer<typeof SettingsEventType>;
19
+ declare const TermDependencies: z.ZodRecord<z.ZodString, z.ZodPipe<z.ZodArray<z.ZodNumber>, z.ZodTransform<number[], number[]>>>;
20
+ type TermDependencies = z.infer<typeof TermDependencies>;
21
+ declare const PostEventData: z.ZodObject<{
22
+ post_id: z.ZodNumber;
23
+ affected_terms: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodPipe<z.ZodArray<z.ZodNumber>, z.ZodTransform<number[], number[]>>>>>;
24
+ post_type: z.ZodString;
25
+ url: z.ZodOptional<z.ZodString>;
26
+ }, z.core.$strip>;
27
+ type PostEventData = z.infer<typeof PostEventData>;
28
+ declare const TermEventData: z.ZodObject<{
29
+ term_id: z.ZodNumber;
30
+ affected_terms: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodPipe<z.ZodArray<z.ZodNumber>, z.ZodTransform<number[], number[]>>>>>;
31
+ taxonomy: z.ZodString;
32
+ post_types: z.ZodArray<z.ZodString>;
33
+ count: z.ZodNumber;
34
+ url: z.ZodOptional<z.ZodString>;
35
+ }, z.core.$strip>;
36
+ type TermEventData = z.infer<typeof TermEventData>;
5
37
  declare const KizloEvent: z.ZodUnion<readonly [z.ZodObject<{
6
38
  type: z.ZodEnum<{
7
39
  "post.created": "post.created";
@@ -11,6 +43,7 @@ declare const KizloEvent: z.ZodUnion<readonly [z.ZodObject<{
11
43
  }>;
12
44
  data: z.ZodObject<{
13
45
  post_id: z.ZodNumber;
46
+ affected_terms: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodPipe<z.ZodArray<z.ZodNumber>, z.ZodTransform<number[], number[]>>>>>;
14
47
  post_type: z.ZodString;
15
48
  url: z.ZodOptional<z.ZodString>;
16
49
  }, z.core.$strip>;
@@ -22,6 +55,7 @@ declare const KizloEvent: z.ZodUnion<readonly [z.ZodObject<{
22
55
  }>;
23
56
  data: z.ZodObject<{
24
57
  term_id: z.ZodNumber;
58
+ affected_terms: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodPipe<z.ZodArray<z.ZodNumber>, z.ZodTransform<number[], number[]>>>>>;
25
59
  taxonomy: z.ZodString;
26
60
  post_types: z.ZodArray<z.ZodString>;
27
61
  count: z.ZodNumber;
@@ -34,6 +68,7 @@ declare const KizloEvent: z.ZodUnion<readonly [z.ZodObject<{
34
68
  "settings.identity.updated": "settings.identity.updated";
35
69
  "settings.authors.updated": "settings.authors.updated";
36
70
  "settings.crawling.updated": "settings.crawling.updated";
71
+ "settings.headless.updated": "settings.headless.updated";
37
72
  "settings.integration.updated": "settings.integration.updated";
38
73
  "settings.woocommerce.updated": "settings.woocommerce.updated";
39
74
  }>;
@@ -49,4 +84,4 @@ declare const KizloEvent: z.ZodUnion<readonly [z.ZodObject<{
49
84
  }, z.core.$strip>]>;
50
85
  type KizloEvent = z.infer<typeof KizloEvent>;
51
86
  //#endregion
52
- export { KizloEvent };
87
+ export { KizloEvent, PostEventData, SETTINGS_INDEXED_EVENT_TYPES, SettingsEventType, TermDependencies, TermEventData };
@@ -20,6 +20,7 @@ const SETTINGS_SIMPLE_EVENT_TYPES = [
20
20
  "settings.identity.updated",
21
21
  "settings.authors.updated",
22
22
  "settings.crawling.updated",
23
+ "settings.headless.updated",
23
24
  "settings.integration.updated",
24
25
  "settings.woocommerce.updated"
25
26
  ];
@@ -34,8 +35,10 @@ const EVENT_TYPES = [
34
35
  ...SETTINGS_EVENT_TYPES
35
36
  ];
36
37
  const EventType = z.enum(EVENT_TYPES);
38
+ const TermDependencies = z.record(z.string().min(1), z.array(z.number().int().positive().max(Number.MAX_SAFE_INTEGER)).transform((ids) => [...new Set(ids)]));
37
39
  const PostEventData = z.object({
38
- post_id: z.number(),
40
+ post_id: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
41
+ affected_terms: TermDependencies.nullable().optional(),
39
42
  post_type: z.string(),
40
43
  url: z.string().optional()
41
44
  });
@@ -44,7 +47,8 @@ const PostEvent = z.object({
44
47
  data: PostEventData
45
48
  });
46
49
  const TermEventData = z.object({
47
- term_id: z.number(),
50
+ term_id: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
51
+ affected_terms: TermDependencies.nullable().optional(),
48
52
  taxonomy: z.string(),
49
53
  post_types: z.array(z.string()),
50
54
  count: z.number().nonnegative(),
@@ -77,4 +81,4 @@ const AnyEvent = z.object({
77
81
  });
78
82
 
79
83
  //#endregion
80
- export { AnyEvent, KizloEvent, POST_EVENT_TYPES, SETTINGS_EVENT_TYPES, TERM_EVENT_TYPES };
84
+ export { AnyEvent, KizloEvent, POST_EVENT_TYPES, PostEventData, SETTINGS_EVENT_TYPES, SETTINGS_INDEXED_EVENT_TYPES, SettingsIndexedEventData, SettingsSimpleEventData, TERM_EVENT_TYPES, TermDependencies, TermEventData };
@@ -1,8 +1,18 @@
1
+ import { KizloError } from "../shared/error.js";
1
2
  import { KizloEvent } from "./schema.js";
2
3
  import { safeValidateSchema } from "@kizlo/shared";
3
4
 
4
5
  //#region src/webhook/utils.ts
5
- async function validateEvent(events, data) {
6
+ async function validateEvent(events, data, failureMode = "log") {
7
+ const explicit = events?.find((entry) => entry.types.includes(data.type))?.data;
8
+ if (failureMode === "retry" && explicit) {
9
+ const result = await safeValidateSchema(explicit, data.data);
10
+ if (!result.success) throw new KizloError("UNPROCESSABLE_CONTENT", { message: "Invalid webhook dependencies. Update the event-producing plugin and check affected_terms." });
11
+ return {
12
+ type: data.type,
13
+ data: result.value
14
+ };
15
+ }
6
16
  const internal = await safeValidateSchema(KizloEvent, data);
7
17
  if (internal.success) return internal.value;
8
18
  const external = events?.find((a) => a.types.includes(data.type))?.data;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kizlo",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "type": "module",
5
5
  "description": "A headless WordPress framework for TypeScript.",
6
6
  "keywords": [
@@ -106,10 +106,10 @@
106
106
  "default": "./dist/tanstack-start/react/server.js"
107
107
  }
108
108
  },
109
- "./tanstack-start/react/cloudflare": {
109
+ "./tanstack-start/cloudflare": {
110
110
  "import": {
111
- "types": "./dist/tanstack-start/react/cloudflare.d.ts",
112
- "default": "./dist/tanstack-start/react/cloudflare.js"
111
+ "types": "./dist/tanstack-start/cloudflare.d.ts",
112
+ "default": "./dist/tanstack-start/cloudflare.js"
113
113
  }
114
114
  }
115
115
  },
@@ -147,6 +147,8 @@
147
147
  "peerDependencies": {
148
148
  "@tanstack/react-router": "^1.170.41",
149
149
  "@tanstack/react-start": "^1.168.60",
150
+ "@tanstack/start-client-core": "^1.170.34",
151
+ "@tanstack/start-server-core": "^1.169.39",
150
152
  "astro": "^7.0.0",
151
153
  "next": "^14.0.0 || ^15.0.0 || ^16.0.0",
152
154
  "react": "^18.0.0 || ^19.0.0",
@@ -160,6 +162,12 @@
160
162
  "@tanstack/react-start": {
161
163
  "optional": true
162
164
  },
165
+ "@tanstack/start-client-core": {
166
+ "optional": true
167
+ },
168
+ "@tanstack/start-server-core": {
169
+ "optional": true
170
+ },
163
171
  "astro": {
164
172
  "optional": true
165
173
  },