@asteroidcms/core-utils 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +168 -1
- package/dist/client.cjs +505 -0
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +309 -1
- package/dist/client.d.ts +309 -1
- package/dist/client.js +500 -3
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +379 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +186 -1
- package/dist/index.d.ts +186 -1
- package/dist/index.js +368 -1
- package/dist/index.js.map +1 -1
- package/dist/next.cjs +211 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +114 -0
- package/dist/next.d.ts +114 -0
- package/dist/next.js +201 -0
- package/dist/next.js.map +1 -0
- package/dist/server.cjs +699 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +342 -0
- package/dist/server.d.ts +342 -0
- package/dist/server.js +669 -0
- package/dist/server.js.map +1 -0
- package/package.json +23 -2
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<h1>
|
|
3
3
|
<div style="display: inline-flex; align-items: center; gap: 4px;">
|
|
4
|
-
<img src="https://cms.theasteroid.tech/logo/
|
|
4
|
+
<img src="https://cms.theasteroid.tech/logo/logo.svg" alt="@asteroidcms" height="25px" />
|
|
5
5
|
<span>/core-utils</span>
|
|
6
6
|
</div>
|
|
7
7
|
</h1>
|
|
@@ -30,6 +30,17 @@ npm install @apollo/client-integration-nextjs # for nextjs (optional)
|
|
|
30
30
|
|
|
31
31
|
---
|
|
32
32
|
|
|
33
|
+
## Entry points
|
|
34
|
+
|
|
35
|
+
| Entry point | What it exports |
|
|
36
|
+
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
37
|
+
| `@asteroidcms/core-utils` | Core utilities: `fetchCmsContent`, `cmsMutate`, `buildCmsQuery`, `buildCmsMutation`, `cmsImage`, `parseRichText`, `getContentReadTime`, `extractHeadingsFromHtml`, `createApolloClient`, `AsteroidCMSProvider`. |
|
|
38
|
+
| `@asteroidcms/core-utils/client` | Client components and hooks (browser / React): `AsteroidCMSProvider`, `useCmsContent`, `useCmsMutate`, `useCmsImage`, `RichTextContent`, `extractHeadingsFromElement`. |
|
|
39
|
+
| `@asteroidcms/core-utils/next` | Next.js metadata helpers and SEO head component. Optional peer dep on `next`. |
|
|
40
|
+
| `@asteroidcms/core-utils/server` | Server components: `AsteroidArticlesListingServer`, `AsteroidArticlePageServer`, `defineArticleSource`, `createCmsServerClient`, `generateListingMetadata`, `generateArticleMetadata`. Also exports `fetchArticles`, `fetchArticle`, `fetchRelatedArticles`, `buildSearchConditions`. Server-only -- the CMS API key never reaches the browser. |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
33
44
|
## Quick start
|
|
34
45
|
|
|
35
46
|
Wrap your app once:
|
|
@@ -364,6 +375,8 @@ import { cmsImage } from "@asteroidcms/core-utils";
|
|
|
364
375
|
cmsImage(id, { cmsUrl: "https://cms-api.example.com" });
|
|
365
376
|
```
|
|
366
377
|
|
|
378
|
+
**Note:** Article render-prop callbacks (`renderPostCard` for listing, `renderRelatedPosts` for related posts, `renderContent`/`renderHeader` for article body) in both `AsteroidArticlesListing` (client) and `AsteroidArticlesListingServer` / `AsteroidArticlePageServer` (server) receive an injected `cmsImage(idOrUrl)` resolver. Prefer it over calling `useCmsImage()` directly so the same render function works in client and server components. A server equivalent for article listing and article page exists under `@asteroidcms/core-utils/server`.
|
|
379
|
+
|
|
367
380
|
---
|
|
368
381
|
|
|
369
382
|
## `getContentReadTime`
|
|
@@ -475,6 +488,160 @@ const client = createApolloClient({
|
|
|
475
488
|
|
|
476
489
|
---
|
|
477
490
|
|
|
491
|
+
## `@asteroidcms/core-utils/server`
|
|
492
|
+
|
|
493
|
+
Server-only entry. Guarded by `server-only` so it fails loudly if imported in a client module.
|
|
494
|
+
|
|
495
|
+
See [docs/web-sdk-react/13-server-article-components.md](./docs/web-sdk-react/13-server-article-components.md) for the full guide.
|
|
496
|
+
|
|
497
|
+
### `createCmsServerClient` + `defineArticleSource`
|
|
498
|
+
|
|
499
|
+
Define a source once in a server-only module and import it from any route that needs it.
|
|
500
|
+
|
|
501
|
+
```ts
|
|
502
|
+
// cms/articleSource.ts
|
|
503
|
+
import { createCmsServerClient, defineArticleSource } from "@asteroidcms/core-utils/server";
|
|
504
|
+
import type { AsteroidSeoConfig } from "@asteroidcms/core-utils";
|
|
505
|
+
|
|
506
|
+
const cmsClient = createCmsServerClient({
|
|
507
|
+
cmsUrl: process.env.CMS_API_BASE_URL!,
|
|
508
|
+
apiKey: process.env.CMS_API_KEY!, // server-only, NOT NEXT_PUBLIC
|
|
509
|
+
revalidate: 300,
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const articleSeo: AsteroidSeoConfig = {
|
|
513
|
+
siteName: "Acme",
|
|
514
|
+
baseUrl: "https://acme.example",
|
|
515
|
+
cmsUrl: process.env.CMS_API_BASE_URL!,
|
|
516
|
+
defaultDescription: "News and updates.",
|
|
517
|
+
articlePath: "/news",
|
|
518
|
+
contentLabel: "News",
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
export const articleSource = defineArticleSource({
|
|
522
|
+
client: cmsClient,
|
|
523
|
+
schemaSlug: "news",
|
|
524
|
+
listSelect: ["slug", "title", "description", "featured_image", "published_date",
|
|
525
|
+
{ field: "category", single: true, select: ["slug", "name"] }],
|
|
526
|
+
detailSelect: ["slug", "title", "description", "content", "tags", "featured_image", "published_date",
|
|
527
|
+
{ field: "category", single: true, select: ["slug", "name"] },
|
|
528
|
+
{ field: "author", single: true, select: ["name"] }],
|
|
529
|
+
seo: articleSeo,
|
|
530
|
+
relatedLimit: 3,
|
|
531
|
+
});
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
`createCmsServerClient` memoizes per request via React `cache` when available (React Server Components / React 19 / Next.js bundled React). On React 18 stable without `cache` it degrades to no per-request dedup but remains correct.
|
|
535
|
+
|
|
536
|
+
`defineArticleSource` required fields: `client`, `schemaSlug`, `listSelect`, `detailSelect`, `seo`. Optional: `searchFields`, `articleType`, `status`, `relatedLimit`, `groupPostsByCategory`.
|
|
537
|
+
|
|
538
|
+
### `AsteroidArticlesListingServer`
|
|
539
|
+
|
|
540
|
+
Read `searchParams` in the page and pass the query as `searchQuery`.
|
|
541
|
+
|
|
542
|
+
```tsx
|
|
543
|
+
// app/news/page.tsx
|
|
544
|
+
import { AsteroidArticlesListingServer, generateListingMetadata } from "@asteroidcms/core-utils/server";
|
|
545
|
+
import { articleSource } from "@/cms/articleSource";
|
|
546
|
+
|
|
547
|
+
export const generateMetadata = () => generateListingMetadata(articleSource);
|
|
548
|
+
|
|
549
|
+
export default async function NewsPage({
|
|
550
|
+
searchParams,
|
|
551
|
+
}: {
|
|
552
|
+
searchParams: Promise<{ q?: string }>;
|
|
553
|
+
}) {
|
|
554
|
+
const { q } = await searchParams;
|
|
555
|
+
return (
|
|
556
|
+
<AsteroidArticlesListingServer
|
|
557
|
+
source={articleSource}
|
|
558
|
+
searchQuery={q}
|
|
559
|
+
renderPostCard={({ post, cmsImage }) => (
|
|
560
|
+
<a href={`/news/${post.slug}`}>
|
|
561
|
+
<h2>{post.title}</h2>
|
|
562
|
+
</a>
|
|
563
|
+
)}
|
|
564
|
+
/>
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
Required prop: `renderPostCard` receives `{ post, cmsImage }`. Optional: `renderFeaturedCard`, `renderEmpty`, `renderSearch`, `categorySlug`, `searchParamKey`, `searchBoxProps`, and more.
|
|
570
|
+
|
|
571
|
+
### `AsteroidArticlePageServer`
|
|
572
|
+
|
|
573
|
+
```tsx
|
|
574
|
+
// app/news/[slug]/page.tsx
|
|
575
|
+
import { AsteroidArticlePageServer, generateArticleMetadata } from "@asteroidcms/core-utils/server";
|
|
576
|
+
import { articleSource } from "@/cms/articleSource";
|
|
577
|
+
|
|
578
|
+
export async function generateMetadata({
|
|
579
|
+
params,
|
|
580
|
+
}: {
|
|
581
|
+
params: Promise<{ slug: string }>;
|
|
582
|
+
}) {
|
|
583
|
+
return generateArticleMetadata(articleSource, params);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export default async function ArticlePage({
|
|
587
|
+
params,
|
|
588
|
+
}: {
|
|
589
|
+
params: Promise<{ slug: string }>;
|
|
590
|
+
}) {
|
|
591
|
+
const { slug } = await params;
|
|
592
|
+
return (
|
|
593
|
+
<AsteroidArticlePageServer
|
|
594
|
+
source={articleSource}
|
|
595
|
+
slug={slug}
|
|
596
|
+
renderHeader={({ post }) => <h1>{post.title}</h1>}
|
|
597
|
+
renderContent={({ post }) => (
|
|
598
|
+
<div dangerouslySetInnerHTML={{ __html: post.content ?? "" }} />
|
|
599
|
+
)}
|
|
600
|
+
renderRelatedPosts={({ relatedPosts, cmsImage }) => (
|
|
601
|
+
<ul>
|
|
602
|
+
{relatedPosts.map((related) => (
|
|
603
|
+
<li key={related.slug}>
|
|
604
|
+
<a href={`/news/${related.slug}`}>{related.title}</a>
|
|
605
|
+
</li>
|
|
606
|
+
))}
|
|
607
|
+
</ul>
|
|
608
|
+
)}
|
|
609
|
+
renderError={({ reason }) =>
|
|
610
|
+
reason === "not-found" ? <p>Not found.</p> : <p>Error loading post.</p>
|
|
611
|
+
}
|
|
612
|
+
/>
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
Render-prop slots: `renderHeader`, `renderContent`, `renderRelatedPosts` (receives `{ post, relatedPosts, cmsImage }`), `renderError`. Every render prop receives an injected `cmsImage(idOrUrl)` resolver -- do not call `useCmsImage()` inside render props.
|
|
618
|
+
|
|
619
|
+
### Metadata helpers
|
|
620
|
+
|
|
621
|
+
```ts
|
|
622
|
+
import { generateListingMetadata, generateArticleMetadata } from "@asteroidcms/core-utils/server";
|
|
623
|
+
|
|
624
|
+
// Listing page -- positional args: source first
|
|
625
|
+
export const generateMetadata = () => generateListingMetadata(articleSource);
|
|
626
|
+
|
|
627
|
+
// Category page
|
|
628
|
+
export async function generateMetadata({ params }) {
|
|
629
|
+
const { category } = await params;
|
|
630
|
+
return generateListingMetadata(articleSource, { categorySlug: category });
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Article page -- positional args: source first, then params or slug
|
|
634
|
+
export async function generateMetadata({ params }) {
|
|
635
|
+
return generateArticleMetadata(articleSource, params);
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### Low-level fetch helpers
|
|
640
|
+
|
|
641
|
+
`fetchArticles`, `fetchArticle`, `fetchRelatedArticles`, and `buildSearchConditions` are also exported for custom fetch logic outside the ready-made server components.
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
478
645
|
## Development
|
|
479
646
|
|
|
480
647
|
```bash
|