@asteroidcms/core-utils 0.1.8 → 0.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,342 @@
1
+ import { ApolloClient } from '@apollo/client';
2
+ import { ReactNode } from 'react';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+ import { ArticleSearchBoxProps } from '@asteroidcms/core-utils/client';
5
+ import { Metadata } from 'next';
6
+
7
+ interface CmsServerClientConfig {
8
+ cmsUrl: string;
9
+ apiKey: string;
10
+ graphqlPath?: string;
11
+ headers?: Record<string, string>;
12
+ /** Next.js ISR revalidate seconds, applied to the GraphQL fetch. */
13
+ revalidate?: number;
14
+ /** Next.js cache tags for on-demand revalidation. */
15
+ tags?: string[];
16
+ /** Escape hatch: provide a pre-built client (e.g. registerApolloClient). */
17
+ getClient?: () => ApolloClient;
18
+ }
19
+ interface CmsServerClient {
20
+ getClient: () => ApolloClient;
21
+ cmsUrl: string;
22
+ }
23
+ /**
24
+ * Build a server-only CMS client for use with `fetchCmsContent` / `cmsMutate`.
25
+ * The API key stays on the server. The client is memoized per-request via
26
+ * React `cache()` for query deduplication.
27
+ */
28
+ declare function createCmsServerClient(config: CmsServerClientConfig): CmsServerClient;
29
+
30
+ type FieldSelector = string | {
31
+ field: string;
32
+ as?: string;
33
+ };
34
+ type ReferenceExpansion = {
35
+ field: string;
36
+ as?: string;
37
+ single?: boolean;
38
+ select?: readonly (FieldSelector | ReferenceExpansion)[];
39
+ };
40
+ type ContentStatus = "DRAFT" | "PUBLISHED" | "ARCHIVED";
41
+ type CmsSearchCondition = {
42
+ field: string;
43
+ value: string;
44
+ mode?: string;
45
+ };
46
+ type UseCmsContentOptions = {
47
+ schema_slug: string;
48
+ entrySlug?: string;
49
+ select?: readonly (FieldSelector | ReferenceExpansion)[];
50
+ fullData?: boolean;
51
+ limit?: number;
52
+ offset?: number;
53
+ status?: ContentStatus;
54
+ filter?: Record<string, string | number | boolean | null>;
55
+ search?: CmsSearchCondition[];
56
+ variables?: Record<string, unknown>;
57
+ };
58
+
59
+ /** Theme colors used when generating dynamic OG images. */
60
+ interface AsteroidOgImagePalette {
61
+ background: string;
62
+ foreground: string;
63
+ accent: string;
64
+ accentMuted?: string;
65
+ mutedText?: string;
66
+ }
67
+ interface AsteroidOgImageParams {
68
+ title: string;
69
+ subtitle?: string;
70
+ eyebrow?: string;
71
+ type?: "article" | "listing";
72
+ }
73
+ /** Site-level identity used to build the Organization/WebSite JSON-LD graph. */
74
+ interface AsteroidOrganizationConfig {
75
+ logoUrl?: string;
76
+ contactEmail?: string;
77
+ contactPhone?: string;
78
+ address?: {
79
+ street?: string;
80
+ city?: string;
81
+ country?: string;
82
+ };
83
+ /** Generic list of profile URLs (LinkedIn, X, etc.). Maps to schema.org `sameAs`. */
84
+ socials?: string[];
85
+ }
86
+ /** Site-level SEO config passed into the headless Asteroid content components. */
87
+ interface AsteroidSeoConfig {
88
+ siteName: string;
89
+ baseUrl: string;
90
+ twitter?: string;
91
+ defaultDescription?: string;
92
+ defaultKeywords?: string;
93
+ /** Path prefix for the article collection (e.g. `/blog`, `/news`, `/docs`). Default: `/blog` */
94
+ articlePath?: string;
95
+ /**
96
+ * Human label for the article collection, used in default titles/eyebrows
97
+ * (e.g. "Blog", "News", "Documentation"). Default: `"Articles"`.
98
+ */
99
+ contentLabel?: string;
100
+ /**
101
+ * Override how a raw page/article title becomes the final `<title>`.
102
+ * Default: `(title) => `${title} | ${siteName}``.
103
+ */
104
+ titleTemplate?: (title: string) => string;
105
+ ogImage?: {
106
+ palette: AsteroidOgImagePalette;
107
+ /** OG image API route path. Default: `/api/og` */
108
+ apiPath?: string;
109
+ };
110
+ /** Override dynamic OG image URL generation (e.g. custom CDN or renderer). */
111
+ getOgImageUrl?: (params: AsteroidOgImageParams) => string | undefined;
112
+ /** Site identity for the Organization/WebSite JSON-LD graph. */
113
+ organization?: AsteroidOrganizationConfig;
114
+ /** Extra schema.org @graph nodes (e.g. a ProfessionalService node). */
115
+ extraJsonLdNodes?: object[];
116
+ /** CMS base URL for resolving featured-image URLs. Required server-side for featured images. */
117
+ cmsUrl?: string;
118
+ /** URL to a PWA manifest.json. When set, a `<link rel="manifest">` is emitted. */
119
+ manifestUrl?: string;
120
+ /** Site-wide default robots policy. Per-page overrides are available on builder options. */
121
+ noindex?: boolean;
122
+ }
123
+
124
+ /** schema.org Article subtypes; pick per content kind. */
125
+ type ArticleJsonLdType = "Article" | "BlogPosting" | "NewsArticle" | "TechArticle" | (string & {});
126
+
127
+ /** Resolves a CMS asset id (or passthrough URL) to an absolute media URL. */
128
+ type ArticleImageResolver = (idOrUrl?: string) => string;
129
+ /** Minimal article post shape shared across Asteroid CMS apps. */
130
+ interface AsteroidArticlePost {
131
+ slug: string;
132
+ title: string;
133
+ description?: string;
134
+ featured_image?: string;
135
+ featured?: boolean;
136
+ published_date?: string | null;
137
+ category?: {
138
+ slug: string;
139
+ name: string;
140
+ description?: string;
141
+ };
142
+ }
143
+ interface AsteroidArticleCategoryGroup<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
144
+ categoryName: string;
145
+ categorySlug: string;
146
+ posts: TPost[];
147
+ }
148
+ /** Runtime-agnostic computed state shared by client + server listing. */
149
+ interface ArticlesViewState<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
150
+ posts: TPost[];
151
+ featured: TPost | null;
152
+ rest: TPost[];
153
+ categoryGroups: AsteroidArticleCategoryGroup<TPost>[];
154
+ isEmpty: boolean;
155
+ isSearching: boolean;
156
+ /** The effective (already-debounced, for client) search query. */
157
+ searchQuery: string;
158
+ }
159
+ type AsteroidArticlesEmptyReason = "error" | "no-posts" | "no-results";
160
+ interface AsteroidArticlesSearchParams {
161
+ value: string;
162
+ onChange: (value: string) => void;
163
+ onSubmit: (event: {
164
+ preventDefault: () => void;
165
+ }) => void;
166
+ }
167
+ interface AsteroidArticlesHeaderParams {
168
+ eyebrow: ReactNode;
169
+ title: ReactNode;
170
+ description?: ReactNode;
171
+ search: ReactNode;
172
+ }
173
+ interface AsteroidArticlesFeaturedCardParams<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
174
+ post: TPost;
175
+ cmsImage: ArticleImageResolver;
176
+ }
177
+ interface AsteroidArticlesPostCardParams<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
178
+ post: TPost;
179
+ index: number;
180
+ group: AsteroidArticleCategoryGroup<TPost>;
181
+ cmsImage: ArticleImageResolver;
182
+ }
183
+ interface AsteroidArticlesPostGridParams<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
184
+ posts: TPost[];
185
+ group: AsteroidArticleCategoryGroup<TPost>;
186
+ children: ReactNode;
187
+ }
188
+ interface AsteroidArticlesCategoryHeadingParams<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
189
+ group: AsteroidArticleCategoryGroup<TPost>;
190
+ }
191
+ interface AsteroidArticlesCategoryGroupParams<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
192
+ group: AsteroidArticleCategoryGroup<TPost>;
193
+ defaultContent: ReactNode;
194
+ }
195
+ interface AsteroidArticlesEmptyParams {
196
+ reason: AsteroidArticlesEmptyReason;
197
+ searchQuery?: string;
198
+ error?: unknown;
199
+ }
200
+ interface AsteroidArticlesContentParams {
201
+ featured: ReactNode;
202
+ groups: ReactNode;
203
+ noSearchResults: ReactNode;
204
+ }
205
+
206
+ interface AsteroidArticlePagePost {
207
+ slug: string;
208
+ title: string;
209
+ description?: string;
210
+ content?: string;
211
+ featured_image?: string;
212
+ tags?: string;
213
+ published_date?: string | null;
214
+ category?: {
215
+ slug: string;
216
+ name: string;
217
+ };
218
+ author?: {
219
+ name: string;
220
+ bio?: string;
221
+ };
222
+ }
223
+ type Slot<TPost> = (params: {
224
+ post: TPost;
225
+ cmsImage: ArticleImageResolver;
226
+ }) => ReactNode;
227
+ interface ArticleBodyRenderProps<TPost extends AsteroidArticlePagePost = AsteroidArticlePagePost> {
228
+ backLink?: ReactNode;
229
+ renderHeader?: Slot<TPost>;
230
+ renderMeta?: Slot<TPost>;
231
+ renderDescription?: Slot<TPost>;
232
+ renderFeaturedImage?: Slot<TPost>;
233
+ renderToc?: Slot<TPost>;
234
+ renderContent?: Slot<TPost>;
235
+ renderPreArticle?: Slot<TPost>;
236
+ renderMidArticle?: Slot<TPost>;
237
+ renderPostArticle?: Slot<TPost>;
238
+ renderTags?: Slot<TPost>;
239
+ renderAuthorDetails?: Slot<TPost>;
240
+ renderRelatedPosts?: (params: {
241
+ post: TPost;
242
+ relatedPosts: TPost[];
243
+ cmsImage: ArticleImageResolver;
244
+ }) => ReactNode;
245
+ renderCTA?: Slot<TPost>;
246
+ }
247
+
248
+ type Select = NonNullable<UseCmsContentOptions["select"]>;
249
+ interface ArticleSourceConfig<TPost extends AsteroidArticlePost = AsteroidArticlePost, TDetail extends AsteroidArticlePagePost = AsteroidArticlePagePost> {
250
+ client: CmsServerClient;
251
+ schemaSlug: string;
252
+ listSelect: Select;
253
+ detailSelect: Select;
254
+ seo: AsteroidSeoConfig;
255
+ searchFields?: string[];
256
+ status?: ContentStatus;
257
+ articleType?: ArticleJsonLdType;
258
+ relatedLimit?: number;
259
+ groupPostsByCategory?: (posts: TPost[]) => unknown;
260
+ }
261
+ interface ArticleSource<TPost extends AsteroidArticlePost = AsteroidArticlePost, TDetail extends AsteroidArticlePagePost = AsteroidArticlePagePost> {
262
+ client: CmsServerClient;
263
+ schemaSlug: string;
264
+ listSelect: Select;
265
+ detailSelect: Select;
266
+ seo: AsteroidSeoConfig;
267
+ searchFields: string[];
268
+ status: ContentStatus;
269
+ articleType: ArticleJsonLdType;
270
+ relatedLimit: number;
271
+ groupPostsByCategory?: (posts: TPost[]) => unknown;
272
+ }
273
+ declare function buildSearchConditions(fields: string[], query?: string): CmsSearchCondition[] | undefined;
274
+ declare function defineArticleSource<TPost extends AsteroidArticlePost = AsteroidArticlePost, TDetail extends AsteroidArticlePagePost = AsteroidArticlePagePost>(config: ArticleSourceConfig<TPost, TDetail>): ArticleSource<TPost, TDetail>;
275
+ declare function fetchArticles<TPost extends AsteroidArticlePost = AsteroidArticlePost>(source: ArticleSource<TPost>, opts?: {
276
+ searchQuery?: string;
277
+ categorySlug?: string;
278
+ limit?: number;
279
+ }): Promise<TPost[]>;
280
+ declare function fetchArticle<TDetail extends AsteroidArticlePagePost = AsteroidArticlePagePost>(source: ArticleSource<AsteroidArticlePost, TDetail>, slug: string): Promise<TDetail | null>;
281
+ declare function fetchRelatedArticles<TDetail extends AsteroidArticlePagePost = AsteroidArticlePagePost>(source: ArticleSource<AsteroidArticlePost, TDetail>, post: TDetail, slug: string): Promise<TDetail[]>;
282
+
283
+ interface ArticlesListingRenderProps<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
284
+ eyebrow?: ReactNode;
285
+ title?: ReactNode;
286
+ description?: ReactNode;
287
+ renderRoot?: (params: {
288
+ children: ReactNode;
289
+ }) => ReactNode;
290
+ renderHeader?: (params: AsteroidArticlesHeaderParams) => ReactNode;
291
+ renderFeaturedCard?: (params: AsteroidArticlesFeaturedCardParams<TPost>) => ReactNode;
292
+ renderPostCard: (params: AsteroidArticlesPostCardParams<TPost>) => ReactNode;
293
+ renderCategoryHeading?: (params: AsteroidArticlesCategoryHeadingParams<TPost>) => ReactNode;
294
+ renderPostGrid?: (params: AsteroidArticlesPostGridParams<TPost>) => ReactNode;
295
+ renderCategoryGroup?: (params: AsteroidArticlesCategoryGroupParams<TPost>) => ReactNode;
296
+ renderSkeleton?: () => ReactNode;
297
+ renderEmpty?: (params: AsteroidArticlesEmptyParams) => ReactNode;
298
+ renderContent?: (params: AsteroidArticlesContentParams) => ReactNode;
299
+ }
300
+
301
+ interface AsteroidArticlesListingServerProps<TPost extends AsteroidArticlePost = AsteroidArticlePost> extends ArticlesListingRenderProps<TPost> {
302
+ source: ArticleSource<TPost>;
303
+ searchQuery?: string;
304
+ categorySlug?: string;
305
+ articleSlug?: string;
306
+ searchParamKey?: string;
307
+ limit?: number;
308
+ noindex?: boolean;
309
+ searchBoxProps?: Omit<ArticleSearchBoxProps, "paramKey">;
310
+ renderSearch?: (params: AsteroidArticlesSearchParams) => ReactNode;
311
+ renderJsonLd?: (state: ArticlesViewState<TPost>) => ReactNode;
312
+ groupPostsByCategory?: (posts: TPost[]) => AsteroidArticleCategoryGroup<TPost>[];
313
+ }
314
+ declare function AsteroidArticlesListingServer<TPost extends AsteroidArticlePost = AsteroidArticlePost>(props: AsteroidArticlesListingServerProps<TPost>): Promise<react_jsx_runtime.JSX.Element>;
315
+
316
+ interface AsteroidArticlePageServerProps<TDetail extends AsteroidArticlePagePost = AsteroidArticlePagePost> extends ArticleBodyRenderProps<TDetail> {
317
+ source: ArticleSource<AsteroidArticlePagePost, TDetail>;
318
+ slug: string;
319
+ articleType?: ArticleJsonLdType;
320
+ noindex?: boolean;
321
+ renderError?: (params: {
322
+ error?: unknown;
323
+ reason: "error" | "not-found";
324
+ }) => ReactNode;
325
+ renderJsonLd?: (params: {
326
+ post: TDetail;
327
+ }) => ReactNode;
328
+ }
329
+ declare function AsteroidArticlePageServer<TDetail extends AsteroidArticlePagePost = AsteroidArticlePagePost>(props: AsteroidArticlePageServerProps<TDetail>): Promise<react_jsx_runtime.JSX.Element>;
330
+
331
+ declare function generateListingMetadata(source: ArticleSource, options?: {
332
+ categoryName?: string;
333
+ categorySlug?: string;
334
+ noindex?: boolean;
335
+ }): Promise<Metadata>;
336
+ declare function generateArticleMetadata(source: ArticleSource<AsteroidArticlePost, AsteroidArticlePagePost>, paramsOrSlug: string | {
337
+ slug: string;
338
+ } | Promise<{
339
+ slug: string;
340
+ }>): Promise<Metadata>;
341
+
342
+ export { type ArticleSource, type ArticleSourceConfig, AsteroidArticlePageServer, type AsteroidArticlePageServerProps, AsteroidArticlesListingServer, type AsteroidArticlesListingServerProps, type CmsServerClient, type CmsServerClientConfig, buildSearchConditions, createCmsServerClient, defineArticleSource, fetchArticle, fetchArticles, fetchRelatedArticles, generateArticleMetadata, generateListingMetadata };