@xexr/blog-platform 1.0.7 → 1.0.9

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 (49) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +110 -29
  3. package/dist/app/blog/[slug]/page.d.ts +68 -0
  4. package/dist/app/blog/[slug]/page.tsx +175 -116
  5. package/dist/app/blog/authors/[slug]/page.test.ts +108 -0
  6. package/dist/app/blog/authors/[slug]/page.tsx +387 -0
  7. package/dist/app/blog/page.tsx +138 -113
  8. package/dist/components/ArticleReadingProgress.tsx +208 -0
  9. package/dist/components/ArticleTopActions.tsx +179 -0
  10. package/dist/components/AuthorByline.tsx +569 -0
  11. package/dist/components/AuthorPages.test.ts +87 -0
  12. package/dist/components/AuthorPages.tsx +129 -0
  13. package/dist/components/ExpandableArticleImage.tsx +79 -0
  14. package/dist/components/Pagination.tsx +236 -0
  15. package/dist/components/TopicArchivePages.tsx +204 -0
  16. package/dist/components/ui/badge.tsx +36 -0
  17. package/dist/components/ui/button.tsx +59 -0
  18. package/dist/components/ui/card.tsx +92 -0
  19. package/dist/components/ui/pagination.tsx +127 -0
  20. package/dist/components/ui/separator.tsx +29 -0
  21. package/dist/index.d.mts +174 -112
  22. package/dist/index.d.ts +174 -112
  23. package/dist/index.js +83 -8
  24. package/dist/index.js.map +1 -1
  25. package/dist/index.mjs +83 -8
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/lib/article-ui.ts +158 -0
  28. package/dist/lib/auth.ts +354 -0
  29. package/dist/lib/authors.ts +100 -0
  30. package/dist/lib/cors.ts +243 -0
  31. package/dist/lib/database.ts +277 -0
  32. package/dist/lib/init.ts +186 -0
  33. package/dist/lib/mdx.ts +73 -0
  34. package/dist/lib/metadata.ts +558 -0
  35. package/dist/lib/ratelimit.ts +224 -0
  36. package/dist/lib/security-headers.ts +322 -0
  37. package/dist/lib/seo.ts +295 -0
  38. package/dist/lib/sitemap.ts +738 -0
  39. package/dist/lib/storage/drizzle.ts +400 -0
  40. package/dist/lib/structured-data.ts +609 -0
  41. package/dist/lib/topics.ts +56 -0
  42. package/dist/lib/urls.ts +36 -0
  43. package/dist/lib/utils.ts +6 -0
  44. package/dist/lib/version.ts +154 -0
  45. package/dist/types/contracts.ts +259 -0
  46. package/dist/types/index.d.mts +20 -1
  47. package/dist/types/index.d.ts +20 -1
  48. package/dist/types/index.ts +16 -0
  49. package/package.json +14 -9
@@ -0,0 +1,59 @@
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "../../lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
14
+ destructive:
15
+ "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
16
+ outline:
17
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
18
+ secondary:
19
+ "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
20
+ ghost:
21
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
22
+ link: "text-primary underline-offset-4 hover:underline",
23
+ },
24
+ size: {
25
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
26
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
27
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
28
+ icon: "size-9",
29
+ },
30
+ },
31
+ defaultVariants: {
32
+ variant: "default",
33
+ size: "default",
34
+ },
35
+ }
36
+ )
37
+
38
+ function Button({
39
+ className,
40
+ variant,
41
+ size,
42
+ asChild = false,
43
+ ...props
44
+ }: React.ComponentProps<"button"> &
45
+ VariantProps<typeof buttonVariants> & {
46
+ asChild?: boolean
47
+ }) {
48
+ const Comp = asChild ? Slot : "button"
49
+
50
+ return (
51
+ <Comp
52
+ data-slot="button"
53
+ className={cn(buttonVariants({ variant, size, className }))}
54
+ {...props}
55
+ />
56
+ )
57
+ }
58
+
59
+ export { Button, buttonVariants }
@@ -0,0 +1,92 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../../lib/utils"
4
+
5
+ function Card({ className, ...props }: React.ComponentProps<"div">) {
6
+ return (
7
+ <div
8
+ data-slot="card"
9
+ className={cn(
10
+ "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
11
+ className
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
19
+ return (
20
+ <div
21
+ data-slot="card-header"
22
+ className={cn(
23
+ "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
24
+ className
25
+ )}
26
+ {...props}
27
+ />
28
+ )
29
+ }
30
+
31
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
32
+ return (
33
+ <div
34
+ data-slot="card-title"
35
+ className={cn("leading-none font-semibold", className)}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
42
+ return (
43
+ <div
44
+ data-slot="card-description"
45
+ className={cn("text-muted-foreground text-sm", className)}
46
+ {...props}
47
+ />
48
+ )
49
+ }
50
+
51
+ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
52
+ return (
53
+ <div
54
+ data-slot="card-action"
55
+ className={cn(
56
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
57
+ className
58
+ )}
59
+ {...props}
60
+ />
61
+ )
62
+ }
63
+
64
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
65
+ return (
66
+ <div
67
+ data-slot="card-content"
68
+ className={cn("px-6", className)}
69
+ {...props}
70
+ />
71
+ )
72
+ }
73
+
74
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
75
+ return (
76
+ <div
77
+ data-slot="card-footer"
78
+ className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
79
+ {...props}
80
+ />
81
+ )
82
+ }
83
+
84
+ export {
85
+ Card,
86
+ CardHeader,
87
+ CardFooter,
88
+ CardTitle,
89
+ CardAction,
90
+ CardDescription,
91
+ CardContent,
92
+ }
@@ -0,0 +1,127 @@
1
+ import * as React from "react"
2
+ import {
3
+ ChevronLeftIcon,
4
+ ChevronRightIcon,
5
+ MoreHorizontalIcon,
6
+ } from "lucide-react"
7
+
8
+ import { cn } from "../../lib/utils"
9
+ import { buttonVariants } from "./button"
10
+
11
+ function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
12
+ return (
13
+ <nav
14
+ role="navigation"
15
+ aria-label="pagination"
16
+ data-slot="pagination"
17
+ className={cn("mx-auto flex w-full justify-center", className)}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ function PaginationContent({
24
+ className,
25
+ ...props
26
+ }: React.ComponentProps<"ul">) {
27
+ return (
28
+ <ul
29
+ data-slot="pagination-content"
30
+ className={cn("flex flex-row items-center gap-1", className)}
31
+ {...props}
32
+ />
33
+ )
34
+ }
35
+
36
+ function PaginationItem({ ...props }: React.ComponentProps<"li">) {
37
+ return <li data-slot="pagination-item" {...props} />
38
+ }
39
+
40
+ type PaginationLinkProps = {
41
+ isActive?: boolean
42
+ size?: "default" | "sm" | "lg" | "icon"
43
+ } & React.ComponentProps<"a">
44
+
45
+ function PaginationLink({
46
+ className,
47
+ isActive,
48
+ size = "icon",
49
+ ...props
50
+ }: PaginationLinkProps) {
51
+ return (
52
+ <a
53
+ aria-current={isActive ? "page" : undefined}
54
+ data-slot="pagination-link"
55
+ data-active={isActive}
56
+ className={cn(
57
+ buttonVariants({
58
+ variant: isActive ? "outline" : "ghost",
59
+ size,
60
+ }),
61
+ className
62
+ )}
63
+ {...props}
64
+ />
65
+ )
66
+ }
67
+
68
+ function PaginationPrevious({
69
+ className,
70
+ ...props
71
+ }: React.ComponentProps<typeof PaginationLink>) {
72
+ return (
73
+ <PaginationLink
74
+ aria-label="Go to previous page"
75
+ size="default"
76
+ className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
77
+ {...props}
78
+ >
79
+ <ChevronLeftIcon />
80
+ <span className="hidden sm:block">Previous</span>
81
+ </PaginationLink>
82
+ )
83
+ }
84
+
85
+ function PaginationNext({
86
+ className,
87
+ ...props
88
+ }: React.ComponentProps<typeof PaginationLink>) {
89
+ return (
90
+ <PaginationLink
91
+ aria-label="Go to next page"
92
+ size="default"
93
+ className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
94
+ {...props}
95
+ >
96
+ <span className="hidden sm:block">Next</span>
97
+ <ChevronRightIcon />
98
+ </PaginationLink>
99
+ )
100
+ }
101
+
102
+ function PaginationEllipsis({
103
+ className,
104
+ ...props
105
+ }: React.ComponentProps<"span">) {
106
+ return (
107
+ <span
108
+ aria-hidden
109
+ data-slot="pagination-ellipsis"
110
+ className={cn("flex size-9 items-center justify-center", className)}
111
+ {...props}
112
+ >
113
+ <MoreHorizontalIcon className="size-4" />
114
+ <span className="sr-only">More pages</span>
115
+ </span>
116
+ )
117
+ }
118
+
119
+ export {
120
+ Pagination,
121
+ PaginationContent,
122
+ PaginationLink,
123
+ PaginationItem,
124
+ PaginationPrevious,
125
+ PaginationNext,
126
+ PaginationEllipsis,
127
+ }
@@ -0,0 +1,29 @@
1
+ import * as React from "react"
2
+ import * as SeparatorPrimitive from "@radix-ui/react-separator"
3
+
4
+ import { cn } from "../../lib/utils"
5
+
6
+ const Separator = React.forwardRef<
7
+ React.ElementRef<typeof SeparatorPrimitive.Root>,
8
+ React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
9
+ >(
10
+ (
11
+ { className, orientation = "horizontal", decorative = true, ...props },
12
+ ref
13
+ ) => (
14
+ <SeparatorPrimitive.Root
15
+ ref={ref}
16
+ decorative={decorative}
17
+ orientation={orientation}
18
+ className={cn(
19
+ "shrink-0 bg-border",
20
+ orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
21
+ className
22
+ )}
23
+ {...props}
24
+ />
25
+ )
26
+ )
27
+ Separator.displayName = SeparatorPrimitive.Root.displayName
28
+
29
+ export { Separator }
package/dist/index.d.mts CHANGED
@@ -1,8 +1,8 @@
1
- import { PublishedArticle, PublishedSite, PublishedTopic, PublishResult, PublishedAuthor } from './types/index.mjs';
2
- export { PublishPayload } from './types/index.mjs';
1
+ import { PublishedArticle, PublishedSite, PublishedAuthor, PublishResult, PublishedTopic } from './types/index.mjs';
2
+ export { PublishPayload, PublishedAuthorSocialLinks } from './types/index.mjs';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import * as React from 'react';
5
- import { CSSProperties } from 'react';
5
+ import { CSSProperties, ReactElement } from 'react';
6
6
  import * as class_variance_authority_types from 'class-variance-authority/types';
7
7
  import { VariantProps } from 'class-variance-authority';
8
8
  import { ClassValue } from 'clsx';
@@ -100,55 +100,97 @@ declare function generateBlogHomeMetadata(siteName?: string, siteDescription?: s
100
100
  };
101
101
 
102
102
  interface ArticlePageProps {
103
- article: PublishedArticle;
103
+ article: PublishedArticle;
104
+ className?: string;
105
+ navigation?: {
106
+ backToHome?: {
107
+ url: string;
108
+ label?: string;
109
+ };
110
+ previousArticle?: {
111
+ url: string;
112
+ title: string;
113
+ featuredImage?: string;
114
+ featuredImageAlt?: string;
115
+ };
116
+ nextArticle?: {
117
+ url: string;
118
+ title: string;
119
+ featuredImage?: string;
120
+ featuredImageAlt?: string;
121
+ };
122
+ };
123
+ basePath?: string;
124
+ theme?: BlogTheme;
125
+ postArticleCta?: {
126
+ title: string;
127
+ description?: string;
128
+ href: string;
129
+ label: string;
130
+ };
131
+ relatedArticles?: PublishedArticle[];
132
+ showShareLinks?: boolean;
133
+ }
134
+
135
+ declare function ArticlePage(props: ArticlePageProps): ReactElement;
136
+
137
+ declare function generateArticleMetadata(
138
+ article: PublishedArticle,
139
+ basePath?: string,
140
+ ): {
141
+ title: string;
142
+ description: string;
143
+ keywords: string;
144
+ authors: { name: string }[] | undefined;
145
+ openGraph: {
146
+ title: string;
147
+ description: string;
148
+ type: string;
149
+ url: string;
150
+ images: { url: string; alt: string }[] | undefined;
151
+ publishedTime: string;
152
+ modifiedTime: string;
153
+ authors: string[] | undefined;
154
+ siteName: string;
155
+ };
156
+ twitter: {
157
+ card: string;
158
+ title: string;
159
+ description: string;
160
+ images: string[] | undefined;
161
+ };
162
+ alternates: {
163
+ canonical: string;
164
+ };
165
+ };
166
+
167
+ interface AuthorPageProps {
168
+ author: PublishedAuthor;
169
+ articles: PublishedArticle[];
170
+ site: PublishedSite;
104
171
  className?: string;
105
- /** Optional navigation links */
172
+ basePath?: string;
173
+ theme?: BlogTheme;
174
+ pagination?: {
175
+ currentPage: number;
176
+ totalPages: number;
177
+ baseUrl?: string;
178
+ totalItems?: number;
179
+ };
106
180
  navigation?: {
107
181
  backToHome?: {
108
182
  url: string;
109
183
  label?: string;
110
184
  };
111
- previousArticle?: {
112
- url: string;
113
- title: string;
114
- featuredImage?: string;
115
- featuredImageAlt?: string;
116
- };
117
- nextArticle?: {
118
- url: string;
119
- title: string;
120
- featuredImage?: string;
121
- featuredImageAlt?: string;
122
- };
123
- };
124
- /** Public path where this blog is mounted, e.g. "/blog" or "/new-blog". */
125
- basePath?: string;
126
- /** Optional theme values for host-site brand integration. */
127
- theme?: BlogTheme;
128
- /** Optional post-article CTA supplied by the host site. */
129
- postArticleCta?: {
130
- title: string;
131
- description?: string;
132
- href: string;
133
- label: string;
134
185
  };
135
- /** Optional related articles supplied by the host site. */
136
- relatedArticles?: PublishedArticle[];
137
- /** Whether to render static article share links. */
138
- showShareLinks?: boolean;
139
186
  }
140
- declare function ArticlePage({ article, className, navigation, basePath, theme, postArticleCta, relatedArticles, showShareLinks, }: ArticlePageProps): react_jsx_runtime.JSX.Element;
141
- /**
142
- * Generate metadata for the article page for SEO optimization.
143
- * This function should be used in the Next.js metadata API.
144
- */
145
- declare function generateArticleMetadata(article: PublishedArticle, basePath?: string): {
187
+ declare function AuthorPage({ author, articles, site, className, basePath, theme, pagination, navigation, }: AuthorPageProps): react_jsx_runtime.JSX.Element;
188
+ declare function generateAuthorPageMetadata(author: PublishedAuthor, site: PublishedSite, basePath?: string): {
146
189
  title: string;
147
190
  description: string;
148
- keywords: string;
149
191
  authors: {
150
192
  name: string;
151
- }[] | undefined;
193
+ }[];
152
194
  openGraph: {
153
195
  title: string;
154
196
  description: string;
@@ -158,9 +200,6 @@ declare function generateArticleMetadata(article: PublishedArticle, basePath?: s
158
200
  url: string;
159
201
  alt: string;
160
202
  }[] | undefined;
161
- publishedTime: string;
162
- modifiedTime: string;
163
- authors: string[] | undefined;
164
203
  siteName: string;
165
204
  };
166
205
  twitter: {
@@ -262,8 +301,88 @@ declare function cn(...inputs: ClassValue[]): string;
262
301
  */
263
302
  declare function normalizeBlogBasePath(basePath?: string): string;
264
303
  declare function buildBlogPath(basePath: string | undefined, slug?: string): string;
304
+ declare function buildAuthorPath(basePath: string | undefined, authorSlug: string): string;
265
305
  declare function getPathPrefixFromBlogUrl(blogUrl: string): string;
266
306
 
307
+ /**
308
+ * Database interface for blog platform storage
309
+ *
310
+ * This provides a simple interface for storing published articles
311
+ * without coupling to any specific database implementation.
312
+ * Customers can implement this interface with their preferred database.
313
+ */
314
+
315
+ interface BlogPlatformStorage {
316
+ /**
317
+ * Store a published article
318
+ */
319
+ saveArticle(article: PublishedArticle, idempotencyKey: string): Promise<PublishResult>;
320
+ /**
321
+ * Get an article by slug
322
+ */
323
+ getArticle(slug: string): Promise<PublishedArticle | null>;
324
+ /**
325
+ * List all published articles
326
+ */
327
+ listArticles(options?: {
328
+ limit?: number;
329
+ offset?: number;
330
+ sortBy?: "publishedAt" | "updatedAt";
331
+ order?: "asc" | "desc";
332
+ }): Promise<{
333
+ articles: PublishedArticle[];
334
+ total: number;
335
+ }>;
336
+ /**
337
+ * Remove an article by slug
338
+ */
339
+ removeArticle(slug: string, idempotencyKey: string): Promise<PublishResult>;
340
+ /**
341
+ * Check if an operation was already performed (idempotency)
342
+ */
343
+ checkIdempotency(idempotencyKey: string): Promise<PublishResult | null>;
344
+ /**
345
+ * Store the result of an operation for idempotency
346
+ */
347
+ storeOperationResult(idempotencyKey: string, result: PublishResult): Promise<void>;
348
+ }
349
+ /**
350
+ * Default in-memory storage implementation for development/testing
351
+ * IMPORTANT: This is not suitable for production use!
352
+ */
353
+ declare class InMemoryStorage implements BlogPlatformStorage {
354
+ private articles;
355
+ private operations;
356
+ saveArticle(article: PublishedArticle, idempotencyKey: string): Promise<PublishResult>;
357
+ getArticle(slug: string): Promise<PublishedArticle | null>;
358
+ listArticles(options?: {
359
+ limit?: number;
360
+ offset?: number;
361
+ sortBy?: "publishedAt" | "updatedAt";
362
+ order?: "asc" | "desc";
363
+ }): Promise<{
364
+ articles: PublishedArticle[];
365
+ total: number;
366
+ }>;
367
+ removeArticle(slug: string, idempotencyKey: string): Promise<PublishResult>;
368
+ checkIdempotency(idempotencyKey: string): Promise<PublishResult | null>;
369
+ storeOperationResult(idempotencyKey: string, result: PublishResult): Promise<void>;
370
+ }
371
+ declare function getStorage(): BlogPlatformStorage;
372
+ declare function setStorage(storage: BlogPlatformStorage): void;
373
+
374
+ interface AuthorArchive {
375
+ author: PublishedAuthor;
376
+ articles: PublishedArticle[];
377
+ }
378
+ interface AuthorArchiveOptions {
379
+ limit?: number;
380
+ offset?: number;
381
+ }
382
+ declare function getAuthorsFromArticles(articles: readonly PublishedArticle[]): PublishedAuthor[];
383
+ declare function getAuthorArchive(slug: string, articles: readonly PublishedArticle[]): AuthorArchive | null;
384
+ declare function loadAuthorArchive(slug: string, options?: AuthorArchiveOptions, storage?: BlogPlatformStorage): Promise<AuthorArchive | null>;
385
+
267
386
  interface TopicArchive {
268
387
  topic: PublishedTopic;
269
388
  articles: PublishedArticle[];
@@ -290,6 +409,16 @@ interface BlogTopicArchivePageProps {
290
409
  declare function BlogTopicsIndexPage({ articles, basePath, title, description, className, theme, }: BlogTopicsIndexPageProps): react_jsx_runtime.JSX.Element;
291
410
  declare function BlogTopicArchivePage({ articles, topicSlug, basePath, className, theme, }: BlogTopicArchivePageProps): react_jsx_runtime.JSX.Element;
292
411
 
412
+ interface BlogAuthorsIndexPageProps {
413
+ articles: PublishedArticle[];
414
+ basePath?: string;
415
+ title?: string;
416
+ description?: string;
417
+ className?: string;
418
+ theme?: BlogTheme;
419
+ }
420
+ declare function BlogAuthorsIndexPage({ articles, basePath, title, description, className, theme, }: BlogAuthorsIndexPageProps): react_jsx_runtime.JSX.Element;
421
+
293
422
  /**
294
423
  * Authentication configuration interface
295
424
  * Customers can implement this to customize authentication behavior
@@ -389,73 +518,6 @@ declare function initEnvAuth(envVarName?: string): void;
389
518
  */
390
519
  declare function shouldSkipAuth(): boolean;
391
520
 
392
- /**
393
- * Database interface for blog platform storage
394
- *
395
- * This provides a simple interface for storing published articles
396
- * without coupling to any specific database implementation.
397
- * Customers can implement this interface with their preferred database.
398
- */
399
-
400
- interface BlogPlatformStorage {
401
- /**
402
- * Store a published article
403
- */
404
- saveArticle(article: PublishedArticle, idempotencyKey: string): Promise<PublishResult>;
405
- /**
406
- * Get an article by slug
407
- */
408
- getArticle(slug: string): Promise<PublishedArticle | null>;
409
- /**
410
- * List all published articles
411
- */
412
- listArticles(options?: {
413
- limit?: number;
414
- offset?: number;
415
- sortBy?: "publishedAt" | "updatedAt";
416
- order?: "asc" | "desc";
417
- }): Promise<{
418
- articles: PublishedArticle[];
419
- total: number;
420
- }>;
421
- /**
422
- * Remove an article by slug
423
- */
424
- removeArticle(slug: string, idempotencyKey: string): Promise<PublishResult>;
425
- /**
426
- * Check if an operation was already performed (idempotency)
427
- */
428
- checkIdempotency(idempotencyKey: string): Promise<PublishResult | null>;
429
- /**
430
- * Store the result of an operation for idempotency
431
- */
432
- storeOperationResult(idempotencyKey: string, result: PublishResult): Promise<void>;
433
- }
434
- /**
435
- * Default in-memory storage implementation for development/testing
436
- * IMPORTANT: This is not suitable for production use!
437
- */
438
- declare class InMemoryStorage implements BlogPlatformStorage {
439
- private articles;
440
- private operations;
441
- saveArticle(article: PublishedArticle, idempotencyKey: string): Promise<PublishResult>;
442
- getArticle(slug: string): Promise<PublishedArticle | null>;
443
- listArticles(options?: {
444
- limit?: number;
445
- offset?: number;
446
- sortBy?: "publishedAt" | "updatedAt";
447
- order?: "asc" | "desc";
448
- }): Promise<{
449
- articles: PublishedArticle[];
450
- total: number;
451
- }>;
452
- removeArticle(slug: string, idempotencyKey: string): Promise<PublishResult>;
453
- checkIdempotency(idempotencyKey: string): Promise<PublishResult | null>;
454
- storeOperationResult(idempotencyKey: string, result: PublishResult): Promise<void>;
455
- }
456
- declare function getStorage(): BlogPlatformStorage;
457
- declare function setStorage(storage: BlogPlatformStorage): void;
458
-
459
521
  /**
460
522
  * Rate limiting for blog platform API endpoints
461
523
  *
@@ -1369,7 +1431,7 @@ interface SitemapOptions {
1369
1431
  /**
1370
1432
  * Valid sitemap change frequency values as per XML sitemap standard.
1371
1433
  */
1372
- type SitemapChangeFreq = 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
1434
+ type SitemapChangeFreq = "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
1373
1435
  /**
1374
1436
  * Next.js MetadataRoute.Sitemap compatible entry.
1375
1437
  * This matches the expected format for Next.js sitemap generation.
@@ -1885,4 +1947,4 @@ declare function OPTIONS(request: NextRequest): Promise<NextResponse>;
1885
1947
 
1886
1948
  declare const PACKAGE_VERSION: string;
1887
1949
 
1888
- export { ApiKeyAuth, type AuthConfig, type AuthResult, ArticlePage as BlogArticlePage, BlogHomePage, type BlogPlatformConfig, type BlogPlatformStorage, type BlogPostingSchema, type BlogSitemapConfig, type BlogTheme, BlogTopicArchivePage, BlogTopicsIndexPage, type BreadcrumbListSchema, Button, type CanonicalUrlOptions, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, DrizzleStorage, EnvAuth, type GeneratedMetadata, InMemoryStorage, type LinkTag, type MetaTag, type MetadataOptions, type NextjsSitemapEntry, NoAuth, type OrganizationSchema, PACKAGE_VERSION, Pagination$1 as Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, type PersonSchema, PublishResult, PublishedArticle, PublishedAuthor, PublishedSite, PublishedTopic, type RateLimitConfig, type RateLimitResult, Pagination as ShadcnPagination, type SitemapChangeFreq, type SitemapData, type SitemapEntry, type SitemapOptions, StructuredDataError, type StructuredDataOptions, type TopicArchive, UninitializedAuth, type WebSiteSchema, autoConfigureRateLimit, bpArticles, bpOperations, buildBlogPath, buttonVariants, checkRateLimit, cn, configureRateLimit, createBlogSitemapRoute, createDynamicBlogSitemap, createRateLimitErrorResponse, createStructuredDataScript, enforceRateLimit, extractKeywordsFromArticle, extractTableOfContents, extractTokenFromHeader, formatArticleDateMeta, generateArticleMetadata, generateArticleStructuredData, generateAuthorStructuredData, generateBlogHomeMetadata, generateBlogHomeStructuredData, generateBlogSitemapEntries, generateBreadcrumbStructuredData, generateCanonicalUrl, generateCanonicalUrlFromSlug, generateCompleteArticleStructuredData, generateOrganizationStructuredData, generatePaginationMetadata, generateSiteMetadata, generateSitemap, generateSitemapIndex, generateWebSiteStructuredData, getAllTopics, getArticlePrimaryTopic, getAuthConfig, getBaseUrl, getBlogThemeStyle, getDrizzleAdapterSql, getPathPrefixFromBlogUrl, getRateLimitHeaders, getStorage, getTopicArchive, initBlogPlatform, initBlogPlatformDev, initBlogPlatformOnce, initBlogPlatformProd, initEnvAuth, isValidCanonicalUrl, metadataToHtml, normalizeBlogBasePath, normalizeDomain, normalizeSlug, POST$1 as publishHandler, safeGenerateArticleStructuredData, setAuthConfig, setStorage, shouldSkipAuth, sitemapToXml, GET as slugExistsHandler, OPTIONS as slugExistsOptionsHandler, structuredDataToJsonLd, POST as unpublishHandler, updateArticleCanonicalUrl, usePagination, validateMetadata, validateSitemap, validateStructuredData, GET$1 as versionHandler };
1950
+ export { ApiKeyAuth, type AuthConfig, type AuthResult, type AuthorArchive, type AuthorArchiveOptions, ArticlePage as BlogArticlePage, AuthorPage as BlogAuthorPage, BlogAuthorsIndexPage, BlogHomePage, type BlogPlatformConfig, type BlogPlatformStorage, type BlogPostingSchema, type BlogSitemapConfig, type BlogTheme, BlogTopicArchivePage, BlogTopicsIndexPage, type BreadcrumbListSchema, Button, type CanonicalUrlOptions, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, DrizzleStorage, EnvAuth, type GeneratedMetadata, InMemoryStorage, type LinkTag, type MetaTag, type MetadataOptions, type NextjsSitemapEntry, NoAuth, type OrganizationSchema, PACKAGE_VERSION, Pagination$1 as Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, type PersonSchema, PublishResult, PublishedArticle, PublishedAuthor, PublishedSite, PublishedTopic, type RateLimitConfig, type RateLimitResult, Pagination as ShadcnPagination, type SitemapChangeFreq, type SitemapData, type SitemapEntry, type SitemapOptions, StructuredDataError, type StructuredDataOptions, type TopicArchive, UninitializedAuth, type WebSiteSchema, autoConfigureRateLimit, bpArticles, bpOperations, buildAuthorPath, buildBlogPath, buttonVariants, checkRateLimit, cn, configureRateLimit, createBlogSitemapRoute, createDynamicBlogSitemap, createRateLimitErrorResponse, createStructuredDataScript, enforceRateLimit, extractKeywordsFromArticle, extractTableOfContents, extractTokenFromHeader, formatArticleDateMeta, generateArticleMetadata, generateArticleStructuredData, generateAuthorPageMetadata, generateAuthorStructuredData, generateBlogHomeMetadata, generateBlogHomeStructuredData, generateBlogSitemapEntries, generateBreadcrumbStructuredData, generateCanonicalUrl, generateCanonicalUrlFromSlug, generateCompleteArticleStructuredData, generateOrganizationStructuredData, generatePaginationMetadata, generateSiteMetadata, generateSitemap, generateSitemapIndex, generateWebSiteStructuredData, getAllTopics, getArticlePrimaryTopic, getAuthConfig, getAuthorArchive, getAuthorsFromArticles, getBaseUrl, getBlogThemeStyle, getDrizzleAdapterSql, getPathPrefixFromBlogUrl, getRateLimitHeaders, getStorage, getTopicArchive, initBlogPlatform, initBlogPlatformDev, initBlogPlatformOnce, initBlogPlatformProd, initEnvAuth, isValidCanonicalUrl, loadAuthorArchive, metadataToHtml, normalizeBlogBasePath, normalizeDomain, normalizeSlug, POST$1 as publishHandler, safeGenerateArticleStructuredData, setAuthConfig, setStorage, shouldSkipAuth, sitemapToXml, GET as slugExistsHandler, OPTIONS as slugExistsOptionsHandler, structuredDataToJsonLd, POST as unpublishHandler, updateArticleCanonicalUrl, usePagination, validateMetadata, validateSitemap, validateStructuredData, GET$1 as versionHandler };