@postedin/cms-client 0.1.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.
Files changed (73) hide show
  1. package/README.md +66 -0
  2. package/bin/dissect/cli.mjs +138 -0
  3. package/bin/dissect/dissect.mjs +290 -0
  4. package/bin/profile/build.mjs +106 -0
  5. package/bin/profile/fetch-log.mjs +298 -0
  6. package/bin/profile/format.mjs +90 -0
  7. package/bin/profile/interference-summary.mjs +558 -0
  8. package/bin/profile/interference.mjs +604 -0
  9. package/bin/profile/measure.mjs +137 -0
  10. package/bin/profile/report.mjs +90 -0
  11. package/bin/profile/site-env.mjs +16 -0
  12. package/bin/profile/summarize.mjs +429 -0
  13. package/dist/browser.d.ts +145 -0
  14. package/dist/browser.js +11 -0
  15. package/dist/browser.js.map +1 -0
  16. package/dist/chunk-6V54ITTK.js +197 -0
  17. package/dist/chunk-6V54ITTK.js.map +1 -0
  18. package/dist/chunk-MNZ7DIGC.js +51 -0
  19. package/dist/chunk-MNZ7DIGC.js.map +1 -0
  20. package/dist/form-proxy/upload-policy.d.ts +40 -0
  21. package/dist/form-proxy/upload-policy.js +17 -0
  22. package/dist/form-proxy/upload-policy.js.map +1 -0
  23. package/dist/index.d.ts +570 -0
  24. package/dist/index.js +1636 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/payload-types.d.ts +8985 -0
  27. package/dist/payload-types.js +1 -0
  28. package/dist/payload-types.js.map +1 -0
  29. package/package.json +74 -0
  30. package/src/api.ts +387 -0
  31. package/src/blog-listing.ts +75 -0
  32. package/src/browser.ts +24 -0
  33. package/src/client.ts +144 -0
  34. package/src/cms-to-href.ts +70 -0
  35. package/src/cms.ts +86 -0
  36. package/src/collections/appearance.ts +94 -0
  37. package/src/collections/areas.ts +29 -0
  38. package/src/collections/authors.ts +27 -0
  39. package/src/collections/banners.ts +14 -0
  40. package/src/collections/categories.ts +111 -0
  41. package/src/collections/forms.ts +29 -0
  42. package/src/collections/header-footer.ts +19 -0
  43. package/src/collections/image-links.ts +14 -0
  44. package/src/collections/media.ts +18 -0
  45. package/src/collections/options.ts +10 -0
  46. package/src/collections/pages.ts +83 -0
  47. package/src/collections/posts.ts +249 -0
  48. package/src/collections/project.ts +16 -0
  49. package/src/collections/questions.ts +35 -0
  50. package/src/collections/seo.ts +10 -0
  51. package/src/collections/tags.ts +25 -0
  52. package/src/collections/team-members.ts +79 -0
  53. package/src/config-time.ts +98 -0
  54. package/src/context.ts +12 -0
  55. package/src/decode-html.ts +8 -0
  56. package/src/form-proxy/cms-client.ts +95 -0
  57. package/src/form-proxy/cms-errors.ts +73 -0
  58. package/src/form-proxy/cms-write.ts +44 -0
  59. package/src/form-proxy/http.ts +96 -0
  60. package/src/form-proxy/index.ts +73 -0
  61. package/src/form-proxy/rate-limit.ts +46 -0
  62. package/src/form-proxy/submissions.ts +88 -0
  63. package/src/form-proxy/types.ts +23 -0
  64. package/src/form-proxy/upload-policy.ts +92 -0
  65. package/src/form-proxy/uploads.ts +81 -0
  66. package/src/home-page.ts +83 -0
  67. package/src/index.ts +68 -0
  68. package/src/loader.ts +83 -0
  69. package/src/locales.ts +80 -0
  70. package/src/payload-types.ts +10854 -0
  71. package/src/placeholder.ts +9 -0
  72. package/src/resolve-menu-items.ts +184 -0
  73. package/src/routes.ts +184 -0
@@ -0,0 +1,83 @@
1
+ import type { WiredPage } from './collections/pages';
2
+ import type { Context } from './context';
3
+ import type { Locale } from './locales';
4
+ import type { Page } from './payload-types';
5
+ import type { HomePageRef } from './routes';
6
+
7
+ async function fetchHomePage(
8
+ ctx: Context,
9
+ locale: Locale,
10
+ ): Promise<WiredPage | null> {
11
+ const options = await ctx.cms(locale).options();
12
+ const configured = options.homepage?.page;
13
+ const id =
14
+ typeof configured === 'string' ? configured : (configured?.id ?? null);
15
+
16
+ if (!id) {
17
+ return null;
18
+ }
19
+
20
+ // Read the standalone document rather than the copy embedded in the options
21
+ // global: globals are fetched without locale fallback, so an untranslated
22
+ // homepage would come back with null title and null block copy and render as
23
+ // a blank page. It is also the one page with no other route to reach it, so
24
+ // default-locale content beats nothing at all.
25
+ const [doc] = await ctx.api.fetchCmsCollection<Page>('pages', {
26
+ locale,
27
+ fallback: 'default',
28
+ status: 'published',
29
+ limit: 1,
30
+ query: { where: { id: { equals: id } } },
31
+ });
32
+
33
+ if (!doc) {
34
+ return null;
35
+ }
36
+
37
+ const path = (doc.breadcrumbs?.at(-1)?.url ?? doc.slug ?? '').replace(
38
+ /^\//,
39
+ '',
40
+ );
41
+
42
+ // `children` stays empty: the homepage is rendered on its own, not as part of
43
+ // the page tree `cms.pages` builds.
44
+ return { ...doc, children: [], parent: undefined, path } as WiredPage;
45
+ }
46
+
47
+ export function createHomePage(ctx: Context) {
48
+ const cache = new Map<Locale, Promise<WiredPage | null>>();
49
+
50
+ /**
51
+ * The page configured as the site homepage, or `null` when the blog index is
52
+ * used instead. This is the single source of truth for "there is a homepage":
53
+ * `/` renders whatever it returns and `[...slug]` skips exactly the same
54
+ * document, so the route that serves the page and the route that steps aside
55
+ * for it can never disagree.
56
+ *
57
+ * Server-only. The result is cached per locale, so calling it from every
58
+ * component costs one fetch per locale per build.
59
+ */
60
+ function getHomePage(locale: Locale): Promise<WiredPage | null> {
61
+ let pending = cache.get(locale);
62
+
63
+ if (!pending) {
64
+ pending = fetchHomePage(ctx, locale);
65
+ cache.set(locale, pending);
66
+ }
67
+
68
+ return pending;
69
+ }
70
+
71
+ /**
72
+ * The homepage reduced to what URL building needs. Hand it to `createRoutes` or
73
+ * `cmsToHref` — or down to a hydrated island as a prop — so links pointing at
74
+ * that page resolve to `/` (or `/en/`) instead of its own slug route.
75
+ */
76
+ async function getHomePageRef(locale: Locale): Promise<HomePageRef> {
77
+ const page = await getHomePage(locale);
78
+
79
+ return page ? { id: page.id, path: page.path } : null;
80
+ }
81
+
82
+ return { getHomePage, getHomePageRef };
83
+ }
package/src/index.ts ADDED
@@ -0,0 +1,68 @@
1
+ export type {
2
+ Api,
3
+ Collection,
4
+ FallbackMode,
5
+ FetchCmsCollectionOptions,
6
+ FetchCmsGlobalOptions,
7
+ ResolveUploadUrl,
8
+ UploadUse,
9
+ } from './api';
10
+ export { mapWithConcurrency } from './api';
11
+ export type { BlogListing } from './blog-listing';
12
+ export {
13
+ type Client,
14
+ type ClientOptions,
15
+ type CmsWith,
16
+ createClient,
17
+ type Extension,
18
+ type ExtensionContext,
19
+ type Extensions,
20
+ } from './client';
21
+ export type { Cms } from './cms';
22
+ export type { CmsToHref } from './cms-to-href';
23
+ export type {
24
+ AppearanceFallbacks,
25
+ WiredAppearance,
26
+ } from './collections/appearance';
27
+ export { DEFAULT_APPEARANCE_FALLBACKS } from './collections/appearance';
28
+ export type { WiredArea } from './collections/areas';
29
+ export type { WiredAuthor } from './collections/authors';
30
+ export type {
31
+ CategoryBreadcrumb,
32
+ CountedCategory,
33
+ WiredCategory,
34
+ } from './collections/categories';
35
+ export type {
36
+ CmsCallToActionLinks,
37
+ CmsFooter,
38
+ CmsHeader,
39
+ CmsLink,
40
+ CmsMainMenu,
41
+ CmsMenuItem,
42
+ CmsSocialLinks,
43
+ } from './collections/header-footer';
44
+ export type { WiredPage } from './collections/pages';
45
+ export type { WiredPost } from './collections/posts';
46
+ export type { WiredQuestion } from './collections/questions';
47
+ export type { WiredTag } from './collections/tags';
48
+ export type { WiredTeamMember } from './collections/team-members';
49
+ export * from './form-proxy';
50
+ export { createCollectionLoader, createGlobalLoader, type Doc } from './loader';
51
+ export {
52
+ defineLocales,
53
+ type Locale,
54
+ type LocaleOptions,
55
+ type Locales,
56
+ } from './locales';
57
+ export {
58
+ type MenuCollectionData,
59
+ type MenuLabels,
60
+ resolveMenuItems,
61
+ } from './resolve-menu-items';
62
+ export {
63
+ type CreateRoutes,
64
+ defineRoutes,
65
+ type HomePageRef,
66
+ type Routes,
67
+ } from './routes';
68
+ export { defineCmsToHref } from './cms-to-href';
package/src/loader.ts ADDED
@@ -0,0 +1,83 @@
1
+ export function createGlobalLoader<TInput, TOutput = TInput>(
2
+ fetchFn: () => Promise<TInput>,
3
+ options?: {
4
+ transform?: (data: TInput) => Promise<TOutput> | TOutput;
5
+ },
6
+ ) {
7
+ let cache: Promise<TOutput> | TOutput | undefined;
8
+
9
+ return async () => {
10
+ if (!cache) {
11
+ cache = fetchFn().then(async (data) => {
12
+ if (options?.transform) {
13
+ return await options.transform(data);
14
+ }
15
+
16
+ return data as unknown as TOutput;
17
+ });
18
+ }
19
+
20
+ return await cache;
21
+ };
22
+ }
23
+
24
+ export interface Doc {
25
+ id: string;
26
+ slug?: string;
27
+ path?: string;
28
+ title?: string;
29
+ name?: string | null;
30
+ }
31
+
32
+ export function createCollectionLoader<
33
+ TInput extends Array<any>,
34
+ TOutput extends Array<any> = TInput,
35
+ >(
36
+ fetchFn: () => Promise<TInput>,
37
+ options?: {
38
+ transform?: (data: TInput) => Promise<TOutput> | TOutput;
39
+ },
40
+ ) {
41
+ let cache: Promise<TOutput> | TOutput | undefined;
42
+
43
+ const load = async () => {
44
+ if (!cache) {
45
+ cache = fetchFn().then(async (data) => {
46
+ if (options?.transform) {
47
+ return await options.transform(data);
48
+ }
49
+
50
+ return data as unknown as TOutput;
51
+ });
52
+ }
53
+
54
+ return await cache;
55
+ };
56
+
57
+ return Object.assign(load, {
58
+ page: async (
59
+ page: number,
60
+ limit: number,
61
+ options: { excluded: Doc[] } = { excluded: [] },
62
+ ) => {
63
+ const data = (await load()).filter((doc) => {
64
+ if (!options.excluded.length) {
65
+ return true;
66
+ }
67
+
68
+ return !options.excluded.find((ex) => ex.id === doc.id);
69
+ });
70
+
71
+ const first = (page - 1) * limit;
72
+ const last = first + limit;
73
+
74
+ return data.slice(first, last) as TOutput;
75
+ },
76
+ find: async (predicate: Parameters<any[]['find']>[0]) => {
77
+ return ((await load()) as TOutput).find(predicate);
78
+ },
79
+ filter: async (predicate: Parameters<any[]['filter']>[0]) => {
80
+ return ((await load()) as TOutput).filter(predicate);
81
+ },
82
+ });
83
+ }
package/src/locales.ts ADDED
@@ -0,0 +1,80 @@
1
+ import type { Config } from './payload-types';
2
+
3
+ /** Every locale the CMS schema knows. A site builds a subset of them. */
4
+ export type Locale = Config['locale'];
5
+
6
+ export interface LocaleOptions {
7
+ /** The locales this site builds, default first or not. */
8
+ locales: readonly Locale[];
9
+ /** The unprefixed locale. */
10
+ defaultLocale: Locale;
11
+ /** URL prefix per locale: `''` for the default, `'/en'` for English. */
12
+ localePrefix: Readonly<Record<Locale, string>>;
13
+ }
14
+
15
+ export type Locales = ReturnType<typeof defineLocales>;
16
+
17
+ /**
18
+ * The locale helpers every URL in a site is built with. Pure, so a hydrated
19
+ * island can use them as well as the server.
20
+ */
21
+ export function defineLocales({
22
+ locales,
23
+ defaultLocale,
24
+ localePrefix,
25
+ }: LocaleOptions) {
26
+ function isLocale(value: unknown): value is Locale {
27
+ return (
28
+ typeof value === 'string' &&
29
+ (locales as readonly string[]).includes(value)
30
+ );
31
+ }
32
+
33
+ function resolveLocale(value: unknown): Locale {
34
+ return isLocale(value) ? value : defaultLocale;
35
+ }
36
+
37
+ function prefixPath(path: string, locale: Locale): string {
38
+ const prefix = localePrefix[locale];
39
+ if (!prefix) {
40
+ return path;
41
+ }
42
+ // The site is built with `trailingSlash: 'always'`, so the prefix alone is a
43
+ // 404: `/en` is not a route, `/en/` is. Layout falls back to this for any
44
+ // locale a page does not name an alternate for, which made the homepage's
45
+ // own language switcher — and the `hreflang` beside it — a dead link.
46
+ if (path === '/') {
47
+ return `${prefix}/`;
48
+ }
49
+ return `${prefix}${path}`;
50
+ }
51
+
52
+ function stripLocalePrefix(path: string): {
53
+ locale: Locale;
54
+ path: string;
55
+ } {
56
+ for (const locale of locales) {
57
+ const prefix = localePrefix[locale];
58
+ if (!prefix) {
59
+ continue;
60
+ }
61
+ if (path === prefix) {
62
+ return { locale, path: '/' };
63
+ }
64
+ if (path.startsWith(`${prefix}/`)) {
65
+ return { locale, path: path.slice(prefix.length) };
66
+ }
67
+ }
68
+ return { locale: defaultLocale, path };
69
+ }
70
+
71
+ return {
72
+ locales,
73
+ defaultLocale,
74
+ localePrefix,
75
+ isLocale,
76
+ resolveLocale,
77
+ prefixPath,
78
+ stripLocalePrefix,
79
+ };
80
+ }