katanakit-js 3.1.2 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1100 @@
1
+ /** Default WordPress REST API namespace. */
2
+ const WP_API_NAMESPACE = "wp/v2";
3
+ /** Module-level WordPress configuration. */
4
+ let config = null;
5
+ /**
6
+ * Retrieves the registered WordPress config or throws.
7
+ * @internal
8
+ */
9
+ function getConfig() {
10
+ if (!config) {
11
+ throw new Error("[WordPress] Not configured. Call useInitWordPress() first.");
12
+ }
13
+ return config;
14
+ }
15
+ /**
16
+ * Builds the base API URL from config.
17
+ * @internal
18
+ */
19
+ function getApiBase() {
20
+ const { baseUrl, apiNamespace } = getConfig();
21
+ const ns = apiNamespace ?? WP_API_NAMESPACE;
22
+ const cleanBase = baseUrl.replace(/\/+$/, "");
23
+ return `${cleanBase}/wp-json/${ns}`;
24
+ }
25
+ /**
26
+ * Builds authentication headers from config.
27
+ * @internal
28
+ */
29
+ function getAuthHeaders() {
30
+ const { auth } = getConfig();
31
+ if (!auth)
32
+ return {};
33
+ switch (auth.type) {
34
+ case "application-passwords":
35
+ case "basic": {
36
+ const credentials = btoa(`${auth.username}:${auth.password}`);
37
+ return { Authorization: `Basic ${credentials}` };
38
+ }
39
+ case "jwt":
40
+ return { Authorization: `Bearer ${auth.token}` };
41
+ case "nonce":
42
+ return { "X-WP-Nonce": auth.nonce };
43
+ default:
44
+ return {};
45
+ }
46
+ }
47
+ /**
48
+ * Converts query params to URL search params.
49
+ * Handles _fields (string), _embed (boolean or string), and arrays.
50
+ * @internal
51
+ */
52
+ function buildQueryParams(options) {
53
+ if (!options)
54
+ return "";
55
+ const params = new URLSearchParams();
56
+ for (const [key, value] of Object.entries(options)) {
57
+ if (value === undefined || value === null)
58
+ continue;
59
+ if (key === "_embed") {
60
+ // _embed can be: true (embed all), false (skip), or "author,wp:featuredmedia" (specific)
61
+ if (value === true) {
62
+ params.set("_embed", "");
63
+ }
64
+ else if (value === false) {
65
+ continue;
66
+ }
67
+ else if (typeof value === "string" && value) {
68
+ params.set("_embed", value);
69
+ }
70
+ continue;
71
+ }
72
+ if (key === "_fields") {
73
+ // _fields is always a string: "id,title,link"
74
+ if (typeof value === "string" && value) {
75
+ params.set("_fields", value);
76
+ }
77
+ continue;
78
+ }
79
+ if (Array.isArray(value)) {
80
+ params.set(key, value.join(","));
81
+ }
82
+ else {
83
+ params.set(key, String(value));
84
+ }
85
+ }
86
+ const qs = params.toString();
87
+ return qs ? `?${qs}` : "";
88
+ }
89
+ /**
90
+ * Internal helper to make requests to the WordPress REST API.
91
+ * @internal
92
+ */
93
+ async function wpFetch(endpoint, options = {}) {
94
+ const url = `${getApiBase()}${endpoint}`;
95
+ try {
96
+ const response = await fetch(url, {
97
+ ...options,
98
+ headers: {
99
+ "Content-Type": "application/json",
100
+ ...getAuthHeaders(),
101
+ ...options.headers,
102
+ },
103
+ });
104
+ if (!response.ok) {
105
+ const errorBody = await response.text().catch(() => null);
106
+ return {
107
+ data: null,
108
+ error: {
109
+ message: `WordPress API Error: ${response.statusText}`,
110
+ status: response.status,
111
+ details: errorBody,
112
+ },
113
+ url,
114
+ status: response.status,
115
+ ok: false,
116
+ };
117
+ }
118
+ if (response.status === 204) {
119
+ return {
120
+ data: null,
121
+ error: null,
122
+ url,
123
+ status: 204,
124
+ ok: true,
125
+ };
126
+ }
127
+ const data = (await response.json());
128
+ return { data, error: null, url, status: response.status, ok: true };
129
+ }
130
+ catch (err) {
131
+ const message = err instanceof Error ? err.message : String(err);
132
+ return {
133
+ data: null,
134
+ error: { message: `Network Error: ${message}`, status: 0 },
135
+ url,
136
+ status: 0,
137
+ ok: false,
138
+ };
139
+ }
140
+ }
141
+ /**
142
+ * Internal helper for multipart file uploads.
143
+ * @internal
144
+ */
145
+ async function wpUpload(endpoint, file, meta) {
146
+ const url = `${getApiBase()}${endpoint}`;
147
+ try {
148
+ const formData = new FormData();
149
+ // Add the file
150
+ if (typeof Buffer !== "undefined" && file instanceof Buffer) {
151
+ const blob = new Blob([new Uint8Array(file)]);
152
+ formData.append("file", blob, "upload");
153
+ }
154
+ else {
155
+ formData.append("file", file, file.name ?? "upload");
156
+ }
157
+ // Add metadata
158
+ if (meta) {
159
+ for (const [key, value] of Object.entries(meta)) {
160
+ if (value !== undefined && value !== null) {
161
+ formData.append(key, String(value));
162
+ }
163
+ }
164
+ }
165
+ const response = await fetch(url, {
166
+ method: "POST",
167
+ headers: {
168
+ ...getAuthHeaders(),
169
+ // Don't set Content-Type — browser sets multipart boundary
170
+ },
171
+ body: formData,
172
+ });
173
+ if (!response.ok) {
174
+ const errorBody = await response.text().catch(() => null);
175
+ return {
176
+ data: null,
177
+ error: {
178
+ message: `WordPress Upload Error: ${response.statusText}`,
179
+ status: response.status,
180
+ details: errorBody,
181
+ },
182
+ url,
183
+ status: response.status,
184
+ ok: false,
185
+ };
186
+ }
187
+ const data = (await response.json());
188
+ return { data, error: null, url, status: response.status, ok: true };
189
+ }
190
+ catch (err) {
191
+ const message = err instanceof Error ? err.message : String(err);
192
+ return {
193
+ data: null,
194
+ error: { message: `Upload Error: ${message}`, status: 0 },
195
+ url,
196
+ status: 0,
197
+ ok: false,
198
+ };
199
+ }
200
+ }
201
+ /* -------------------------------------------------------------------------- */
202
+ /* Initialization */
203
+ /* -------------------------------------------------------------------------- */
204
+ /**
205
+ * Registers the WordPress REST API configuration. Call this once before
206
+ * any other WordPress function.
207
+ *
208
+ * Supports multiple auth methods: Application Passwords (recommended for
209
+ * plugins), JWT tokens, Basic Auth, and nonce-based auth for themes.
210
+ *
211
+ * @param cfg - Site URL, authentication method, and optional API namespace.
212
+ *
213
+ * @example
214
+ * ```ts
215
+ * import { useInitWordPress } from "katanakit-js/adapters/wordpress";
216
+ *
217
+ * // With Application Passwords (recommended)
218
+ * useInitWordPress({
219
+ * baseUrl: "https://mysite.com",
220
+ * auth: { type: "application-passwords", username: "admin", password: "xxxx xxxx xxxx" },
221
+ * });
222
+ *
223
+ * // With JWT
224
+ * useInitWordPress({
225
+ * baseUrl: "https://mysite.com",
226
+ * auth: { type: "jwt", token: "eyJhbGci..." },
227
+ * });
228
+ * ```
229
+ */
230
+ export function useInitWordPress(cfg) {
231
+ config = { ...cfg };
232
+ }
233
+ /* -------------------------------------------------------------------------- */
234
+ /* Posts */
235
+ /* -------------------------------------------------------------------------- */
236
+ /**
237
+ * Retrieves a list of WordPress posts with optional filtering and pagination.
238
+ *
239
+ * Use to display blog listings, search posts, or filter by category/tag/status.
240
+ * Returns a single page of results (default 10 per page).
241
+ *
242
+ * @param options - Query params: `per_page`, `page`, `search`, `categories`,
243
+ * `tags`, `status`, `orderby`, `order`, `_embed`, etc.
244
+ * @returns Array of posts.
245
+ *
246
+ * @example
247
+ * ```ts
248
+ * // Get latest 5 published posts
249
+ * const result = await useWpGetPosts({ per_page: 5, status: "publish" });
250
+ *
251
+ * // Search posts by keyword
252
+ * const result = await useWpGetPosts({ search: "tutorial" });
253
+ *
254
+ * // Get posts from category 3, sorted by title
255
+ * const result = await useWpGetPosts({ categories: 3, orderby: "title", order: "asc" });
256
+ * ```
257
+ */
258
+ export async function useWpGetPosts(options) {
259
+ return wpFetch(`/posts${buildQueryParams(options)}`);
260
+ }
261
+ /**
262
+ * Retrieves a single WordPress post by ID.
263
+ *
264
+ * @param id - The post ID.
265
+ * @param options - Optional query params (e.g. _embed for featured media).
266
+ * @returns The post object.
267
+ *
268
+ * @example
269
+ * ```ts
270
+ * const result = await useWpGetPost(42, { _embed: true });
271
+ * if (result.ok) console.log(result.data.title.rendered);
272
+ * ```
273
+ */
274
+ export async function useWpGetPost(id, options) {
275
+ return wpFetch(`/posts/${id}${buildQueryParams(options)}`);
276
+ }
277
+ /**
278
+ * Creates a new WordPress post.
279
+ *
280
+ * Use to publish blog entries, create drafts, or schedule future posts.
281
+ * The post will be assigned to the authenticated user by default.
282
+ *
283
+ * @param data - Post data: `title`, `content` (HTML), `status` (publish/draft/pending),
284
+ * `categories`, `tags`, `featured_media`, `excerpt`, `slug`, `date`, etc.
285
+ * @returns The created post.
286
+ *
287
+ * @example
288
+ * ```ts
289
+ * // Publish immediately
290
+ * const result = await useWpCreatePost({
291
+ * title: "My New Post",
292
+ * content: "<p>Hello World!</p>",
293
+ * status: "publish",
294
+ * categories: [1, 3],
295
+ * });
296
+ *
297
+ * // Create as draft
298
+ * await useWpCreatePost({ title: "Draft Post", content: "...", status: "draft" });
299
+ * ```
300
+ */
301
+ export async function useWpCreatePost(data) {
302
+ return wpFetch("/posts", {
303
+ method: "POST",
304
+ body: JSON.stringify(data),
305
+ });
306
+ }
307
+ /**
308
+ * Updates an existing WordPress post.
309
+ *
310
+ * @param id - The post ID to update.
311
+ * @param data - Fields to update.
312
+ * @returns The updated post.
313
+ *
314
+ * @example
315
+ * ```ts
316
+ * await useWpUpdatePost(42, { title: "Updated Title" });
317
+ * ```
318
+ */
319
+ export async function useWpUpdatePost(id, data) {
320
+ return wpFetch(`/posts/${id}`, {
321
+ method: "POST",
322
+ body: JSON.stringify(data),
323
+ });
324
+ }
325
+ /**
326
+ * Deletes a WordPress post.
327
+ *
328
+ * @param id - The post ID to delete.
329
+ * @param force - If true, permanently deletes. If false, moves to trash.
330
+ * @returns The deleted post.
331
+ *
332
+ * @example
333
+ * ```ts
334
+ * await useWpDeletePost(42); // Move to trash
335
+ * await useWpDeletePost(42, true); // Permanently delete
336
+ * ```
337
+ */
338
+ export async function useWpDeletePost(id, force = false) {
339
+ return wpFetch(`/posts/${id}?force=${force}`, {
340
+ method: "DELETE",
341
+ });
342
+ }
343
+ /* -------------------------------------------------------------------------- */
344
+ /* Pages */
345
+ /* -------------------------------------------------------------------------- */
346
+ /**
347
+ * Retrieves a list of WordPress pages.
348
+ *
349
+ * @param options - Query params for filtering, sorting, and pagination.
350
+ * @returns Array of pages.
351
+ *
352
+ * @example
353
+ * ```ts
354
+ * const result = await useWpGetPages({ per_page: 20 });
355
+ * ```
356
+ */
357
+ export async function useWpGetPages(options) {
358
+ return wpFetch(`/pages${buildQueryParams(options)}`);
359
+ }
360
+ /**
361
+ * Retrieves a single WordPress page by ID.
362
+ *
363
+ * @param id - The page ID.
364
+ * @param options - Optional query params.
365
+ * @returns The page object.
366
+ *
367
+ * @example
368
+ * ```ts
369
+ * const result = await useWpGetPage(10);
370
+ * if (result.ok) console.log(result.data.title.rendered);
371
+ * ```
372
+ */
373
+ export async function useWpGetPage(id, options) {
374
+ return wpFetch(`/pages/${id}${buildQueryParams(options)}`);
375
+ }
376
+ /**
377
+ * Creates a new WordPress page.
378
+ *
379
+ * @param data - Page data (title, content, parent, etc.).
380
+ * @returns The created page.
381
+ *
382
+ * @example
383
+ * ```ts
384
+ * await useWpCreatePage({
385
+ * title: "About Us",
386
+ * content: "<p>Welcome to our site!</p>",
387
+ * status: "publish",
388
+ * });
389
+ * ```
390
+ */
391
+ export async function useWpCreatePage(data) {
392
+ return wpFetch("/pages", {
393
+ method: "POST",
394
+ body: JSON.stringify(data),
395
+ });
396
+ }
397
+ /**
398
+ * Updates an existing WordPress page.
399
+ *
400
+ * @param id - The page ID to update.
401
+ * @param data - Fields to update.
402
+ * @returns The updated page.
403
+ *
404
+ * @example
405
+ * ```ts
406
+ * await useWpUpdatePage(10, { title: "Updated About" });
407
+ * ```
408
+ */
409
+ export async function useWpUpdatePage(id, data) {
410
+ return wpFetch(`/pages/${id}`, {
411
+ method: "POST",
412
+ body: JSON.stringify(data),
413
+ });
414
+ }
415
+ /**
416
+ * Deletes a WordPress page.
417
+ *
418
+ * @param id - The page ID to delete.
419
+ * @param force - If true, permanently deletes. If false, moves to trash.
420
+ * @returns The deleted page.
421
+ *
422
+ * @example
423
+ * ```ts
424
+ * await useWpDeletePage(10); // Move to trash
425
+ * ```
426
+ */
427
+ export async function useWpDeletePage(id, force = false) {
428
+ return wpFetch(`/pages/${id}?force=${force}`, {
429
+ method: "DELETE",
430
+ });
431
+ }
432
+ /* -------------------------------------------------------------------------- */
433
+ /* Media */
434
+ /* -------------------------------------------------------------------------- */
435
+ /**
436
+ * Retrieves a list of WordPress media items.
437
+ *
438
+ * @param options - Query params for filtering and pagination.
439
+ * @returns Array of media items.
440
+ *
441
+ * @example
442
+ * ```ts
443
+ * const result = await useWpGetMedia({ per_page: 20 });
444
+ * ```
445
+ */
446
+ export async function useWpGetMedia(options) {
447
+ return wpFetch(`/media${buildQueryParams(options)}`);
448
+ }
449
+ /**
450
+ * Retrieves a single WordPress media item by ID.
451
+ *
452
+ * @param id - The media item ID.
453
+ * @returns The media object.
454
+ *
455
+ * @example
456
+ * ```ts
457
+ * const result = await useWpGetMediaItem(42);
458
+ * if (result.ok) console.log(result.data.source_url);
459
+ * ```
460
+ */
461
+ export async function useWpGetMediaItem(id) {
462
+ return wpFetch(`/media/${id}`);
463
+ }
464
+ /**
465
+ * Uploads a file to the WordPress media library.
466
+ *
467
+ * Use to upload images, documents, or any file type supported by WordPress.
468
+ * Returns the media item with its URL for use as featured media or in content.
469
+ *
470
+ * @param file - File (browser), Blob, or Buffer (Node.js) to upload.
471
+ * @param meta - Optional metadata: `title`, `alt_text`, `caption`, `description`,
472
+ * `post` (attach to existing post), `slug`.
473
+ * @returns The created media item with `source_url`.
474
+ *
475
+ * @example
476
+ * ```ts
477
+ * // From a file input (browser)
478
+ * const file = document.querySelector("input[type=file]").files[0];
479
+ * const result = await useWpUploadMedia(file, {
480
+ * title: "My Image",
481
+ * alt_text: "Description for accessibility",
482
+ * });
483
+ * if (result.ok) console.log(result.data.source_url); // URL to use in content
484
+ *
485
+ * // From a Buffer (Node.js)
486
+ * const buffer = fs.readFileSync("photo.jpg");
487
+ * await useWpUploadMedia(buffer, { title: "Photo" });
488
+ * ```
489
+ */
490
+ export async function useWpUploadMedia(file, meta) {
491
+ return wpUpload("/media", file, meta);
492
+ }
493
+ /**
494
+ * Updates metadata of a WordPress media item.
495
+ *
496
+ * @param id - The media item ID.
497
+ * @param data - Fields to update.
498
+ * @returns The updated media item.
499
+ *
500
+ * @example
501
+ * ```ts
502
+ * await useWpUpdateMedia(42, { alt_text: "New alt text" });
503
+ * ```
504
+ */
505
+ export async function useWpUpdateMedia(id, data) {
506
+ return wpFetch(`/media/${id}`, {
507
+ method: "POST",
508
+ body: JSON.stringify(data),
509
+ });
510
+ }
511
+ /**
512
+ * Deletes a WordPress media item.
513
+ *
514
+ * @param id - The media item ID.
515
+ * @param force - If true, permanently deletes.
516
+ * @returns The deleted media item.
517
+ *
518
+ * @example
519
+ * ```ts
520
+ * await useWpDeleteMedia(42, true);
521
+ * ```
522
+ */
523
+ export async function useWpDeleteMedia(id, force = false) {
524
+ return wpFetch(`/media/${id}?force=${force}`, {
525
+ method: "DELETE",
526
+ });
527
+ }
528
+ /* -------------------------------------------------------------------------- */
529
+ /* Categories */
530
+ /* -------------------------------------------------------------------------- */
531
+ /**
532
+ * Retrieves a list of WordPress categories.
533
+ *
534
+ * @param options - Query params for filtering and pagination.
535
+ * @returns Array of categories.
536
+ *
537
+ * @example
538
+ * ```ts
539
+ * const result = await useWpGetCategories({ per_page: 50 });
540
+ * if (result.ok) result.data.forEach(c => console.log(c.name));
541
+ * ```
542
+ */
543
+ export async function useWpGetCategories(options) {
544
+ return wpFetch(`/categories${buildQueryParams(options)}`);
545
+ }
546
+ /**
547
+ * Retrieves a single WordPress category by ID.
548
+ *
549
+ * @param id - The category ID.
550
+ * @returns The category object.
551
+ *
552
+ * @example
553
+ * ```ts
554
+ * const result = await useWpGetCategory(5);
555
+ * if (result.ok) console.log(result.data.name);
556
+ * ```
557
+ */
558
+ export async function useWpGetCategory(id) {
559
+ return wpFetch(`/categories/${id}`);
560
+ }
561
+ /**
562
+ * Creates a new WordPress category.
563
+ *
564
+ * @param data - Category data (name, slug, parent, etc.).
565
+ * @returns The created category.
566
+ *
567
+ * @example
568
+ * ```ts
569
+ * await useWpCreateCategory({ name: "Technology", slug: "tech" });
570
+ * ```
571
+ */
572
+ export async function useWpCreateCategory(data) {
573
+ return wpFetch("/categories", {
574
+ method: "POST",
575
+ body: JSON.stringify(data),
576
+ });
577
+ }
578
+ /**
579
+ * Updates an existing WordPress category.
580
+ *
581
+ * @param id - The category ID to update.
582
+ * @param data - Fields to update.
583
+ * @returns The updated category.
584
+ *
585
+ * @example
586
+ * ```ts
587
+ * await useWpUpdateCategory(5, { name: "Tech News" });
588
+ * ```
589
+ */
590
+ export async function useWpUpdateCategory(id, data) {
591
+ return wpFetch(`/categories/${id}`, {
592
+ method: "POST",
593
+ body: JSON.stringify(data),
594
+ });
595
+ }
596
+ /**
597
+ * Deletes a WordPress category.
598
+ *
599
+ * @param id - The category ID to delete.
600
+ * @param force - If true, permanently deletes.
601
+ * @returns The deleted category.
602
+ *
603
+ * @example
604
+ * ```ts
605
+ * await useWpDeleteCategory(5);
606
+ * ```
607
+ */
608
+ export async function useWpDeleteCategory(id, force = false) {
609
+ return wpFetch(`/categories/${id}?force=${force}`, {
610
+ method: "DELETE",
611
+ });
612
+ }
613
+ /* -------------------------------------------------------------------------- */
614
+ /* Tags */
615
+ /* -------------------------------------------------------------------------- */
616
+ /**
617
+ * Retrieves a list of WordPress tags.
618
+ *
619
+ * @param options - Query params for filtering and pagination.
620
+ * @returns Array of tags.
621
+ *
622
+ * @example
623
+ * ```ts
624
+ * const result = await useWpGetTags({ search: "javascript" });
625
+ * ```
626
+ */
627
+ export async function useWpGetTags(options) {
628
+ return wpFetch(`/tags${buildQueryParams(options)}`);
629
+ }
630
+ /**
631
+ * Retrieves a single WordPress tag by ID.
632
+ *
633
+ * @param id - The tag ID.
634
+ * @returns The tag object.
635
+ *
636
+ * @example
637
+ * ```ts
638
+ * const result = await useWpGetTag(12);
639
+ * ```
640
+ */
641
+ export async function useWpGetTag(id) {
642
+ return wpFetch(`/tags/${id}`);
643
+ }
644
+ /**
645
+ * Creates a new WordPress tag.
646
+ *
647
+ * @param data - Tag data (name, slug, etc.).
648
+ * @returns The created tag.
649
+ *
650
+ * @example
651
+ * ```ts
652
+ * await useWpCreateTag({ name: "TypeScript", slug: "typescript" });
653
+ * ```
654
+ */
655
+ export async function useWpCreateTag(data) {
656
+ return wpFetch("/tags", {
657
+ method: "POST",
658
+ body: JSON.stringify(data),
659
+ });
660
+ }
661
+ /**
662
+ * Updates an existing WordPress tag.
663
+ *
664
+ * @param id - The tag ID to update.
665
+ * @param data - Fields to update.
666
+ * @returns The updated tag.
667
+ *
668
+ * @example
669
+ * ```ts
670
+ * await useWpUpdateTag(12, { name: "TS" });
671
+ * ```
672
+ */
673
+ export async function useWpUpdateTag(id, data) {
674
+ return wpFetch(`/tags/${id}`, {
675
+ method: "POST",
676
+ body: JSON.stringify(data),
677
+ });
678
+ }
679
+ /**
680
+ * Deletes a WordPress tag.
681
+ *
682
+ * @param id - The tag ID to delete.
683
+ * @param force - If true, permanently deletes.
684
+ * @returns The deleted tag.
685
+ *
686
+ * @example
687
+ * ```ts
688
+ * await useWpDeleteTag(12);
689
+ * ```
690
+ */
691
+ export async function useWpDeleteTag(id, force = false) {
692
+ return wpFetch(`/tags/${id}?force=${force}`, {
693
+ method: "DELETE",
694
+ });
695
+ }
696
+ /* -------------------------------------------------------------------------- */
697
+ /* Comments */
698
+ /* -------------------------------------------------------------------------- */
699
+ /**
700
+ * Retrieves a list of WordPress comments.
701
+ *
702
+ * @param options - Query params for filtering and pagination.
703
+ * @returns Array of comments.
704
+ *
705
+ * @example
706
+ * ```ts
707
+ * // Get comments for a specific post
708
+ * const result = await useWpGetComments({ post: 42 });
709
+ * ```
710
+ */
711
+ export async function useWpGetComments(options) {
712
+ return wpFetch(`/comments${buildQueryParams(options)}`);
713
+ }
714
+ /**
715
+ * Retrieves a single WordPress comment by ID.
716
+ *
717
+ * @param id - The comment ID.
718
+ * @returns The comment object.
719
+ *
720
+ * @example
721
+ * ```ts
722
+ * const result = await useWpGetComment(7);
723
+ * ```
724
+ */
725
+ export async function useWpGetComment(id) {
726
+ return wpFetch(`/comments/${id}`);
727
+ }
728
+ /**
729
+ * Creates a new WordPress comment.
730
+ *
731
+ * @param data - Comment data (post ID, content, author, etc.).
732
+ * @returns The created comment.
733
+ *
734
+ * @example
735
+ * ```ts
736
+ * await useWpCreateComment({
737
+ * post: 42,
738
+ * content: "Great article!",
739
+ * author_name: "John",
740
+ * author_email: "john@example.com",
741
+ * });
742
+ * ```
743
+ */
744
+ export async function useWpCreateComment(data) {
745
+ return wpFetch("/comments", {
746
+ method: "POST",
747
+ body: JSON.stringify(data),
748
+ });
749
+ }
750
+ /**
751
+ * Updates an existing WordPress comment.
752
+ *
753
+ * @param id - The comment ID to update.
754
+ * @param data - Fields to update.
755
+ * @returns The updated comment.
756
+ *
757
+ * @example
758
+ * ```ts
759
+ * await useWpUpdateComment(7, { content: "Updated comment" });
760
+ * ```
761
+ */
762
+ export async function useWpUpdateComment(id, data) {
763
+ return wpFetch(`/comments/${id}`, {
764
+ method: "POST",
765
+ body: JSON.stringify(data),
766
+ });
767
+ }
768
+ /**
769
+ * Deletes a WordPress comment.
770
+ *
771
+ * @param id - The comment ID to delete.
772
+ * @param force - If true, permanently deletes.
773
+ * @returns The deleted comment.
774
+ *
775
+ * @example
776
+ * ```ts
777
+ * await useWpDeleteComment(7);
778
+ * ```
779
+ */
780
+ export async function useWpDeleteComment(id, force = false) {
781
+ return wpFetch(`/comments/${id}?force=${force}`, {
782
+ method: "DELETE",
783
+ });
784
+ }
785
+ /* -------------------------------------------------------------------------- */
786
+ /* Users */
787
+ /* -------------------------------------------------------------------------- */
788
+ /**
789
+ * Retrieves a list of WordPress users.
790
+ *
791
+ * @param options - Query params for filtering and pagination.
792
+ * @returns Array of users.
793
+ *
794
+ * @example
795
+ * ```ts
796
+ * const result = await useWpGetUsers({ roles: "editor" });
797
+ * ```
798
+ */
799
+ export async function useWpGetUsers(options) {
800
+ return wpFetch(`/users${buildQueryParams(options)}`);
801
+ }
802
+ /**
803
+ * Retrieves a single WordPress user by ID.
804
+ *
805
+ * @param id - The user ID.
806
+ * @returns The user object.
807
+ *
808
+ * @example
809
+ * ```ts
810
+ * const result = await useWpGetUser(1);
811
+ * if (result.ok) console.log(result.data.name);
812
+ * ```
813
+ */
814
+ export async function useWpGetUser(id) {
815
+ return wpFetch(`/users/${id}`);
816
+ }
817
+ /**
818
+ * Retrieves the currently authenticated WordPress user.
819
+ *
820
+ * @returns The current user object.
821
+ *
822
+ * @example
823
+ * ```ts
824
+ * const result = await useWpGetCurrentUser();
825
+ * if (result.ok) console.log(result.data.email);
826
+ * ```
827
+ */
828
+ export async function useWpGetCurrentUser() {
829
+ return wpFetch("/users/me");
830
+ }
831
+ /**
832
+ * Creates a new WordPress user.
833
+ *
834
+ * @param data - User data (username, email, password, etc.).
835
+ * @returns The created user.
836
+ *
837
+ * @example
838
+ * ```ts
839
+ * await useWpCreateUser({
840
+ * username: "johndoe",
841
+ * email: "john@example.com",
842
+ * password: "secure-password",
843
+ * roles: ["editor"],
844
+ * });
845
+ * ```
846
+ */
847
+ export async function useWpCreateUser(data) {
848
+ return wpFetch("/users", {
849
+ method: "POST",
850
+ body: JSON.stringify(data),
851
+ });
852
+ }
853
+ /**
854
+ * Updates an existing WordPress user.
855
+ *
856
+ * @param id - The user ID to update.
857
+ * @param data - Fields to update.
858
+ * @returns The updated user.
859
+ *
860
+ * @example
861
+ * ```ts
862
+ * await useWpUpdateUser(2, { name: "John Smith" });
863
+ * ```
864
+ */
865
+ export async function useWpUpdateUser(id, data) {
866
+ return wpFetch(`/users/${id}`, {
867
+ method: "POST",
868
+ body: JSON.stringify(data),
869
+ });
870
+ }
871
+ /**
872
+ * Deletes a WordPress user.
873
+ *
874
+ * @param id - The user ID to delete.
875
+ * @param reassign - User ID to reassign content to (required for non-admin).
876
+ * @returns The deleted user.
877
+ *
878
+ * @example
879
+ * ```ts
880
+ * await useWpDeleteUser(2, 1); // Reassign content to user 1
881
+ * ```
882
+ */
883
+ export async function useWpDeleteUser(id, reassign) {
884
+ const qs = reassign ? `?reassign=${reassign}` : "";
885
+ return wpFetch(`/users/${id}${qs}`, {
886
+ method: "DELETE",
887
+ });
888
+ }
889
+ /* -------------------------------------------------------------------------- */
890
+ /* Custom Post Types */
891
+ /* -------------------------------------------------------------------------- */
892
+ /**
893
+ * Retrieves a list of custom post type entries.
894
+ *
895
+ * @param postType - The custom post type slug (e.g. "product", "portfolio").
896
+ * @param options - Query params for filtering and pagination.
897
+ * @returns Array of custom post entries.
898
+ *
899
+ * @example
900
+ * ```ts
901
+ * const result = await useWpGetCustomPosts("product", { per_page: 10 });
902
+ * ```
903
+ */
904
+ export async function useWpGetCustomPosts(postType, options) {
905
+ return wpFetch(`/${postType}${buildQueryParams(options)}`);
906
+ }
907
+ /**
908
+ * Retrieves a single custom post type entry by ID.
909
+ *
910
+ * @param postType - The custom post type slug.
911
+ * @param id - The entry ID.
912
+ * @param options - Optional query params.
913
+ * @returns The custom post entry.
914
+ *
915
+ * @example
916
+ * ```ts
917
+ * const result = await useWpGetCustomPost("product", 15);
918
+ * ```
919
+ */
920
+ export async function useWpGetCustomPost(postType, id, options) {
921
+ return wpFetch(`/${postType}/${id}${buildQueryParams(options)}`);
922
+ }
923
+ /**
924
+ * Creates a new custom post type entry.
925
+ *
926
+ * @param postType - The custom post type slug.
927
+ * @param data - Entry data.
928
+ * @returns The created entry.
929
+ *
930
+ * @example
931
+ * ```ts
932
+ * await useWpCreateCustomPost("product", {
933
+ * title: "Widget",
934
+ * content: "<p>A great widget</p>",
935
+ * status: "publish",
936
+ * });
937
+ * ```
938
+ */
939
+ export async function useWpCreateCustomPost(postType, data) {
940
+ return wpFetch(`/${postType}`, {
941
+ method: "POST",
942
+ body: JSON.stringify(data),
943
+ });
944
+ }
945
+ /**
946
+ * Updates a custom post type entry.
947
+ *
948
+ * @param postType - The custom post type slug.
949
+ * @param id - The entry ID.
950
+ * @param data - Fields to update.
951
+ * @returns The updated entry.
952
+ *
953
+ * @example
954
+ * ```ts
955
+ * await useWpUpdateCustomPost("product", 15, { title: "Updated Widget" });
956
+ * ```
957
+ */
958
+ export async function useWpUpdateCustomPost(postType, id, data) {
959
+ return wpFetch(`/${postType}/${id}`, {
960
+ method: "POST",
961
+ body: JSON.stringify(data),
962
+ });
963
+ }
964
+ /**
965
+ * Deletes a custom post type entry.
966
+ *
967
+ * @param postType - The custom post type slug.
968
+ * @param id - The entry ID.
969
+ * @param force - If true, permanently deletes.
970
+ * @returns The deleted entry.
971
+ *
972
+ * @example
973
+ * ```ts
974
+ * await useWpDeleteCustomPost("product", 15, true);
975
+ * ```
976
+ */
977
+ export async function useWpDeleteCustomPost(postType, id, force = false) {
978
+ return wpFetch(`/${postType}/${id}?force=${force}`, {
979
+ method: "DELETE",
980
+ });
981
+ }
982
+ /* -------------------------------------------------------------------------- */
983
+ /* Batch Operations */
984
+ /* -------------------------------------------------------------------------- */
985
+ /**
986
+ * Executes multiple WordPress REST API requests in a single batch.
987
+ *
988
+ * @param operations - Array of batch operations to execute.
989
+ * @returns Combined results from all operations.
990
+ *
991
+ * @example
992
+ * ```ts
993
+ * const result = await useWpBatch([
994
+ * { method: "GET", path: "/wp/v2/posts/1" },
995
+ * { method: "POST", path: "/wp/v2/posts", body: { title: "New Post" } },
996
+ * ]);
997
+ * if (result.ok) console.log(result.data.responses);
998
+ * ```
999
+ */
1000
+ export async function useWpBatch(operations) {
1001
+ return wpFetch("/batch/v1", {
1002
+ method: "POST",
1003
+ body: JSON.stringify({ requests: operations }),
1004
+ });
1005
+ }
1006
+ /* -------------------------------------------------------------------------- */
1007
+ /* Pagination Helpers */
1008
+ /* -------------------------------------------------------------------------- */
1009
+ /**
1010
+ * Fetches ALL posts matching the criteria, automatically handling pagination.
1011
+ *
1012
+ * Use when you need every post (e.g. for sitemap generation, RSS feeds,
1013
+ * or data exports) without managing page numbers. Loops until all pages
1014
+ * are fetched.
1015
+ *
1016
+ * @param options - Query params for filtering (`status`, `categories`, `tags`,
1017
+ * `author`, `search`, `orderby`, `order`). Do NOT pass `page` or `per_page`.
1018
+ * @returns All posts as a flat array.
1019
+ *
1020
+ * @example
1021
+ * ```ts
1022
+ * // Get ALL published posts
1023
+ * const result = await useWpListAllPosts({ status: "publish" });
1024
+ * if (result.ok) console.log(`Total: ${result.data.length}`);
1025
+ *
1026
+ * // Get ALL posts from category 5
1027
+ * const cat5 = await useWpListAllPosts({ categories: 5 });
1028
+ * ```
1029
+ */
1030
+ export async function useWpListAllPosts(options) {
1031
+ const allPosts = [];
1032
+ let page = 1;
1033
+ while (true) {
1034
+ const result = await useWpGetPosts({ ...options, page, per_page: 100 });
1035
+ if (!result.ok)
1036
+ return result;
1037
+ allPosts.push(...result.data);
1038
+ if (result.data.length < 100)
1039
+ break;
1040
+ page++;
1041
+ }
1042
+ return {
1043
+ data: allPosts,
1044
+ error: null,
1045
+ url: "",
1046
+ status: 200,
1047
+ ok: true,
1048
+ };
1049
+ }
1050
+ /**
1051
+ * Searches ALL posts by keyword, automatically handling pagination.
1052
+ *
1053
+ * Use to find posts by title or content when you need every match,
1054
+ * not just the first page. Combines `search` param with auto-pagination.
1055
+ *
1056
+ * @param query - Search term (searches title and content).
1057
+ * @param options - Additional filters (`status`, `categories`, etc.).
1058
+ * @returns All posts matching the search.
1059
+ *
1060
+ * @example
1061
+ * ```ts
1062
+ * const result = await useWpSearchAllPosts("tutorial");
1063
+ * if (result.ok) result.data.forEach(p => console.log(p.title.rendered));
1064
+ * ```
1065
+ */
1066
+ export async function useWpSearchAllPosts(query, options) {
1067
+ return useWpListAllPosts({ ...options, search: query });
1068
+ }
1069
+ /**
1070
+ * Finds a single post by its URL slug.
1071
+ *
1072
+ * Use for slug-based routing (e.g. `/blog/:slug` pages) where you need
1073
+ * to fetch a post by its human-readable URL identifier. Returns `null`
1074
+ * if no post matches the slug.
1075
+ *
1076
+ * @param slug - The post slug (e.g. "hello-world").
1077
+ * @returns The post or `null` if not found.
1078
+ *
1079
+ * @example
1080
+ * ```ts
1081
+ * // In an Astro/[slug].astro or similar dynamic route
1082
+ * const result = await useWpFindPostBySlug("hello-world");
1083
+ * if (result.ok && result.data) {
1084
+ * console.log(result.data.title.rendered);
1085
+ * }
1086
+ * ```
1087
+ */
1088
+ export async function useWpFindPostBySlug(slug) {
1089
+ const result = await useWpGetPosts({ slug, per_page: 1 });
1090
+ if (!result.ok)
1091
+ return result;
1092
+ return {
1093
+ data: result.data[0] ?? null,
1094
+ error: null,
1095
+ url: result.url,
1096
+ status: result.status,
1097
+ ok: true,
1098
+ };
1099
+ }
1100
+ //# sourceMappingURL=wordpress.service.js.map