katanakit-js 3.2.2 → 4.0.3

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.
Files changed (193) hide show
  1. package/README.md +453 -326
  2. package/dist/adapters/angular/fetch.d.ts +37 -0
  3. package/dist/adapters/angular/fetch.d.ts.map +1 -0
  4. package/dist/adapters/angular/fetch.js +67 -0
  5. package/dist/adapters/angular/fetch.js.map +1 -0
  6. package/dist/adapters/angular/index.d.ts +5 -0
  7. package/dist/adapters/angular/index.d.ts.map +1 -0
  8. package/dist/adapters/angular/index.js +5 -0
  9. package/dist/adapters/angular/index.js.map +1 -0
  10. package/dist/adapters/angular/query.d.ts +90 -0
  11. package/dist/adapters/angular/query.d.ts.map +1 -0
  12. package/dist/adapters/angular/query.js +103 -0
  13. package/dist/adapters/angular/query.js.map +1 -0
  14. package/dist/adapters/angular/watch.d.ts +34 -0
  15. package/dist/adapters/angular/watch.d.ts.map +1 -0
  16. package/dist/adapters/angular/watch.js +38 -0
  17. package/dist/adapters/angular/watch.js.map +1 -0
  18. package/dist/adapters/assistant/assistant.service.js +1 -1
  19. package/dist/adapters/assistant/assistant.service.js.map +1 -1
  20. package/dist/adapters/astro/astro.service.d.ts +0 -1
  21. package/dist/adapters/astro/astro.service.d.ts.map +1 -1
  22. package/dist/adapters/astro/astro.service.js +0 -1
  23. package/dist/adapters/astro/astro.service.js.map +1 -1
  24. package/dist/adapters/bun/dummyjson.service.d.ts +88 -0
  25. package/dist/adapters/bun/dummyjson.service.d.ts.map +1 -0
  26. package/dist/adapters/bun/dummyjson.service.js +149 -0
  27. package/dist/adapters/bun/dummyjson.service.js.map +1 -0
  28. package/dist/adapters/bun/index.d.ts +5 -0
  29. package/dist/adapters/bun/index.d.ts.map +1 -0
  30. package/dist/adapters/bun/index.js +5 -0
  31. package/dist/adapters/bun/index.js.map +1 -0
  32. package/dist/adapters/bun/main.d.ts +2 -0
  33. package/dist/adapters/bun/main.d.ts.map +1 -0
  34. package/dist/adapters/bun/main.js +6 -0
  35. package/dist/adapters/bun/main.js.map +1 -0
  36. package/dist/adapters/bun/routes.d.ts +62 -0
  37. package/dist/adapters/bun/routes.d.ts.map +1 -0
  38. package/dist/adapters/bun/routes.js +60 -0
  39. package/dist/adapters/bun/routes.js.map +1 -0
  40. package/dist/adapters/bun/seed.d.ts +65 -0
  41. package/dist/adapters/bun/seed.d.ts.map +1 -0
  42. package/dist/adapters/bun/seed.js +117 -0
  43. package/dist/adapters/bun/seed.js.map +1 -0
  44. package/dist/adapters/bun/server.d.ts +53 -0
  45. package/dist/adapters/bun/server.d.ts.map +1 -0
  46. package/dist/adapters/bun/server.js +64 -0
  47. package/dist/adapters/bun/server.js.map +1 -0
  48. package/dist/adapters/express/app.d.ts +2 -1
  49. package/dist/adapters/express/app.d.ts.map +1 -1
  50. package/dist/adapters/express/app.js +14 -2
  51. package/dist/adapters/express/app.js.map +1 -1
  52. package/dist/adapters/express/products.controller.d.ts.map +1 -1
  53. package/dist/adapters/express/products.controller.js +3 -1
  54. package/dist/adapters/express/products.controller.js.map +1 -1
  55. package/dist/adapters/express/server.js +2 -2
  56. package/dist/adapters/express/server.js.map +1 -1
  57. package/dist/adapters/notion/notion.service.d.ts +4 -2
  58. package/dist/adapters/notion/notion.service.d.ts.map +1 -1
  59. package/dist/adapters/notion/notion.service.js +19 -5
  60. package/dist/adapters/notion/notion.service.js.map +1 -1
  61. package/dist/adapters/react/fetch.d.ts +44 -0
  62. package/dist/adapters/react/fetch.d.ts.map +1 -0
  63. package/dist/adapters/react/fetch.js +74 -0
  64. package/dist/adapters/react/fetch.js.map +1 -0
  65. package/dist/adapters/react/index.d.ts +5 -0
  66. package/dist/adapters/react/index.d.ts.map +1 -0
  67. package/dist/adapters/react/index.js +5 -0
  68. package/dist/adapters/react/index.js.map +1 -0
  69. package/dist/adapters/react/query.d.ts +59 -0
  70. package/dist/adapters/react/query.d.ts.map +1 -0
  71. package/dist/adapters/react/query.js +86 -0
  72. package/dist/adapters/react/query.js.map +1 -0
  73. package/dist/adapters/react/watch.d.ts +37 -0
  74. package/dist/adapters/react/watch.d.ts.map +1 -0
  75. package/dist/adapters/react/watch.js +39 -0
  76. package/dist/adapters/react/watch.js.map +1 -0
  77. package/dist/adapters/solid/fetch.d.ts +43 -0
  78. package/dist/adapters/solid/fetch.d.ts.map +1 -0
  79. package/dist/adapters/solid/fetch.js +72 -0
  80. package/dist/adapters/solid/fetch.js.map +1 -0
  81. package/dist/adapters/solid/index.d.ts +5 -0
  82. package/dist/adapters/solid/index.d.ts.map +1 -0
  83. package/dist/adapters/solid/index.js +5 -0
  84. package/dist/adapters/solid/index.js.map +1 -0
  85. package/dist/adapters/solid/query.d.ts +60 -0
  86. package/dist/adapters/solid/query.d.ts.map +1 -0
  87. package/dist/adapters/solid/query.js +88 -0
  88. package/dist/adapters/solid/query.js.map +1 -0
  89. package/dist/adapters/solid/watch.d.ts +32 -0
  90. package/dist/adapters/solid/watch.d.ts.map +1 -0
  91. package/dist/adapters/solid/watch.js +36 -0
  92. package/dist/adapters/solid/watch.js.map +1 -0
  93. package/dist/adapters/svelte/fetch.d.ts +40 -0
  94. package/dist/adapters/svelte/fetch.d.ts.map +1 -0
  95. package/dist/adapters/svelte/fetch.js +75 -0
  96. package/dist/adapters/svelte/fetch.js.map +1 -0
  97. package/dist/adapters/svelte/index.d.ts +5 -0
  98. package/dist/adapters/svelte/index.d.ts.map +1 -0
  99. package/dist/adapters/svelte/index.js +5 -0
  100. package/dist/adapters/svelte/index.js.map +1 -0
  101. package/dist/adapters/svelte/query.d.ts +60 -0
  102. package/dist/adapters/svelte/query.d.ts.map +1 -0
  103. package/dist/adapters/svelte/query.js +78 -0
  104. package/dist/adapters/svelte/query.js.map +1 -0
  105. package/dist/adapters/svelte/watch.d.ts +32 -0
  106. package/dist/adapters/svelte/watch.d.ts.map +1 -0
  107. package/dist/adapters/svelte/watch.js +41 -0
  108. package/dist/adapters/svelte/watch.js.map +1 -0
  109. package/dist/adapters/telegram/telegram.service.js +1 -1
  110. package/dist/adapters/telegram/telegram.service.js.map +1 -1
  111. package/dist/adapters/vanilla/index.d.ts +3 -0
  112. package/dist/adapters/vanilla/index.d.ts.map +1 -0
  113. package/dist/adapters/vanilla/index.js +3 -0
  114. package/dist/adapters/vanilla/index.js.map +1 -0
  115. package/dist/adapters/vanilla/query.d.ts +69 -0
  116. package/dist/adapters/vanilla/query.d.ts.map +1 -0
  117. package/dist/adapters/vanilla/query.js +76 -0
  118. package/dist/adapters/vanilla/query.js.map +1 -0
  119. package/dist/adapters/vue/index.d.ts +1 -0
  120. package/dist/adapters/vue/index.d.ts.map +1 -1
  121. package/dist/adapters/vue/index.js +1 -0
  122. package/dist/adapters/vue/index.js.map +1 -1
  123. package/dist/adapters/vue/query.d.ts +35 -87
  124. package/dist/adapters/vue/query.d.ts.map +1 -1
  125. package/dist/adapters/vue/query.js +57 -168
  126. package/dist/adapters/vue/query.js.map +1 -1
  127. package/dist/adapters/vue/vue.service.d.ts +9 -5
  128. package/dist/adapters/vue/vue.service.d.ts.map +1 -1
  129. package/dist/adapters/vue/vue.service.js +7 -3
  130. package/dist/adapters/vue/vue.service.js.map +1 -1
  131. package/dist/adapters/whatsapp/whatsapp.service.js +1 -1
  132. package/dist/adapters/whatsapp/whatsapp.service.js.map +1 -1
  133. package/dist/adapters/wordpress/wordpress.service.d.ts +2 -1
  134. package/dist/adapters/wordpress/wordpress.service.d.ts.map +1 -1
  135. package/dist/adapters/wordpress/wordpress.service.js +10 -5
  136. package/dist/adapters/wordpress/wordpress.service.js.map +1 -1
  137. package/dist/core/services/agent.service.d.ts.map +1 -1
  138. package/dist/core/services/agent.service.js +9 -1
  139. package/dist/core/services/agent.service.js.map +1 -1
  140. package/dist/core/services/dates.service.d.ts.map +1 -1
  141. package/dist/core/services/dates.service.js +2 -1
  142. package/dist/core/services/dates.service.js.map +1 -1
  143. package/dist/core/services/error.service.d.ts +1 -1
  144. package/dist/core/services/error.service.js +1 -1
  145. package/dist/core/services/http.service.d.ts +26 -5
  146. package/dist/core/services/http.service.d.ts.map +1 -1
  147. package/dist/core/services/http.service.js +30 -6
  148. package/dist/core/services/http.service.js.map +1 -1
  149. package/dist/core/services/logger.service.d.ts +10 -67
  150. package/dist/core/services/logger.service.d.ts.map +1 -1
  151. package/dist/core/services/logger.service.js +13 -139
  152. package/dist/core/services/logger.service.js.map +1 -1
  153. package/dist/core/services/query.service.d.ts +87 -130
  154. package/dist/core/services/query.service.d.ts.map +1 -1
  155. package/dist/core/services/query.service.js +99 -366
  156. package/dist/core/services/query.service.js.map +1 -1
  157. package/dist/core/services/reactive.service.d.ts.map +1 -1
  158. package/dist/core/services/reactive.service.js +7 -10
  159. package/dist/core/services/reactive.service.js.map +1 -1
  160. package/dist/core/services/timing.service.js +8 -8
  161. package/dist/core/services/timing.service.js.map +1 -1
  162. package/dist/core/services/utils.service.d.ts.map +1 -1
  163. package/dist/core/services/utils.service.js +3 -0
  164. package/dist/core/services/utils.service.js.map +1 -1
  165. package/dist/index.d.ts +0 -1
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.js +0 -1
  168. package/dist/index.js.map +1 -1
  169. package/dist/infrastructure/dom/dom.service.d.ts.map +1 -1
  170. package/dist/infrastructure/dom/dom.service.js +18 -4
  171. package/dist/infrastructure/dom/dom.service.js.map +1 -1
  172. package/dist/infrastructure/observer/observer.service.js +2 -2
  173. package/dist/infrastructure/observer/observer.service.js.map +1 -1
  174. package/dist/infrastructure/sensors/sensors.service.js +6 -6
  175. package/dist/infrastructure/sensors/sensors.service.js.map +1 -1
  176. package/dist/infrastructure/storage/storage.service.d.ts.map +1 -1
  177. package/dist/infrastructure/storage/storage.service.js +12 -2
  178. package/dist/infrastructure/storage/storage.service.js.map +1 -1
  179. package/dist/prisma/assistant.store.d.ts +3 -0
  180. package/dist/prisma/assistant.store.d.ts.map +1 -1
  181. package/dist/prisma/assistant.store.js +12 -6
  182. package/dist/prisma/assistant.store.js.map +1 -1
  183. package/dist/prisma/use-prisma.d.ts +5 -4
  184. package/dist/prisma/use-prisma.d.ts.map +1 -1
  185. package/dist/prisma/use-prisma.js +5 -4
  186. package/dist/prisma/use-prisma.js.map +1 -1
  187. package/dist/types/index.d.ts +2 -6
  188. package/dist/types/index.d.ts.map +1 -1
  189. package/package.json +251 -185
  190. package/dist/prisma/db.d.ts +0 -4
  191. package/dist/prisma/db.d.ts.map +0 -1
  192. package/dist/prisma/db.js +0 -12
  193. package/dist/prisma/db.js.map +0 -1
package/README.md CHANGED
@@ -9,7 +9,7 @@ npm install katanakit-js
9
9
  # or
10
10
  bun add katanakit-js
11
11
  # or
12
- pnpm add katanakit-js
12
+ bun add katanakit-js
13
13
  ```
14
14
 
15
15
  ### CDN (ESM)
@@ -18,16 +18,16 @@ In the browser, use jsDelivr **`/+esm`** so named exports and dependencies resol
18
18
 
19
19
  ```html
20
20
  <script type="module">
21
- import { useLogger, useGetApi, useInitApis } from "https://cdn.jsdelivr.net/npm/katanakit-js/+esm";
22
- useLogger("ready");
21
+ import { useLogger, useGetApi, useInitApis } from "https://cdn.jsdelivr.net/npm/katanakit-js/+esm";
22
+ useLogger("ready");
23
23
  </script>
24
24
  ```
25
25
 
26
- | CDN | URL |
27
- |-----|-----|
28
- | **jsDelivr `/+esm`** (recommended) | `https://cdn.jsdelivr.net/npm/katanakit-js/+esm` |
29
- | **esm.sh** | `https://esm.sh/katanakit-js` |
30
- | **Raw ESM file** | `https://cdn.jsdelivr.net/npm/katanakit-js/dist/index.js` (needs bundler or import map) |
26
+ | CDN | URL |
27
+ | ---------------------------------- | --------------------------------------------------------------------------------------- |
28
+ | **jsDelivr `/+esm`** (recommended) | `https://cdn.jsdelivr.net/npm/katanakit-js/+esm` |
29
+ | **esm.sh** | `https://esm.sh/katanakit-js` |
30
+ | **Raw ESM file** | `https://cdn.jsdelivr.net/npm/katanakit-js/dist/index.js` (needs bundler or import map) |
31
31
 
32
32
  Pin a version in production (e.g. `@2.14.2/+esm`). There is no IIFE/UMD build.
33
33
 
@@ -40,21 +40,21 @@ useLogger("boot");
40
40
 
41
41
  // Register your APIs once
42
42
  useInitApis({
43
- pokeapi: {
44
- baseUri: "https://pokeapi.co/api/v2",
45
- endpoints: { pokemonById: "/pokemon/:id/" },
46
- },
43
+ pokeapi: {
44
+ baseUri: "https://pokeapi.co/api/v2",
45
+ endpoints: { pokemonById: "/pokemon/:id/" },
46
+ },
47
47
  });
48
48
 
49
49
  // Fetch with Safe Result — no try/catch needed for HTTP failures
50
50
  const result = await useGetApi<{ name: string }>("pokeapi", "pokemonById", {
51
- params: { id: 25 },
51
+ params: { id: 25 },
52
52
  });
53
53
 
54
54
  if (result.ok) {
55
- console.log(result.data.name); // "pikachu"
55
+ console.log(result.data.name); // "pikachu"
56
56
  } else {
57
- console.error(result.error.message);
57
+ console.error(result.error.message);
58
58
  }
59
59
  ```
60
60
 
@@ -70,28 +70,28 @@ and returns a **Safe Result** (`{ ok, data, error }`) that never throws on HTTP
70
70
  import { useInitApis } from "katanakit-js";
71
71
 
72
72
  useInitApis({
73
- // A public REST API
74
- jsonplaceholder: {
75
- baseUri: "https://jsonplaceholder.typicode.com",
76
- endpoints: {
77
- posts: "/posts",
78
- postById: "/posts/:id",
79
- },
80
- // Applied automatically to specific endpoints (overridable per-call)
81
- defaultQueryParams: {
82
- posts: { _limit: 10 },
83
- },
84
- },
85
-
86
- // Your own backend
87
- myApi: {
88
- baseUri: "https://api.myapp.com/v1",
89
- endpoints: {
90
- users: "/users",
91
- userById: "/users/:id",
92
- createUser: "/users",
93
- },
94
- },
73
+ // A public REST API
74
+ jsonplaceholder: {
75
+ baseUri: "https://jsonplaceholder.typicode.com",
76
+ endpoints: {
77
+ posts: "/posts",
78
+ postById: "/posts/:id",
79
+ },
80
+ // Applied automatically to specific endpoints (overridable per-call)
81
+ defaultQueryParams: {
82
+ posts: { _limit: 10 },
83
+ },
84
+ },
85
+
86
+ // Your own backend
87
+ myApi: {
88
+ baseUri: "https://api.myapp.com/v1",
89
+ endpoints: {
90
+ users: "/users",
91
+ userById: "/users/:id",
92
+ createUser: "/users",
93
+ },
94
+ },
95
95
  });
96
96
  ```
97
97
 
@@ -106,13 +106,13 @@ if (list.ok) console.log(list.data);
106
106
 
107
107
  // Read by ID — :id is replaced by params
108
108
  const post = await useGetApi<{ title: string }>("jsonplaceholder", "postById", {
109
- params: { id: 1 },
109
+ params: { id: 1 },
110
110
  });
111
111
  if (post.ok) console.log(post.data.title);
112
112
 
113
113
  // Override default query params
114
114
  const filtered = await useGetApi("jsonplaceholder", "posts", {
115
- query: { _limit: 5, userId: 1 },
115
+ query: { _limit: 5, userId: 1 },
116
116
  });
117
117
  ```
118
118
 
@@ -123,8 +123,8 @@ import { usePost, usePut, usePatch, useDelete } from "katanakit-js";
123
123
 
124
124
  // POST — body is auto-serialized to JSON
125
125
  const created = await usePost<{ id: number }>("myApi", "createUser", {
126
- name: "Alice",
127
- email: "alice@example.com",
126
+ name: "Alice",
127
+ email: "alice@example.com",
128
128
  });
129
129
 
130
130
  // PUT — full replacement (body + path params)
@@ -143,10 +143,10 @@ There's no global interceptor — pass `headers` directly. This keeps things exp
143
143
 
144
144
  ```ts
145
145
  const result = await useFetch("myApi", "users", {
146
- method: "GET",
147
- headers: {
148
- Authorization: `Bearer ${getToken()}`,
149
- },
146
+ method: "GET",
147
+ headers: {
148
+ Authorization: `Bearer ${getToken()}`,
149
+ },
150
150
  });
151
151
  ```
152
152
 
@@ -158,27 +158,82 @@ Every fetch returns `{ ok, data, error, status, url }`. No try/catch needed for
158
158
  const result = await useGetApi("myApi", "userById", { params: { id: 99999 } });
159
159
 
160
160
  if (result.ok) {
161
- // result.data is typed
162
- console.log(result.data);
161
+ // result.data is typed
162
+ console.log(result.data);
163
163
  } else {
164
- // result.error is always structured
165
- console.log(result.error.status); // 404
166
- console.log(result.error.message); // "HTTP Error: Not Found"
167
- console.log(result.error.details); // parsed response body (if any)
168
- console.log(result.url); // the URL that was called
164
+ // result.error is always structured
165
+ console.log(result.error.status); // 404
166
+ console.log(result.error.message); // "HTTP Error: Not Found"
167
+ console.log(result.error.details); // parsed response body (if any)
168
+ console.log(result.url); // the URL that was called
169
169
  }
170
170
  ```
171
171
 
172
172
  ### 6. Build URLs without fetching
173
173
 
174
+ `useBuildUrl` constructs a full URL from your registered APIs **without making
175
+ a request**. Use it to generate links, image sources, or pass URLs to libraries
176
+ that handle their own fetching.
177
+
178
+ #### Generate navigation links
179
+
174
180
  ```ts
175
181
  import { useBuildUrl } from "katanakit-js";
176
182
 
177
- const url = useBuildUrl("jsonplaceholder", "postById", {
178
- params: { id: 7 },
179
- query: { _limit: 3 },
183
+ // Build a download link
184
+ const downloadUrl = useBuildUrl("myApi", "export", {
185
+ query: { format: "pdf", lang: "es" },
186
+ });
187
+ // → "https://api.myapp.com/v1/export?format=pdf&lang=es"
188
+
189
+ // Use in a template
190
+ <a href={downloadUrl}>Download PDF</a>
191
+ ```
192
+
193
+ #### Generate image/file URLs
194
+
195
+ ```ts
196
+ // Build an image URL for <img src>
197
+ const avatarUrl = useBuildUrl("myApi", "userAvatar", {
198
+ params: { id: 42 },
199
+ query: { size: "large" },
200
+ });
201
+ // → "https://api.myapp.com/v1/users/42/avatar?size=large"
202
+
203
+ <img src={avatarUrl} alt="User avatar" />
204
+ ```
205
+
206
+ #### Pass to third-party libraries
207
+
208
+ ```ts
209
+ // Charts, maps, analytics — libraries that fetch their own data
210
+ const chartDataUrl = useBuildUrl("myApi", "analytics", {
211
+ query: { range: "7d", metric: "visits" },
212
+ });
213
+ new Chart(canvas, { data: chartDataUrl });
214
+ ```
215
+
216
+ #### Debug before fetching
217
+
218
+ ```ts
219
+ // See the full URL before making the request
220
+ console.log(
221
+ "Will fetch:",
222
+ useBuildUrl("myApi", "users", {
223
+ query: { page: 1, per_page: 20 },
224
+ }),
225
+ );
226
+ // → "https://api.myapp.com/v1/users?page=1&per_page=20"
227
+ ```
228
+
229
+ #### SSR: construct URLs server-side
230
+
231
+ ```ts
232
+ // In Astro, Next.js, or Nuxt server code
233
+ const apiUrl = useBuildUrl("notion", "database", {
234
+ params: { id: "db-123" },
180
235
  });
181
- // "https://jsonplaceholder.typicode.com/posts/7?_limit=3"
236
+ // Pass to client component or pre-render
182
237
  ```
183
238
 
184
239
  ### 7. FormData and raw bodies
@@ -201,43 +256,42 @@ against a real API (JSONPlaceholder).
201
256
 
202
257
  ## QueryClient — Cached Data Fetching
203
258
 
204
- The QueryClient is a data-fetching and caching layer built on the reactive kernel.
205
- It integrates with the API manager — your `queryFn` typically calls `useGetApi` or
206
- `useFetch` and returns the Safe Result.
259
+ The query layer is powered by **TanStack Query Core**, bundled as a dependency —
260
+ install `katanakit-js` and the engine comes with it. KatanaKit re-exports the full
261
+ `@tanstack/query-core` API and adds framework bindings plus a bridge for the
262
+ Safe Result pattern.
207
263
 
208
264
  ```ts
209
- import { QueryClient, useQueryClient } from "katanakit-js";
265
+ import { useQueryClient, useInitQueryClient, useSafeQueryFn } from "katanakit-js";
210
266
  import { useGetApi } from "katanakit-js";
211
267
 
212
- // Initialize once (global singleton).
213
- const qc = useQueryClient();
214
-
215
- // Fetch with cache, stale-while-revalidate, retry, and dedup.
216
- const pokemon = await qc.fetchQuery<Pokemon>({
217
- queryKey: ["pokemon", 25],
218
- queryFn: () => useGetApi<Pokemon>("pokeapi", "pokemonById", { params: { id: 25 } }),
219
- staleTime: 60_000, // Cache is fresh for 60s.
220
- retry: 3, // Retry 3 times on failure.
221
- refetchOnWindowFocus: true,
268
+ // Configure the shared client once (defaults for every query).
269
+ useInitQueryClient({
270
+ defaultOptions: { queries: { staleTime: 60_000, retry: 2 } },
222
271
  });
272
+
273
+ // Access it anywhere to invalidate, prefetch, or set data.
274
+ const qc = useQueryClient();
275
+ await qc.invalidateQueries({ queryKey: ["pokemon"] });
223
276
  ```
224
277
 
225
278
  ### Vue composables
226
279
 
227
280
  ```vue
228
281
  <script setup>
229
- import { useQuery, useMutation, useQueryClient } from "katanakit-js/adapters/vue";
282
+ import { useQuery, useMutation, useQueryClient, useSafeQueryFn } from "katanakit-js/adapters/vue";
230
283
  import { useGetApi, usePost } from "katanakit-js";
231
284
 
232
285
  const qc = useQueryClient();
233
286
 
234
- const { data, isLoading, error, refetch } = useQuery({
235
- queryKey: () => ["users"],
236
- queryFn: () => useGetApi<User[]>("myApi", "users"),
287
+ const query = useQuery({
288
+ queryKey: ["users"],
289
+ queryFn: useSafeQueryFn(() => useGetApi<User[]>("myApi", "users")),
237
290
  staleTime: 30_000,
238
291
  });
292
+ // query.data, query.isPending, query.isFetching, query.error, query.status…
239
293
 
240
- const { mutate, isLoading: mutating } = useMutation({
294
+ const mutation = useMutation({
241
295
  mutationFn: (name: string) => usePost("myApi", "createUser", { name }),
242
296
  onSuccess: () => qc.invalidateQueries({ queryKey: ["users"] }),
243
297
  });
@@ -286,6 +340,27 @@ when you want sessions, persistence, and channels (REST, Telegram, WhatsApp). Fa
286
340
  follow the Safe Result pattern: they **never throw**. Check `result.ok` and read `result.data`
287
341
  or `result.error`.
288
342
 
343
+ ### When to use Kitt
344
+
345
+ Kitt is a **lightweight, zero-dependency** wrapper for OpenAI-compatible APIs.
346
+ Use it when you need:
347
+
348
+ - **One-shot chat** — ask a question, get an answer (`useChat`)
349
+ - **Tool-calling agents** — autonomous loops that read/write/run (`useRunAgent`)
350
+ - **Session-aware bots** — REST, Telegram, WhatsApp channels (`useReply`)
351
+
352
+ ### When to use alternatives
353
+
354
+ | Need | Use instead |
355
+ | ------------------------- | --------------------------------------------- |
356
+ | Streaming token-by-token | [Vercel AI SDK](https://sdk.vercel.ai) |
357
+ | RAG with embeddings | [LangChain.js](https://js.langchain.com) |
358
+ | Multi-agent orchestration | [CrewAI](https://github.com/crewAIInc/crewAI) |
359
+ | Full chatbot framework | [Botpress](https://botpress.com) |
360
+
361
+ Kitt stays small (0 dependencies) because it does one thing well:
362
+ **chat completions with tool calls, using any OpenAI-compatible endpoint.**
363
+
289
364
  ### Low-level API
290
365
 
291
366
  ```ts
@@ -297,21 +372,21 @@ useInitAgent({ model: "qwen3.8-max" });
297
372
 
298
373
  // Assistant — single-shot review / question.
299
374
  const review = await useChat([
300
- { role: "user", content: "Summarize the benefits of solar energy in three bullet points." },
375
+ { role: "user", content: "Summarize the benefits of solar energy in three bullet points." },
301
376
  ]);
302
377
  if (review.ok) console.log(review.data);
303
378
 
304
379
  // Agent — autonomous tool-calling loop that can act (read/write/run).
305
380
  const result = await useRunAgent("Fix the type errors in src/", {
306
- tools: [
307
- {
308
- name: "readFile",
309
- description: "Returns file contents",
310
- parameters: { type: "object", properties: { path: { type: "string" } } },
311
- execute: ({ path }) => fs.readFile(path, "utf8"),
312
- },
313
- ],
314
- maxSteps: 12,
381
+ tools: [
382
+ {
383
+ name: "readFile",
384
+ description: "Returns file contents",
385
+ parameters: { type: "object", properties: { path: { type: "string" } } },
386
+ execute: ({ path }) => fs.readFile(path, "utf8"),
387
+ },
388
+ ],
389
+ maxSteps: 12,
315
390
  });
316
391
  ```
317
392
 
@@ -331,20 +406,20 @@ import "dotenv/config";
331
406
  import { useInitAssistant, useReply } from "katanakit-js";
332
407
 
333
408
  useInitAssistant({
334
- // apiKey — process.env.DASHSCOPE_API_KEY
335
- // baseUrl — KITT_BASE_URL
336
- // model — "qwen3.8-max"
337
- // systemPrompt — KITT_SYSTEM_PROMPT
338
- // store — in-memory (useCreateMemoryStore)
339
- // tools — AiTool[]
340
- // maxSteps — number
409
+ // apiKey — process.env.DASHSCOPE_API_KEY
410
+ // baseUrl — KITT_BASE_URL
411
+ // model — "qwen3.8-max"
412
+ // systemPrompt — KITT_SYSTEM_PROMPT
413
+ // store — in-memory (useCreateMemoryStore)
414
+ // tools — AiTool[]
415
+ // maxSteps — number
341
416
  });
342
417
 
343
418
  const result = await useReply(undefined, "What are your hours?");
344
419
  if (result.ok) {
345
- console.log(result.data.reply, result.data.sessionId);
420
+ console.log(result.data.reply, result.data.sessionId);
346
421
  } else {
347
- console.error(result.error.message);
422
+ console.error(result.error.message);
348
423
  }
349
424
  ```
350
425
 
@@ -353,12 +428,12 @@ if (result.ok) {
353
428
 
354
429
  Also on the main barrel:
355
430
 
356
- | Helper | Signature |
357
- |--------|-----------|
358
- | `useCreateSession` | `(channel?) => Promise<string>` |
359
- | `useGetHistory` | `(sessionId) => Promise<AiMessage[]>` |
360
- | `useResetSession` | `(sessionId) => Promise<void>` |
361
- | `useCreateMemoryStore` | `() => ConversationStore` |
431
+ | Helper | Signature |
432
+ | ---------------------- | ------------------------------------- |
433
+ | `useCreateSession` | `(channel?) => Promise<string>` |
434
+ | `useGetHistory` | `(sessionId) => Promise<AiMessage[]>` |
435
+ | `useResetSession` | `(sessionId) => Promise<void>` |
436
+ | `useCreateMemoryStore` | `() => ConversationStore` |
362
437
 
363
438
  Then start a channel (see below). Copy keys from [`.env.example`](https://github.com/senseikatana/katanakit-js/blob/main/.env.example):
364
439
 
@@ -377,12 +452,12 @@ DATABASE_URL=
377
452
 
378
453
  ### Run standalone (this repo)
379
454
 
380
- | Script | Starts |
381
- |--------|--------|
382
- | `pnpm assistant:dev` | REST assistant. Uses Prisma when `DATABASE_URL` is set. |
383
- | `pnpm telegram:dev` | Telegram long polling. Requires `TELEGRAM_BOT_TOKEN`. |
384
- | `pnpm whatsapp:dev` | WhatsApp webhook. Requires `WHATSAPP_*`. |
385
- | `pnpm assistant:demo` | Demo in `examples/assistant/`. Set `KITT_CHANNEL=rest\|telegram\|whatsapp`. |
455
+ | Script | Starts |
456
+ | ------------------------ | --------------------------------------------------------------------------- |
457
+ | `bun run assistant:dev` | REST assistant. Uses Prisma when `DATABASE_URL` is set. |
458
+ | `bun run telegram:dev` | Telegram long polling. Requires `TELEGRAM_BOT_TOKEN`. |
459
+ | `bun run whatsapp:dev` | WhatsApp webhook. Requires `WHATSAPP_*`. |
460
+ | `bun run assistant:demo` | Demo in `examples/assistant/`. Set `KITT_CHANNEL=rest\|telegram\|whatsapp`. |
386
461
 
387
462
  ### REST
388
463
 
@@ -393,21 +468,21 @@ useStartAssistant(); // port?, host?, mountPath = "/assistant", options?
393
468
  // or mount useCreateAssistantRouter() on an existing Express app
394
469
  ```
395
470
 
396
- | Method | Path | Body | Response |
397
- |--------|------|------|----------|
398
- | `POST` | `/assistant/chat` | `{ sessionId?, message }` | `{ ok, data: { reply, sessionId }, error }` |
399
- | `GET` | `/assistant/sessions/:id` | — | `{ sessionId, messages }` or `404` |
400
- | `DELETE` | `/assistant/sessions/:id` | — | `204` or `404` |
471
+ | Method | Path | Body | Response |
472
+ | -------- | ------------------------- | ------------------------- | ------------------------------------------- |
473
+ | `POST` | `/assistant/chat` | `{ sessionId?, message }` | `{ ok, data: { reply, sessionId }, error }` |
474
+ | `GET` | `/assistant/sessions/:id` | — | `{ sessionId, messages }` or `404` |
475
+ | `DELETE` | `/assistant/sessions/:id` | — | `204` or `404` |
401
476
 
402
477
  The endpoints are **public by default**. In production pass an auth guard (applied to every
403
478
  route). Unknown session ids return `404`, and `useReply` rejects a `sessionId` that does not exist.
404
479
 
405
480
  ```ts
406
481
  useStartAssistant(3000, "localhost", "/assistant", {
407
- guard: (req, res, next) =>
408
- req.header("authorization") === `Bearer ${process.env.ASSISTANT_API_KEY}`
409
- ? next()
410
- : res.status(401).end(),
482
+ guard: (req, res, next) =>
483
+ req.header("authorization") === `Bearer ${process.env.ASSISTANT_API_KEY}`
484
+ ? next()
485
+ : res.status(401).end(),
411
486
  });
412
487
  ```
413
488
 
@@ -422,9 +497,12 @@ curl -X POST http://localhost:3000/assistant/chat \
422
497
  POST `/chat` and POST `/whatsapp/webhook` are rate-limited by default (20 req/min and 60 req/min per IP). Override via the router options:
423
498
 
424
499
  ```ts
425
- app.use("/assistant", useCreateAssistantRouter({
426
- rateLimit: rateLimit({ windowMs: 60_000, max: 30 }),
427
- }));
500
+ app.use(
501
+ "/assistant",
502
+ useCreateAssistantRouter({
503
+ rateLimit: rateLimit({ windowMs: 60_000, max: 30 }),
504
+ }),
505
+ );
428
506
  ```
429
507
 
430
508
  ### Telegram (BotFather)
@@ -442,7 +520,7 @@ await useStartTelegramPolling();
442
520
  2. Send `/newbot` — choose a name and a username.
443
521
  3. Copy the token.
444
522
  4. Set `TELEGRAM_BOT_TOKEN`.
445
- 5. Run `pnpm telegram:dev` (long polling, no public URL).
523
+ 5. Run `bun run telegram:dev` (long polling, no public URL).
446
524
  6. Optional webhook: expose HTTPS and call `useHandleTelegramUpdate(update)` on inbound updates.
447
525
 
448
526
  ### WhatsApp (Meta Cloud API)
@@ -453,10 +531,10 @@ Webhook: `GET` / `POST` `/whatsapp/webhook`. Session ids are `wa:<phone>`.
453
531
  import { useInitWhatsApp, useStartWhatsApp } from "katanakit-js/adapters/whatsapp";
454
532
 
455
533
  useInitWhatsApp({
456
- token: process.env.WHATSAPP_TOKEN,
457
- phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID,
458
- verifyToken: process.env.WHATSAPP_VERIFY_TOKEN,
459
- appSecret: process.env.WHATSAPP_APP_SECRET,
534
+ token: process.env.WHATSAPP_TOKEN,
535
+ phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID,
536
+ verifyToken: process.env.WHATSAPP_VERIFY_TOKEN,
537
+ appSecret: process.env.WHATSAPP_APP_SECRET,
460
538
  });
461
539
  useStartWhatsApp();
462
540
  ```
@@ -465,7 +543,7 @@ useStartWhatsApp();
465
543
  2. Add the WhatsApp product.
466
544
  3. Copy the temporary or permanent token, the Phone number ID, and the **App Secret**.
467
545
  4. Set `WHATSAPP_TOKEN`, `WHATSAPP_PHONE_NUMBER_ID`, `WHATSAPP_VERIFY_TOKEN`, and `WHATSAPP_APP_SECRET`.
468
- 5. Run `pnpm whatsapp:dev`.
546
+ 5. Run `bun run whatsapp:dev`.
469
547
  6. Expose public HTTPS (Cloudflare Tunnel or ngrok) to `GET`/`POST` `/whatsapp/webhook`.
470
548
  7. In Meta, set the webhook URL and verify token; subscribe to `messages`.
471
549
 
@@ -500,7 +578,7 @@ owns the database, run `prisma contract emit` then `prisma db init` in that app.
500
578
  `readFile` on `knowledge-base.md` and `saveNote` to `notes.jsonl`.
501
579
 
502
580
  ```bash
503
- pnpm assistant:demo
581
+ bun run assistant:demo
504
582
  # KITT_CHANNEL=rest|telegram|whatsapp
505
583
  ```
506
584
 
@@ -540,39 +618,39 @@ const result = await useGetApi("pokeapi", "pokemonById", { params: { id: 25 } })
540
618
 
541
619
  ```ts
542
620
  import { useLogger, useInitApis, useGetApi } from "katanakit-js";
543
- import { useKatanaFetch } from "katanakit-js/adapters/vue"; // Vue only
544
- import { useUnwrap } from "katanakit-js/adapters/nuxt"; // Nuxt only
621
+ import { useRequest } from "katanakit-js/adapters/vue"; // Vue only
622
+ import { useUnwrap } from "katanakit-js/adapters/nuxt"; // Nuxt only
545
623
  ```
546
624
 
547
625
  ```html
548
626
  <!-- vanilla -->
549
627
  <script type="module">
550
- import { useLogger } from "https://cdn.jsdelivr.net/npm/katanakit-js/+esm";
628
+ import { useLogger } from "https://cdn.jsdelivr.net/npm/katanakit-js/+esm";
551
629
  </script>
552
630
  ```
553
631
 
554
- See [Getting Started](https://senseikatana.com/katanakit-js/docs/guides/getting-started/) for full recipes.
632
+ See [Getting Started](https://docs.senseikatana.com/docs/guides/getting-started/) for full recipes.
555
633
 
556
634
  ## Framework Adapters
557
635
 
558
- | Adapter | Import | Description |
559
- |---------|--------|-------------|
560
- | **Express** | `katanakit-js/adapters/express` | Reference server with CORS and hardened headers |
561
- | **Nuxt** | `katanakit-js/adapters/nuxt` | `useUnwrap`, `useSafeResponse`, `useEventResponse` |
562
- | **Vue** | `katanakit-js/adapters/vue` | `useKatanaFetch` composable with reactivity |
563
- | **Astro** | `katanakit-js` or `katanakit-js/adapters/astro` | `AstroService`, `RssService` |
564
- | **Assistant** | `katanakit-js/adapters/assistant` | REST digital assistant (`useStartAssistant`) |
565
- | **Telegram** | `katanakit-js/adapters/telegram` | BotFather bot (`useInitTelegram`, `useStartTelegramPolling`) |
566
- | **WhatsApp** | `katanakit-js/adapters/whatsapp` | Meta Cloud API (`useInitWhatsApp`, `useStartWhatsApp`) |
636
+ | Adapter | Import | Description |
637
+ | ------------- | ----------------------------------------------- | ------------------------------------------------------------ |
638
+ | **Express** | `katanakit-js/adapters/express` | Reference server with CORS and hardened headers |
639
+ | **Nuxt** | `katanakit-js/adapters/nuxt` | `useUnwrap`, `useSafeResponse`, `useEventResponse` |
640
+ | **Vue** | `katanakit-js/adapters/vue` | `useRequest` composable with reactivity |
641
+ | **Astro** | `katanakit-js` or `katanakit-js/adapters/astro` | `AstroService`, `RssService` |
642
+ | **Assistant** | `katanakit-js/adapters/assistant` | REST digital assistant (`useStartAssistant`) |
643
+ | **Telegram** | `katanakit-js/adapters/telegram` | BotFather bot (`useInitTelegram`, `useStartTelegramPolling`) |
644
+ | **WhatsApp** | `katanakit-js/adapters/whatsapp` | Meta Cloud API (`useInitWhatsApp`, `useStartWhatsApp`) |
567
645
 
568
646
  ## REST API Adapters
569
647
 
570
648
  Typed adapters for popular REST APIs with auth, pagination helpers, and full TypeScript types.
571
649
  All functions return `FetchResult<T>` — the same Safe Result pattern used by the HTTP client.
572
650
 
573
- | Adapter | Import | Description |
574
- |---------|--------|-------------|
575
- | **Notion** | `katanakit-js/adapters/notion` | Pages, databases, blocks, search with cursor pagination |
651
+ | Adapter | Import | Description |
652
+ | ------------- | --------------------------------- | ----------------------------------------------------------------- |
653
+ | **Notion** | `katanakit-js/adapters/notion` | Pages, databases, blocks, search with cursor pagination |
576
654
  | **WordPress** | `katanakit-js/adapters/wordpress` | Posts, pages, media, categories, tags, comments, users, batch ops |
577
655
 
578
656
  ### Notion
@@ -593,10 +671,10 @@ useInitNotion({ token: process.env.NOTION_TOKEN });
593
671
 
594
672
  ```ts
595
673
  import {
596
- useNotionGetPage,
597
- useNotionCreatePage,
598
- useNotionUpdatePage,
599
- useNotionArchivePage,
674
+ useNotionGetPage,
675
+ useNotionCreatePage,
676
+ useNotionUpdatePage,
677
+ useNotionArchivePage,
600
678
  } from "katanakit-js/adapters/notion";
601
679
 
602
680
  // Get a single page with all its properties
@@ -605,24 +683,24 @@ if (page.ok) console.log(page.data.properties);
605
683
 
606
684
  // Create a page inside a database
607
685
  const created = await useNotionCreatePage(
608
- { type: "database_id", database_id: "db-id" },
609
- {
610
- Name: { title: [{ type: "text", text: { content: "My Task" } }] },
611
- Status: { select: { name: "To Do" } },
612
- },
686
+ { type: "database_id", database_id: "db-id" },
687
+ {
688
+ Name: { title: [{ type: "text", text: { content: "My Task" } }] },
689
+ Status: { select: { name: "To Do" } },
690
+ },
613
691
  );
614
692
 
615
693
  // Create a child page with content blocks
616
694
  const child = await useNotionCreatePage(
617
- { type: "page_id", page_id: "parent-id" },
618
- { title: { title: [{ type: "text", text: { content: "Child Page" } }] } },
619
- [{ type: "paragraph", paragraph: { rich_text: [{ type: "text", text: { content: "Hello!" } }] } }],
695
+ { type: "page_id", page_id: "parent-id" },
696
+ { title: { title: [{ type: "text", text: { content: "Child Page" } }] } },
697
+ [{ type: "paragraph", paragraph: { rich_text: [{ type: "text", text: { content: "Hello!" } }] } }],
620
698
  );
621
699
 
622
700
  // Update page properties (only changed fields)
623
701
  await useNotionUpdatePage("page-id", {
624
- Status: { select: { name: "Done" } },
625
- DueDate: { date: { start: "2025-12-31" } },
702
+ Status: { select: { name: "Done" } },
703
+ DueDate: { date: { start: "2025-12-31" } },
626
704
  });
627
705
 
628
706
  // Archive (soft-delete) a page
@@ -633,26 +711,26 @@ await useNotionArchivePage("page-id");
633
711
 
634
712
  ```ts
635
713
  import {
636
- useNotionGetDatabase,
637
- useNotionQueryDatabase,
638
- useNotionCreateDatabase,
639
- useNotionUpdateDatabase,
640
- useNotionListAllDatabasePages,
714
+ useNotionGetDatabase,
715
+ useNotionQueryDatabase,
716
+ useNotionCreateDatabase,
717
+ useNotionUpdateDatabase,
718
+ useNotionListAllDatabasePages,
641
719
  } from "katanakit-js/adapters/notion";
642
720
 
643
721
  // Inspect database schema (property names, types, options)
644
722
  const schema = await useNotionGetDatabase("db-id");
645
723
  if (schema.ok) {
646
- Object.entries(schema.data.properties).forEach(([name, prop]) => {
647
- console.log(`${name}: ${prop.type}`);
648
- });
724
+ Object.entries(schema.data.properties).forEach(([name, prop]) => {
725
+ console.log(`${name}: ${prop.type}`);
726
+ });
649
727
  }
650
728
 
651
729
  // Query with filter + sort (single page of results)
652
730
  const page1 = await useNotionQueryDatabase("db-id", {
653
- filter: { property: "Status", select: { equals: "Published" } },
654
- sorts: [{ property: "Date", direction: "descending" }],
655
- page_size: 10,
731
+ filter: { property: "Status", select: { equals: "Published" } },
732
+ sorts: [{ property: "Date", direction: "descending" }],
733
+ page_size: 10,
656
734
  });
657
735
 
658
736
  // Get ALL pages (auto-pagination — handles cursors internally)
@@ -660,76 +738,74 @@ const all = await useNotionListAllDatabasePages("db-id");
660
738
 
661
739
  // With filter + sort
662
740
  const published = await useNotionListAllDatabasePages(
663
- "db-id",
664
- { property: "Status", select: { equals: "Published" } },
665
- [{ property: "Date", direction: "descending" }],
741
+ "db-id",
742
+ { property: "Status", select: { equals: "Published" } },
743
+ [{ property: "Date", direction: "descending" }],
666
744
  );
667
745
 
668
746
  // Create a new database
669
747
  await useNotionCreateDatabase(
670
- { type: "page_id", page_id: "parent-id" },
671
- [{ type: "text", text: { content: "My Tasks" } }],
672
- {
673
- Name: { title: {} },
674
- Status: { select: { options: [{ name: "To Do" }, { name: "Done" }] } },
675
- Priority: { select: { options: [{ name: "Low" }, { name: "High" }] } },
676
- },
748
+ { type: "page_id", page_id: "parent-id" },
749
+ [{ type: "text", text: { content: "My Tasks" } }],
750
+ {
751
+ Name: { title: {} },
752
+ Status: { select: { options: [{ name: "To Do" }, { name: "Done" }] } },
753
+ Priority: { select: { options: [{ name: "Low" }, { name: "High" }] } },
754
+ },
677
755
  );
678
756
 
679
757
  // Rename a database
680
- await useNotionUpdateDatabase("db-id", [
681
- { type: "text", text: { content: "Renamed Database" } },
682
- ]);
758
+ await useNotionUpdateDatabase("db-id", [{ type: "text", text: { content: "Renamed Database" } }]);
683
759
  ```
684
760
 
685
761
  #### Blocks — read, write, append, delete page content
686
762
 
687
763
  ```ts
688
764
  import {
689
- useNotionGetBlock,
690
- useNotionGetBlockChildren,
691
- useNotionListAllBlockChildren,
692
- useNotionAppendBlocks,
693
- useNotionUpdateBlock,
694
- useNotionDeleteBlock,
765
+ useNotionGetBlock,
766
+ useNotionGetBlockChildren,
767
+ useNotionListAllBlockChildren,
768
+ useNotionAppendBlocks,
769
+ useNotionUpdateBlock,
770
+ useNotionDeleteBlock,
695
771
  } from "katanakit-js/adapters/notion";
696
772
 
697
773
  // Get ALL blocks of a page (auto-pagination)
698
774
  const blocks = await useNotionListAllBlockChildren("page-id");
699
775
  if (blocks.ok) {
700
- blocks.data.forEach(b => console.log(b.type)); // "paragraph", "heading_1", etc.
776
+ blocks.data.forEach((b) => console.log(b.type)); // "paragraph", "heading_1", etc.
701
777
  }
702
778
 
703
779
  // Manual pagination (for fine-grained cursor control)
704
780
  const page = await useNotionGetBlockChildren("page-id", { page_size: 50 });
705
781
  if (page.ok) {
706
- console.log(page.data.results); // blocks
707
- console.log(page.data.has_more); // true if more pages exist
708
- console.log(page.data.next_cursor); // pass as start_cursor for next page
782
+ console.log(page.data.results); // blocks
783
+ console.log(page.data.has_more); // true if more pages exist
784
+ console.log(page.data.next_cursor); // pass as start_cursor for next page
709
785
  }
710
786
 
711
787
  // Append content blocks to a page
712
788
  await useNotionAppendBlocks("page-id", [
713
- {
714
- type: "heading_2",
715
- heading_2: { rich_text: [{ type: "text", text: { content: "New Section" } }] },
716
- },
717
- {
718
- type: "paragraph",
719
- paragraph: { rich_text: [{ type: "text", text: { content: "Body text here." } }] },
720
- },
721
- {
722
- type: "to_do",
723
- to_do: {
724
- rich_text: [{ type: "text", text: { content: "Checklist item" } }],
725
- checked: false,
726
- },
727
- },
789
+ {
790
+ type: "heading_2",
791
+ heading_2: { rich_text: [{ type: "text", text: { content: "New Section" } }] },
792
+ },
793
+ {
794
+ type: "paragraph",
795
+ paragraph: { rich_text: [{ type: "text", text: { content: "Body text here." } }] },
796
+ },
797
+ {
798
+ type: "to_do",
799
+ to_do: {
800
+ rich_text: [{ type: "text", text: { content: "Checklist item" } }],
801
+ checked: false,
802
+ },
803
+ },
728
804
  ]);
729
805
 
730
806
  // Update a block's content
731
807
  await useNotionUpdateBlock("block-id", {
732
- paragraph: { rich_text: [{ type: "text", text: { content: "Updated text" } }] },
808
+ paragraph: { rich_text: [{ type: "text", text: { content: "Updated text" } }] },
733
809
  });
734
810
 
735
811
  // Delete (archive) a block
@@ -746,14 +822,14 @@ const found = await useNotionSearchContent({ query: "meeting notes" });
746
822
 
747
823
  // Search only databases
748
824
  const dbs = await useNotionSearchContent({
749
- query: "tasks",
750
- filter: { value: "database", property: "object" },
825
+ query: "tasks",
826
+ filter: { value: "database", property: "object" },
751
827
  });
752
828
 
753
829
  // Search only pages, sorted by recently edited
754
830
  const pages = await useNotionSearchContent({
755
- filter: { value: "page", property: "object" },
756
- sort: { direction: "descending", timestamp: "last_edited_time" },
831
+ filter: { value: "page", property: "object" },
832
+ sort: { direction: "descending", timestamp: "last_edited_time" },
757
833
  });
758
834
  ```
759
835
 
@@ -768,22 +844,22 @@ if (user.ok) console.log(user.data.name);
768
844
 
769
845
  // List all workspace members + bots
770
846
  const users = await useNotionListUsers();
771
- if (users.ok) users.data.results.forEach(u => console.log(u.name));
847
+ if (users.ok) users.data.results.forEach((u) => console.log(u.name));
772
848
  ```
773
849
 
774
850
  #### Framework examples
775
851
 
776
- | Framework | Example | Description |
777
- |-----------|---------|-------------|
778
- | **Vue 3** | [`examples/notion/vue-blog.vue`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/vue-blog.vue) | Blog listing with `useQuery` composable |
779
- | **Vue 3** | [`examples/notion/vue-post.vue`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/vue-post.vue) | Single post view |
780
- | **Nuxt 3** | [`examples/notion/nuxt-blog.vue`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/nuxt-blog.vue) | SSR blog listing with `useAsyncData` |
781
- | **Nuxt 3** | [`examples/notion/nuxt-post.vue`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/nuxt-post.vue) | SSR single post view |
782
- | **Astro** | [`examples/notion/astro-blog.astro`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/astro-blog.astro) | Static blog listing |
783
- | **Astro** | [`examples/notion/astro-[slug].astro`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/astro-[slug].astro) | Dynamic `[slug]` route |
784
- | **Next.js** | [`examples/notion/next-blog.tsx`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/next-blog.tsx) | Server component blog listing |
785
- | **Next.js** | [`examples/notion/next-[slug].tsx`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/next-[slug].tsx) | Dynamic `[slug]` page |
786
- | **Node.js** | [`examples/notion/demo.ts`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/demo.ts) | Runnable demo covering all Notion operations |
852
+ | Framework | Example | Description |
853
+ | ----------- | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
854
+ | **Vue 3** | [`examples/notion/vue-blog.vue`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/vue-blog.vue) | Blog listing with `useQuery` composable |
855
+ | **Vue 3** | [`examples/notion/vue-post.vue`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/vue-post.vue) | Single post view |
856
+ | **Nuxt 3** | [`examples/notion/nuxt-blog.vue`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/nuxt-blog.vue) | SSR blog listing with `useAsyncData` |
857
+ | **Nuxt 3** | [`examples/notion/nuxt-post.vue`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/nuxt-post.vue) | SSR single post view |
858
+ | **Astro** | [`examples/notion/astro-blog.astro`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/astro-blog.astro) | Static blog listing |
859
+ | **Astro** | [`examples/notion/astro-[slug].astro`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/astro-[slug].astro) | Dynamic `[slug]` route |
860
+ | **Next.js** | [`examples/notion/next-blog.tsx`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/next-blog.tsx) | Server component blog listing |
861
+ | **Next.js** | [`examples/notion/next-[slug].tsx`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/next-[slug].tsx) | Dynamic `[slug]` page |
862
+ | **Node.js** | [`examples/notion/demo.ts`](https://github.com/senseikatana/katanakit-js/tree/main/examples/notion/demo.ts) | Runnable demo covering all Notion operations |
787
863
 
788
864
  ### WordPress
789
865
 
@@ -798,20 +874,20 @@ import { useInitWordPress } from "katanakit-js/adapters/wordpress";
798
874
 
799
875
  // Application Passwords (recommended) — WP Admin → Users → Your Profile → Application Passwords
800
876
  useInitWordPress({
801
- baseUrl: "https://mysite.com",
802
- auth: { type: "application-passwords", username: "admin", password: "xxxx xxxx xxxx" },
877
+ baseUrl: "https://mysite.com",
878
+ auth: { type: "application-passwords", username: "admin", password: "xxxx xxxx xxxx" },
803
879
  });
804
880
 
805
881
  // JWT tokens
806
882
  useInitWordPress({
807
- baseUrl: "https://mysite.com",
808
- auth: { type: "jwt", token: "eyJhbGci..." },
883
+ baseUrl: "https://mysite.com",
884
+ auth: { type: "jwt", token: "eyJhbGci..." },
809
885
  });
810
886
 
811
887
  // Nonce-based (for WP themes)
812
888
  useInitWordPress({
813
- baseUrl: "https://mysite.com",
814
- auth: { type: "nonce", nonce: "abc123" },
889
+ baseUrl: "https://mysite.com",
890
+ auth: { type: "nonce", nonce: "abc123" },
815
891
  });
816
892
  ```
817
893
 
@@ -819,22 +895,22 @@ useInitWordPress({
819
895
 
820
896
  ```ts
821
897
  import {
822
- useWpGetPosts,
823
- useWpGetPost,
824
- useWpCreatePost,
825
- useWpUpdatePost,
826
- useWpDeletePost,
827
- useWpListAllPosts,
828
- useWpSearchAllPosts,
829
- useWpFindPostBySlug,
898
+ useWpGetPosts,
899
+ useWpGetPost,
900
+ useWpCreatePost,
901
+ useWpUpdatePost,
902
+ useWpDeletePost,
903
+ useWpListAllPosts,
904
+ useWpSearchAllPosts,
905
+ useWpFindPostBySlug,
830
906
  } from "katanakit-js/adapters/wordpress";
831
907
 
832
908
  // List published posts (paginated)
833
909
  const posts = await useWpGetPosts({
834
- per_page: 5,
835
- status: "publish",
836
- orderby: "date",
837
- order: "desc",
910
+ per_page: 5,
911
+ status: "publish",
912
+ orderby: "date",
913
+ order: "desc",
838
914
  });
839
915
 
840
916
  // Get a single post with embedded resources
@@ -843,19 +919,19 @@ if (post.ok) console.log(post.data.title.rendered);
843
919
 
844
920
  // Create a post
845
921
  await useWpCreatePost({
846
- title: "My New Post",
847
- content: "<p>Hello World!</p>",
848
- status: "publish", // "draft" | "pending" | "publish"
849
- categories: [1, 3],
850
- tags: [5, 8],
922
+ title: "My New Post",
923
+ content: "<p>Hello World!</p>",
924
+ status: "publish", // "draft" | "pending" | "publish"
925
+ categories: [1, 3],
926
+ tags: [5, 8],
851
927
  });
852
928
 
853
929
  // Update a post
854
930
  await useWpUpdatePost(42, { title: "Updated Title", status: "publish" });
855
931
 
856
932
  // Delete (trash or permanent)
857
- await useWpDeletePost(42); // move to trash
858
- await useWpDeletePost(42, true); // permanently delete
933
+ await useWpDeletePost(42); // move to trash
934
+ await useWpDeletePost(42, true); // permanently delete
859
935
 
860
936
  // Get ALL posts (auto-pagination — loops until exhausted)
861
937
  const all = await useWpListAllPosts({ status: "publish" });
@@ -863,7 +939,7 @@ if (all.ok) console.log(`Total: ${all.data.length}`);
863
939
 
864
940
  // Search ALL posts by keyword
865
941
  const found = await useWpSearchAllPosts("tutorial");
866
- if (found.ok) found.data.forEach(p => console.log(p.title.rendered));
942
+ if (found.ok) found.data.forEach((p) => console.log(p.title.rendered));
867
943
 
868
944
  // Find post by slug (for dynamic routes like /blog/:slug)
869
945
  const bySlug = await useWpFindPostBySlug("hello-world");
@@ -874,11 +950,11 @@ if (bySlug.ok && bySlug.data) console.log(bySlug.data.title.rendered);
874
950
 
875
951
  ```ts
876
952
  import {
877
- useWpGetPages,
878
- useWpGetPage,
879
- useWpCreatePage,
880
- useWpUpdatePage,
881
- useWpDeletePage,
953
+ useWpGetPages,
954
+ useWpGetPage,
955
+ useWpCreatePage,
956
+ useWpUpdatePage,
957
+ useWpDeletePage,
882
958
  } from "katanakit-js/adapters/wordpress";
883
959
 
884
960
  const pages = await useWpGetPages({ per_page: 20 });
@@ -887,10 +963,10 @@ const page = await useWpGetPage(10);
887
963
  if (page.ok) console.log(page.data.title.rendered);
888
964
 
889
965
  await useWpCreatePage({
890
- title: "About Us",
891
- content: "<p>Welcome to our site!</p>",
892
- status: "publish",
893
- parent: 0, // top-level page (set a page ID for child pages)
966
+ title: "About Us",
967
+ content: "<p>Welcome to our site!</p>",
968
+ status: "publish",
969
+ parent: 0, // top-level page (set a page ID for child pages)
894
970
  });
895
971
 
896
972
  await useWpUpdatePage(10, { title: "Updated About" });
@@ -901,11 +977,11 @@ await useWpDeletePage(10); // trash
901
977
 
902
978
  ```ts
903
979
  import {
904
- useWpGetMedia,
905
- useWpGetMediaItem,
906
- useWpUploadMedia,
907
- useWpUpdateMedia,
908
- useWpDeleteMedia,
980
+ useWpGetMedia,
981
+ useWpGetMediaItem,
982
+ useWpUploadMedia,
983
+ useWpUpdateMedia,
984
+ useWpDeleteMedia,
909
985
  } from "katanakit-js/adapters/wordpress";
910
986
 
911
987
  // List media items
@@ -918,9 +994,9 @@ if (item.ok) console.log(item.data.source_url);
918
994
  // Upload from browser (File from <input type="file">)
919
995
  const file = document.querySelector("input[type=file]").files[0];
920
996
  const uploaded = await useWpUploadMedia(file, {
921
- title: "My Image",
922
- alt_text: "Description for accessibility",
923
- caption: "Image caption",
997
+ title: "My Image",
998
+ alt_text: "Description for accessibility",
999
+ caption: "Image caption",
924
1000
  });
925
1001
  if (uploaded.ok) console.log(uploaded.data.source_url); // URL to use in content
926
1002
 
@@ -940,10 +1016,16 @@ await useWpDeleteMedia(42, true); // permanent
940
1016
 
941
1017
  ```ts
942
1018
  import {
943
- useWpGetCategories, useWpGetCategory, useWpCreateCategory,
944
- useWpUpdateCategory, useWpDeleteCategory,
945
- useWpGetTags, useWpGetTag, useWpCreateTag,
946
- useWpUpdateTag, useWpDeleteTag,
1019
+ useWpGetCategories,
1020
+ useWpGetCategory,
1021
+ useWpCreateCategory,
1022
+ useWpUpdateCategory,
1023
+ useWpDeleteCategory,
1024
+ useWpGetTags,
1025
+ useWpGetTag,
1026
+ useWpCreateTag,
1027
+ useWpUpdateTag,
1028
+ useWpDeleteTag,
947
1029
  } from "katanakit-js/adapters/wordpress";
948
1030
 
949
1031
  // Categories
@@ -963,8 +1045,11 @@ await useWpDeleteTag(12);
963
1045
 
964
1046
  ```ts
965
1047
  import {
966
- useWpGetComments, useWpGetComment, useWpCreateComment,
967
- useWpUpdateComment, useWpDeleteComment,
1048
+ useWpGetComments,
1049
+ useWpGetComment,
1050
+ useWpCreateComment,
1051
+ useWpUpdateComment,
1052
+ useWpDeleteComment,
968
1053
  } from "katanakit-js/adapters/wordpress";
969
1054
 
970
1055
  // Get comments for a post
@@ -972,10 +1057,10 @@ const comments = await useWpGetComments({ post: 42, per_page: 10 });
972
1057
 
973
1058
  // Create a comment (public or authenticated)
974
1059
  await useWpCreateComment({
975
- post: 42,
976
- content: "Great article!",
977
- author_name: "John",
978
- author_email: "john@example.com",
1060
+ post: 42,
1061
+ content: "Great article!",
1062
+ author_name: "John",
1063
+ author_email: "john@example.com",
979
1064
  });
980
1065
 
981
1066
  // Update / delete
@@ -987,18 +1072,22 @@ await useWpDeleteComment(7);
987
1072
 
988
1073
  ```ts
989
1074
  import {
990
- useWpGetUsers, useWpGetUser, useWpGetCurrentUser,
991
- useWpCreateUser, useWpUpdateUser, useWpDeleteUser,
1075
+ useWpGetUsers,
1076
+ useWpGetUser,
1077
+ useWpGetCurrentUser,
1078
+ useWpCreateUser,
1079
+ useWpUpdateUser,
1080
+ useWpDeleteUser,
992
1081
  } from "katanakit-js/adapters/wordpress";
993
1082
 
994
1083
  const users = await useWpGetUsers({ roles: "editor" });
995
1084
  const me = await useWpGetCurrentUser(); // authenticated user
996
1085
 
997
1086
  await useWpCreateUser({
998
- username: "johndoe",
999
- email: "john@example.com",
1000
- password: "secure-password",
1001
- roles: ["editor"],
1087
+ username: "johndoe",
1088
+ email: "john@example.com",
1089
+ password: "secure-password",
1090
+ roles: ["editor"],
1002
1091
  });
1003
1092
 
1004
1093
  await useWpUpdateUser(2, { name: "John Smith" });
@@ -1009,8 +1098,11 @@ await useWpDeleteUser(2, 1); // reassign content to user 1
1009
1098
 
1010
1099
  ```ts
1011
1100
  import {
1012
- useWpGetCustomPosts, useWpGetCustomPost,
1013
- useWpCreateCustomPost, useWpUpdateCustomPost, useWpDeleteCustomPost,
1101
+ useWpGetCustomPosts,
1102
+ useWpGetCustomPost,
1103
+ useWpCreateCustomPost,
1104
+ useWpUpdateCustomPost,
1105
+ useWpDeleteCustomPost,
1014
1106
  } from "katanakit-js/adapters/wordpress";
1015
1107
 
1016
1108
  // Works with any registered CPT: "product", "portfolio", "event", etc.
@@ -1018,9 +1110,9 @@ const products = await useWpGetCustomPosts("product", { per_page: 10 });
1018
1110
  const product = await useWpGetCustomPost("product", 15);
1019
1111
 
1020
1112
  await useWpCreateCustomPost("product", {
1021
- title: "Widget",
1022
- content: "<p>A great widget</p>",
1023
- status: "publish",
1113
+ title: "Widget",
1114
+ content: "<p>A great widget</p>",
1115
+ status: "publish",
1024
1116
  });
1025
1117
 
1026
1118
  await useWpUpdateCustomPost("product", 15, { title: "Updated Widget" });
@@ -1033,13 +1125,13 @@ await useWpDeleteCustomPost("product", 15, true);
1033
1125
  import { useWpBatch } from "katanakit-js/adapters/wordpress";
1034
1126
 
1035
1127
  const result = await useWpBatch([
1036
- { method: "GET", path: "/wp/v2/posts?per_page=2" },
1037
- { method: "GET", path: "/wp/v2/pages?per_page=2" },
1038
- { method: "GET", path: "/wp/v2/categories?per_page=5" },
1128
+ { method: "GET", path: "/wp/v2/posts?per_page=2" },
1129
+ { method: "GET", path: "/wp/v2/pages?per_page=2" },
1130
+ { method: "GET", path: "/wp/v2/categories?per_page=5" },
1039
1131
  ]);
1040
1132
 
1041
1133
  if (result.ok) {
1042
- result.data.responses.forEach(resp => console.log(resp.status));
1134
+ result.data.responses.forEach((resp) => console.log(resp.status));
1043
1135
  }
1044
1136
  ```
1045
1137
 
@@ -1051,8 +1143,8 @@ significantly for list views.
1051
1143
  ```ts
1052
1144
  // Only fetch id, title, link, slug, and date
1053
1145
  const posts = await useWpGetPosts({
1054
- per_page: 20,
1055
- _fields: "id,title,link,slug,date",
1146
+ per_page: 20,
1147
+ _fields: "id,title,link,slug,date",
1056
1148
  });
1057
1149
  ```
1058
1150
 
@@ -1067,16 +1159,17 @@ const posts = await useWpGetPosts({ _embed: true });
1067
1159
 
1068
1160
  // Embed only specific resources
1069
1161
  const posts = await useWpGetPosts({
1070
- _embed: "author,wp:featuredmedia",
1162
+ _embed: "author,wp:featuredmedia",
1071
1163
  });
1072
1164
 
1073
1165
  // Access embedded data
1074
1166
  if (posts.ok) {
1075
- for (const post of posts.data) {
1076
- const author = post._embedded?.author?.[0]?.name;
1077
- const image = post._embedded?.["wp:featuredmedia"]?.[0]?.source_url;
1078
- const thumbnail = post._embedded?.["wp:featuredmedia"]?.[0]?.media_details?.sizes?.thumbnail?.source_url;
1079
- }
1167
+ for (const post of posts.data) {
1168
+ const author = post._embedded?.author?.[0]?.name;
1169
+ const image = post._embedded?.["wp:featuredmedia"]?.[0]?.source_url;
1170
+ const thumbnail =
1171
+ post._embedded?.["wp:featuredmedia"]?.[0]?.media_details?.sizes?.thumbnail?.source_url;
1172
+ }
1080
1173
  }
1081
1174
  ```
1082
1175
 
@@ -1089,51 +1182,85 @@ media item, or custom post type entry.
1089
1182
  ```ts
1090
1183
  // Request ACF fields explicitly with _fields
1091
1184
  const posts = await useWpGetPosts({
1092
- per_page: 5,
1093
- _fields: "id,title,acf",
1185
+ per_page: 5,
1186
+ _fields: "id,title,acf",
1094
1187
  });
1095
1188
 
1096
1189
  if (posts.ok) {
1097
- for (const post of posts.data) {
1098
- if (post.acf) {
1099
- // ACF fields are dynamic — access by field name
1100
- console.log(post.acf.my_field_name);
1101
- }
1102
- }
1190
+ for (const post of posts.data) {
1191
+ if (post.acf) {
1192
+ // ACF fields are dynamic — access by field name
1193
+ console.log(post.acf.my_field_name);
1194
+ }
1195
+ }
1103
1196
  }
1104
1197
 
1105
1198
  // Combine _fields + _embed + ACF for full-featured list views
1106
1199
  const full = await useWpGetPosts({
1107
- per_page: 5,
1108
- _fields: "id,title,link,slug,date,acf",
1109
- _embed: "author,wp:featuredmedia",
1200
+ per_page: 5,
1201
+ _fields: "id,title,link,slug,date,acf",
1202
+ _embed: "author,wp:featuredmedia",
1110
1203
  });
1111
1204
  ```
1112
1205
 
1113
1206
  #### Framework examples
1114
1207
 
1115
- | Framework | Example | Description |
1116
- |-----------|---------|-------------|
1117
- | **Vue 3** | [`examples/wordpress/vue-blog.vue`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/vue-blog.vue) | Blog listing with categories, featured images, `_embed` |
1118
- | **Vue 3** | [`examples/wordpress/vue-post.vue`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/vue-post.vue) | Single post view with embedded author |
1119
- | **Nuxt 3** | [`examples/wordpress/nuxt-blog.vue`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/nuxt-blog.vue) | SSR blog listing with `useAsyncData` |
1120
- | **Nuxt 3** | [`examples/wordpress/nuxt-post.vue`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/nuxt-post.vue) | SSR single post view |
1121
- | **Astro** | [`examples/wordpress/astro-blog.astro`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/astro-blog.astro) | Static blog listing |
1122
- | **Astro** | [`examples/wordpress/astro-[slug].astro`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/astro-[slug].astro) | Dynamic `[slug]` route |
1123
- | **Next.js** | [`examples/wordpress/next-blog.tsx`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/next-blog.tsx) | Server component blog listing |
1124
- | **Next.js** | [`examples/wordpress/next-[slug].tsx`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/next-[slug].tsx) | Dynamic `[slug]` page |
1125
- | **Node.js** | [`examples/wordpress/demo.ts`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/demo.ts) | Runnable demo covering all WP operations, `_fields`, `_embed`, ACF |
1208
+ | Framework | Example | Description |
1209
+ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
1210
+ | **Vue 3** | [`examples/wordpress/vue-blog.vue`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/vue-blog.vue) | Blog listing with categories, featured images, `_embed` |
1211
+ | **Vue 3** | [`examples/wordpress/vue-post.vue`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/vue-post.vue) | Single post view with embedded author |
1212
+ | **Nuxt 3** | [`examples/wordpress/nuxt-blog.vue`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/nuxt-blog.vue) | SSR blog listing with `useAsyncData` |
1213
+ | **Nuxt 3** | [`examples/wordpress/nuxt-post.vue`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/nuxt-post.vue) | SSR single post view |
1214
+ | **Astro** | [`examples/wordpress/astro-blog.astro`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/astro-blog.astro) | Static blog listing |
1215
+ | **Astro** | [`examples/wordpress/astro-[slug].astro`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/astro-[slug].astro) | Dynamic `[slug]` route |
1216
+ | **Next.js** | [`examples/wordpress/next-blog.tsx`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/next-blog.tsx) | Server component blog listing |
1217
+ | **Next.js** | [`examples/wordpress/next-[slug].tsx`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/next-[slug].tsx) | Dynamic `[slug]` page |
1218
+ | **Node.js** | [`examples/wordpress/demo.ts`](https://github.com/senseikatana/katanakit-js/tree/main/examples/wordpress/demo.ts) | Runnable demo covering all WP operations, `_fields`, `_embed`, ACF |
1219
+
1220
+ ## Contributing
1221
+
1222
+ We welcome contributions from the community! Whether it's fixing a bug, adding
1223
+ a feature, or improving documentation, every contribution helps make KatanaKit
1224
+ better for everyone.
1225
+
1226
+ ### Quick start
1227
+
1228
+ ```bash
1229
+ git clone https://github.com/senseikatana/katanakit-js.git
1230
+ cd katanakit-js
1231
+ git checkout dev
1232
+ bun install
1233
+ bun run check # verify everything works
1234
+ ```
1235
+
1236
+ ### Ways to contribute
1237
+
1238
+ - **Report bugs** — [Open an issue](https://github.com/senseikatana/katanakit-js/issues) with a clear description and reproduction steps
1239
+ - **Suggest features** — [Start a discussion](https://github.com/senseikatana/katanakit-js/discussions) to propose new ideas
1240
+ - **Submit a PR** — Fork the repo, create a branch from `dev`, make your changes, and open a PR
1241
+ - **Improve docs** — Fix typos, add examples, or clarify explanations
1242
+ - **Add adapters** — Build integrations for new APIs or frameworks
1243
+
1244
+ ### PR guidelines
1245
+
1246
+ 1. Branch from `dev` (not `main`)
1247
+ 2. Follow the `use*` naming convention
1248
+ 3. Add TypeScript types in `src/types/index.ts`
1249
+ 4. Run `bun run check` before submitting
1250
+ 5. Update `CHANGELOG.md` if the public API changed
1251
+
1252
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development contract.
1126
1253
 
1127
1254
  ## Documentation
1128
1255
 
1129
- The docs site is deployed with Render at [senseikatana.com/katanakit-js](https://senseikatana.com/katanakit-js/).
1256
+ The docs site is deployed with Cloudflare Pages at [docs.senseikatana.com](https://docs.senseikatana.com/).
1130
1257
 
1131
- - [Getting Started](https://senseikatana.com/katanakit-js/docs/guides/getting-started/)
1132
- - [Architecture](https://senseikatana.com/katanakit-js/docs/guides/architecture/)
1133
- - [UI Kit (Katana UI)](https://senseikatana.com/katanakit-js/docs/ui-kit/)
1134
- - [API Reference](https://senseikatana.com/katanakit-js/docs/api/)
1135
- - [Roadmap](https://senseikatana.com/katanakit-js/docs/guides/roadmap/)
1136
- - [Changelog](https://senseikatana.com/katanakit-js/docs/changelog/)
1258
+ - [Getting Started](https://docs.senseikatana.com/docs/guides/getting-started/)
1259
+ - [Architecture](https://docs.senseikatana.com/docs/guides/architecture/)
1260
+ - [UI Kit (Katana UI)](https://docs.senseikatana.com/docs/ui-kit/)
1261
+ - [API Reference](https://docs.senseikatana.com/docs/api/)
1262
+ - [Roadmap](https://docs.senseikatana.com/docs/guides/roadmap/)
1263
+ - [Changelog](https://docs.senseikatana.com/docs/changelog/)
1137
1264
 
1138
1265
  ## License
1139
1266