@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,570 @@
1
+ import { Locale, LocaleOptions, HomePageRef, CmsLink, Locales, CmsMainMenu, Routes } from './browser.js';
2
+ export { CmsCallToActionLinks, CmsFooter, CmsHeader, CmsMenuItem, CmsSocialLinks, CmsToHref, CreateRoutes, defineCmsToHref, defineLocales, defineRoutes } from './browser.js';
3
+ import { Media, Project, Area, Page, TeamMember, Tag, Question, QuestionTag, Category, Post, Appearance, Author, Banner, ImageLink, Form, HeaderFooter, Options as Options$1, Seo, MemberType } from './payload-types.js';
4
+
5
+ /** Which of the two URL helpers is asking: a download link or an `<img>`. */
6
+ type UploadUse = 'file' | 'image';
7
+ /**
8
+ * A site's own rule for where an upload is served from. Return a URL to use
9
+ * it, or `undefined` to let the default rule decide. Consulted for every
10
+ * production build, bucket or not, since a site's rewrites do not depend on
11
+ * one; in dev the URL the CMS returned is always used.
12
+ */
13
+ type ResolveUploadUrl = (media: Media, use: UploadUse) => string | null | undefined;
14
+ interface ApiOptions {
15
+ apiUrl: string;
16
+ apiKey: string;
17
+ projectSlug: string;
18
+ uploadsBaseUrl?: string;
19
+ protectionBypassSecret?: string;
20
+ isDev: boolean;
21
+ pageConcurrency?: number;
22
+ resolveUploadUrl?: ResolveUploadUrl;
23
+ }
24
+ type Collection<T> = {
25
+ docs: T[];
26
+ totalDocs: number;
27
+ limit: number;
28
+ totalPages: number;
29
+ page: number;
30
+ pagingCounter: number;
31
+ hasPrevPage: boolean;
32
+ hasNextPage: boolean;
33
+ prevPage: number | null;
34
+ nextPage: number | null;
35
+ };
36
+ declare function mapWithConcurrency<TItem, TResult>(items: TItem[], limit: number, worker: (item: TItem, index: number) => Promise<TResult>): Promise<TResult[]>;
37
+ type FallbackMode = 'null' | 'default';
38
+ type FetchCmsGlobalOptions = {
39
+ query?: any;
40
+ depth?: number;
41
+ locale?: Locale;
42
+ fallback?: FallbackMode;
43
+ };
44
+ type FetchCmsCollectionOptions = FetchCmsGlobalOptions & {
45
+ status?: string;
46
+ limit?: number;
47
+ sort?: string;
48
+ };
49
+ type Api = ReturnType<typeof createApi>;
50
+ declare function createApi(options: ApiOptions): {
51
+ apiUrl: (url: string, query?: any, scoped?: boolean) => string;
52
+ buildHeaders: () => Record<string, string>;
53
+ getUploadsPublicUrl: (endpoint: string) => string;
54
+ fileUrl: (media?: Media | null) => string | null;
55
+ imageUrl: (media?: Media | null) => string;
56
+ fetchCmsFile: (url: string) => Promise<Response>;
57
+ fetchCmsGlobal: <T>(global: string, fetchOptions?: FetchCmsGlobalOptions) => Promise<T>;
58
+ fetchCmsGlobalCollection: <T>(collection: string, fetchOptions?: FetchCmsGlobalOptions) => Promise<T>;
59
+ fetchCmsCollection: <T>(collection: string, fetchOptions?: FetchCmsCollectionOptions) => Promise<T[]>;
60
+ fetchCmsProject: () => Promise<Project>;
61
+ };
62
+
63
+ declare function createGlobalLoader<TInput, TOutput = TInput>(fetchFn: () => Promise<TInput>, options?: {
64
+ transform?: (data: TInput) => Promise<TOutput> | TOutput;
65
+ }): () => Promise<TOutput | NonNullable<TOutput>>;
66
+ interface Doc {
67
+ id: string;
68
+ slug?: string;
69
+ path?: string;
70
+ title?: string;
71
+ name?: string | null;
72
+ }
73
+ declare function createCollectionLoader<TInput extends Array<any>, TOutput extends Array<any> = TInput>(fetchFn: () => Promise<TInput>, options?: {
74
+ transform?: (data: TInput) => Promise<TOutput> | TOutput;
75
+ }): (() => Promise<TOutput>) & {
76
+ page: (page: number, limit: number, options?: {
77
+ excluded: Doc[];
78
+ }) => Promise<TOutput>;
79
+ find: (predicate: Parameters<any[]["find"]>[0]) => Promise<any>;
80
+ filter: (predicate: Parameters<any[]["filter"]>[0]) => Promise<any[]>;
81
+ };
82
+
83
+ interface WiredArea extends Omit<Area, 'page'> {
84
+ page?: Page | null;
85
+ }
86
+
87
+ interface WiredTeamMember extends Omit<TeamMember, 'slug' | 'areas'> {
88
+ slug: string;
89
+ areas?: {
90
+ docs?: (string | WiredArea)[];
91
+ hasNextPage?: boolean;
92
+ totalDocs?: number;
93
+ };
94
+ }
95
+
96
+ interface WiredTag extends Tag {
97
+ }
98
+
99
+ interface WiredQuestion extends Omit<Question, 'tags'> {
100
+ tags: QuestionTag[];
101
+ }
102
+
103
+ type CategoryBreadcrumb = {
104
+ label: string;
105
+ url: string;
106
+ };
107
+ interface WiredCategory extends Omit<Category, 'parent' | 'slug'> {
108
+ slug: string;
109
+ parent?: WiredCategory;
110
+ children: WiredCategory[];
111
+ path: string;
112
+ breadcrumbs: CategoryBreadcrumb[];
113
+ }
114
+ /**
115
+ * A category plus how many posts are filed under it. The count follows the same
116
+ * rule as `cms.posts.findByCategory`, counting a post under its primary and its
117
+ * secondary categories alike, so the number a listing prints is the number of
118
+ * entries the category page then shows.
119
+ */
120
+ interface CountedCategory extends WiredCategory {
121
+ postCount: number;
122
+ }
123
+
124
+ interface WiredPost extends Post {
125
+ coverImage?: Media | null;
126
+ category?: WiredCategory | null;
127
+ secondaryCategories?: WiredCategory[];
128
+ tags?: WiredTag[];
129
+ searchTokens: string;
130
+ }
131
+
132
+ interface WiredPage extends Omit<Page, 'parent'> {
133
+ parent?: WiredPage;
134
+ children: WiredPage[];
135
+ path: string;
136
+ }
137
+
138
+ type WiredAppearance = Appearance & {
139
+ branding: NonNullable<Appearance['branding']> & {
140
+ brandColor: NonNullable<NonNullable<Appearance['branding']>['brandColor']>;
141
+ logo: NonNullable<NonNullable<Appearance['branding']>['logo']>;
142
+ };
143
+ backgroundColor: {
144
+ lightTheme: string;
145
+ darkTheme: string;
146
+ };
147
+ };
148
+ /**
149
+ * What a blank Appearance field falls back to. Both are CSS values, and both
150
+ * are a site's own: the template states them in its theme file, so its
151
+ * defaults are variables that file defines.
152
+ */
153
+ interface AppearanceFallbacks {
154
+ brandColor: string;
155
+ backgroundColor: {
156
+ lightTheme: string;
157
+ darkTheme: string;
158
+ };
159
+ }
160
+ declare const DEFAULT_APPEARANCE_FALLBACKS: AppearanceFallbacks;
161
+
162
+ interface WiredAuthor extends Omit<Author, 'slug'> {
163
+ slug: string;
164
+ }
165
+
166
+ type Cms = ReturnType<typeof buildCms>;
167
+ declare function buildCms(ctx: Context, locale: Locale): {
168
+ locale: "es" | "en";
169
+ areas: {
170
+ all: (() => Promise<WiredArea[]>) & {
171
+ page: (page: number, limit: number, options?: {
172
+ excluded: Doc[];
173
+ }) => Promise<WiredArea[]>;
174
+ find: (predicate: Parameters<any[]["find"]>[0]) => Promise<any>;
175
+ filter: (predicate: Parameters<any[]["filter"]>[0]) => Promise<any[]>;
176
+ };
177
+ };
178
+ authors: {
179
+ all: (() => Promise<WiredAuthor[]>) & {
180
+ page: (page: number, limit: number, options?: {
181
+ excluded: Doc[];
182
+ }) => Promise<WiredAuthor[]>;
183
+ find: (predicate: Parameters<any[]["find"]>[0]) => Promise<any>;
184
+ filter: (predicate: Parameters<any[]["filter"]>[0]) => Promise<any[]>;
185
+ };
186
+ findBySlug: (slug: string) => Promise<any>;
187
+ };
188
+ banners: {
189
+ all: (() => Promise<Banner[]>) & {
190
+ page: (page: number, limit: number, options?: {
191
+ excluded: Doc[];
192
+ }) => Promise<Banner[]>;
193
+ find: (predicate: Parameters<any[]["find"]>[0]) => Promise<any>;
194
+ filter: (predicate: Parameters<any[]["filter"]>[0]) => Promise<any[]>;
195
+ };
196
+ };
197
+ appearance: () => Promise<WiredAppearance | NonNullable<WiredAppearance>>;
198
+ imageLinks: {
199
+ all: (() => Promise<ImageLink[]>) & {
200
+ page: (page: number, limit: number, options?: {
201
+ excluded: Doc[];
202
+ }) => Promise<ImageLink[]>;
203
+ find: (predicate: Parameters<any[]["find"]>[0]) => Promise<any>;
204
+ filter: (predicate: Parameters<any[]["filter"]>[0]) => Promise<any[]>;
205
+ };
206
+ };
207
+ categories: {
208
+ populated: () => Promise<WiredCategory[]>;
209
+ withCounts: () => Promise<CountedCategory[]>;
210
+ all: (() => Promise<WiredCategory[]>) & {
211
+ page: (page: number, limit: number, options?: {
212
+ excluded: Doc[];
213
+ }) => Promise<WiredCategory[]>;
214
+ find: (predicate: Parameters<any[]["find"]>[0]) => Promise<any>;
215
+ filter: (predicate: Parameters<any[]["filter"]>[0]) => Promise<any[]>;
216
+ };
217
+ wire: (category: Category | string | null | undefined) => Promise<WiredCategory | null | undefined>;
218
+ };
219
+ forms: {
220
+ all: (() => Promise<Form[]>) & {
221
+ page: (page: number, limit: number, options?: {
222
+ excluded: Doc[];
223
+ }) => Promise<Form[]>;
224
+ find: (predicate: Parameters<any[]["find"]>[0]) => Promise<any>;
225
+ filter: (predicate: Parameters<any[]["filter"]>[0]) => Promise<any[]>;
226
+ };
227
+ findById: (id: string) => Promise<Form | null>;
228
+ resolve: (form: Form | string) => Promise<Form | null>;
229
+ };
230
+ headerFooter: () => Promise<HeaderFooter>;
231
+ media: {
232
+ all: (() => Promise<Media[]>) & {
233
+ page: (page: number, limit: number, options?: {
234
+ excluded: Doc[];
235
+ }) => Promise<Media[]>;
236
+ find: (predicate: Parameters<any[]["find"]>[0]) => Promise<any>;
237
+ filter: (predicate: Parameters<any[]["filter"]>[0]) => Promise<any[]>;
238
+ };
239
+ };
240
+ options: () => Promise<Options$1>;
241
+ pages: {
242
+ all: (() => Promise<WiredPage[]>) & {
243
+ page: (page: number, limit: number, options?: {
244
+ excluded: Doc[];
245
+ }) => Promise<WiredPage[]>;
246
+ find: (predicate: Parameters<any[]["find"]>[0]) => Promise<any>;
247
+ filter: (predicate: Parameters<any[]["filter"]>[0]) => Promise<any[]>;
248
+ };
249
+ findByPath: (path: string) => Promise<WiredPage | undefined>;
250
+ findById: (id: string) => Promise<WiredPage | null>;
251
+ };
252
+ posts: {
253
+ all: (() => Promise<WiredPost[]>) & {
254
+ page: (page: number, limit: number, options?: {
255
+ excluded: Doc[];
256
+ }) => Promise<WiredPost[]>;
257
+ find: (predicate: Parameters<any[]["find"]>[0]) => Promise<any>;
258
+ filter: (predicate: Parameters<any[]["filter"]>[0]) => Promise<any[]>;
259
+ };
260
+ exists: () => Promise<boolean>;
261
+ page: (page: number, limit: number, options?: {
262
+ excluded: Doc[];
263
+ } | undefined) => Promise<WiredPost[]>;
264
+ latest: () => Promise<WiredPost[]>;
265
+ featured: () => Promise<WiredPost[]>;
266
+ related: (post: WiredPost, limit?: number) => Promise<any[]>;
267
+ findBySlug: (slug: string) => Promise<any>;
268
+ findByAuthor: (author: Author) => Promise<any[]>;
269
+ findByTeamMember: (memberId: string) => Promise<WiredPost[]>;
270
+ findByCategory: (category: Category) => Promise<any[]>;
271
+ findByTag: (tag: Tag) => Promise<any[]>;
272
+ nextInCategory: (post: WiredPost) => Promise<WiredPost | undefined>;
273
+ };
274
+ project: () => Promise<Project>;
275
+ questions: {
276
+ all: (() => Promise<WiredQuestion[]>) & {
277
+ page: (page: number, limit: number, options?: {
278
+ excluded: Doc[];
279
+ }) => Promise<WiredQuestion[]>;
280
+ find: (predicate: Parameters<any[]["find"]>[0]) => Promise<any>;
281
+ filter: (predicate: Parameters<any[]["filter"]>[0]) => Promise<any[]>;
282
+ };
283
+ filterByTag: (searchTag: QuestionTag) => Promise<any[]>;
284
+ };
285
+ seo: () => Promise<Seo>;
286
+ tags: {
287
+ all: (() => Promise<WiredTag[]>) & {
288
+ page: (page: number, limit: number, options?: {
289
+ excluded: Doc[];
290
+ }) => Promise<WiredTag[]>;
291
+ find: (predicate: Parameters<any[]["find"]>[0]) => Promise<any>;
292
+ filter: (predicate: Parameters<any[]["filter"]>[0]) => Promise<any[]>;
293
+ };
294
+ findBySlug: (slug: string) => Promise<any>;
295
+ };
296
+ teamMembers: {
297
+ all: (() => Promise<WiredTeamMember[]>) & {
298
+ page: (page: number, limit: number, options?: {
299
+ excluded: Doc[];
300
+ }) => Promise<WiredTeamMember[]>;
301
+ find: (predicate: Parameters<any[]["find"]>[0]) => Promise<any>;
302
+ filter: (predicate: Parameters<any[]["filter"]>[0]) => Promise<any[]>;
303
+ };
304
+ types: (() => Promise<MemberType[]>) & {
305
+ page: (page: number, limit: number, options?: {
306
+ excluded: Doc[];
307
+ }) => Promise<MemberType[]>;
308
+ find: (predicate: Parameters<any[]["find"]>[0]) => Promise<any>;
309
+ filter: (predicate: Parameters<any[]["filter"]>[0]) => Promise<any[]>;
310
+ };
311
+ findBySlug: (slug: string) => Promise<any>;
312
+ };
313
+ };
314
+
315
+ interface RateLimiter {
316
+ allow(key: string): boolean;
317
+ }
318
+ interface Options {
319
+ limit: number;
320
+ windowMs: number;
321
+ now?: () => number;
322
+ }
323
+ declare function createRateLimiter({ limit, windowMs, now, }: Options): RateLimiter;
324
+
325
+ type CmsResult = {
326
+ status: number;
327
+ id?: string;
328
+ fields?: string[];
329
+ };
330
+ interface FormOwnershipCheck {
331
+ formBelongsToProject(formId: string): Promise<boolean>;
332
+ }
333
+ interface ProxyDeps<TClient extends FormOwnershipCheck> {
334
+ projectId: string;
335
+ cms: TClient;
336
+ clientAddress: string;
337
+ rateLimiter?: RateLimiter;
338
+ /**
339
+ * The locales a submission may name. Defaults to every locale the CMS
340
+ * schema knows.
341
+ */
342
+ locales?: readonly string[];
343
+ }
344
+
345
+ interface SubmissionsCmsClient extends FormOwnershipCheck {
346
+ createSubmission(payload: {
347
+ form: string;
348
+ project: string;
349
+ data: Record<string, unknown>;
350
+ honeypot: string;
351
+ }, locale?: Locale): Promise<CmsResult>;
352
+ }
353
+ declare const MAX_SUBMISSION_BYTES: number;
354
+ declare function handleFormSubmission(request: Request, deps: ProxyDeps<SubmissionsCmsClient>): Promise<Response>;
355
+
356
+ interface UploadsCmsClient extends FormOwnershipCheck {
357
+ createUpload(file: File, payload: {
358
+ form: string;
359
+ project: string;
360
+ }): Promise<CmsResult>;
361
+ }
362
+ declare function handleFormUpload(request: Request, deps: ProxyDeps<UploadsCmsClient>): Promise<Response>;
363
+
364
+ type FormProxyCmsClient = SubmissionsCmsClient & UploadsCmsClient;
365
+ declare function createFormProxyCmsClient(api: Pick<Api, 'buildHeaders' | 'fetchCmsCollection'>, apiUrl: string): FormProxyCmsClient;
366
+
367
+ declare function refusedFieldNames(body: unknown): string[];
368
+
369
+ /**
370
+ * The part of an Astro `APIContext` the proxy reads. `clientAddress` is a
371
+ * getter that throws on a prerendered route, which is why it is read guarded.
372
+ */
373
+ interface RequestContext {
374
+ readonly clientAddress: string;
375
+ }
376
+ type FormProxy = ReturnType<typeof createFormProxy>;
377
+ declare function createFormProxy(cms: FormProxyCmsClient, project: () => Promise<Project>, locales: readonly string[]): {
378
+ cms: FormProxyCmsClient;
379
+ submissionLimiter: RateLimiter;
380
+ uploadLimiter: RateLimiter;
381
+ buildProxyDeps: (context: RequestContext, rateLimiter: RateLimiter) => Promise<ProxyDeps<FormProxyCmsClient>>;
382
+ handleFormSubmission: typeof handleFormSubmission;
383
+ handleFormUpload: typeof handleFormUpload;
384
+ };
385
+
386
+ interface BlogListing {
387
+ /**
388
+ * What the project calls its blog. `null` when no blog page is configured or
389
+ * the editor left the field blank, in which case the caller falls back to the
390
+ * `blog.title` translation.
391
+ */
392
+ name: string | null;
393
+ /** Intro copy for the listing, in the dual Lexical/HTML shape. */
394
+ description: Page['listingDescription'] | null;
395
+ }
396
+
397
+ /** What an extension collection is built from. */
398
+ interface ExtensionContext {
399
+ api: Context['api'];
400
+ locale: Locale;
401
+ /** The client's other services, for a collection that wires against them. */
402
+ cms: (locale: Locale) => Cms;
403
+ createCollectionLoader: typeof createCollectionLoader;
404
+ createGlobalLoader: typeof createGlobalLoader;
405
+ }
406
+ /**
407
+ * A collection only one site reads. It is built per locale and appears on
408
+ * `client.cms(locale)` under the key it was registered with.
409
+ */
410
+ type Extension<T = unknown> = (context: ExtensionContext) => T;
411
+ /** What each extension builds, by the key it appears under. */
412
+ type Extensions = Record<string, unknown>;
413
+ type CmsWith<E extends Extensions> = Cms & E;
414
+ interface ClientOptions<E extends Extensions = Record<never, never>> extends LocaleOptions {
415
+ /** The CMS origin, e.g. `https://cms.example.com`. */
416
+ apiUrl: string;
417
+ /** A project-scoped `api-keys` document's key. */
418
+ apiKey: string;
419
+ /** Every query is scoped to this project. */
420
+ projectSlug: string;
421
+ /** Public origin of the uploads bucket. Without it the CMS serves files. */
422
+ uploadsBaseUrl?: string;
423
+ /** Sent as `x-vercel-protection-bypass` when the CMS sits behind it. */
424
+ protectionBypassSecret?: string;
425
+ /** Dev serves every upload from the CMS's own URL. */
426
+ isDev: boolean;
427
+ /**
428
+ * The Appearance document, fetched at config time. Required for
429
+ * `cms.appearance()`; a config-time client leaves it out.
430
+ */
431
+ appearance?: Appearance;
432
+ /** What a blank Appearance field falls back to. */
433
+ appearanceFallbacks?: AppearanceFallbacks;
434
+ /**
435
+ * The Project document, fetched at config time. Required for
436
+ * `cms.project()` and the form proxy; a config-time client leaves it out.
437
+ */
438
+ project?: Project;
439
+ /** Collection pages fetched at a time. Default 4. */
440
+ pageConcurrency?: number;
441
+ /** A site's own rule for where an upload is served from. */
442
+ resolveUploadUrl?: ResolveUploadUrl;
443
+ /** Collections only this site reads, by the key they appear under. */
444
+ extensions?: {
445
+ [K in keyof E]: Extension<E[K]>;
446
+ };
447
+ }
448
+ type Client<E extends Extensions = Record<never, never>> = ReturnType<typeof createClient<E>>;
449
+ /**
450
+ * The CMS client a site builds once from its environment. Nothing in here
451
+ * reads the environment itself: an npm-installed package is externalised by
452
+ * Astro, so `import.meta.env` would be empty by the time it ran.
453
+ */
454
+ declare function createClient<E extends Extensions = Record<never, never>>(options: ClientOptions<E>): {
455
+ options: ClientOptions<E>;
456
+ locales: {
457
+ locales: readonly ("es" | "en")[];
458
+ defaultLocale: "es" | "en";
459
+ localePrefix: Readonly<Record<"es" | "en", string>>;
460
+ isLocale: (value: unknown) => value is Locale;
461
+ resolveLocale: (value: unknown) => Locale;
462
+ prefixPath: (path: string, locale: Locale) => string;
463
+ stripLocalePrefix: (path: string) => {
464
+ locale: Locale;
465
+ path: string;
466
+ };
467
+ };
468
+ api: {
469
+ apiUrl: (url: string, query?: any, scoped?: boolean) => string;
470
+ buildHeaders: () => Record<string, string>;
471
+ getUploadsPublicUrl: (endpoint: string) => string;
472
+ fileUrl: (media?: Media | null) => string | null;
473
+ imageUrl: (media?: Media | null) => string;
474
+ fetchCmsFile: (url: string) => Promise<Response>;
475
+ fetchCmsGlobal: <T>(global: string, fetchOptions?: FetchCmsGlobalOptions) => Promise<T>;
476
+ fetchCmsGlobalCollection: <T>(collection: string, fetchOptions?: FetchCmsGlobalOptions) => Promise<T>;
477
+ fetchCmsCollection: <T>(collection: string, fetchOptions?: FetchCmsCollectionOptions) => Promise<T[]>;
478
+ fetchCmsProject: () => Promise<Project>;
479
+ };
480
+ cms: (locale?: Locale) => CmsWith<E>;
481
+ routes: (locale: Locale, homePage?: HomePageRef) => {
482
+ readonly locale: "es" | "en";
483
+ readonly root: <T extends "index" | "search" = "index">(name?: T, ...params: Parameters<{
484
+ readonly index: () => string;
485
+ readonly search: () => string;
486
+ }[T]>) => ReturnType<{
487
+ readonly index: () => string;
488
+ readonly search: () => string;
489
+ }[T]>;
490
+ readonly blog: <T extends "page" | "author" | "category" | "post" | "tag" | "index" | "more" = "index">(name?: T, ...params: Parameters<{
491
+ readonly index: () => string;
492
+ readonly more: () => string;
493
+ readonly page: (n: number) => string;
494
+ readonly category: (path: string) => string;
495
+ readonly author: (slug: string) => string;
496
+ readonly post: (slug: string) => string;
497
+ readonly tag: (slug: string) => string;
498
+ }[T]>) => ReturnType<{
499
+ readonly index: () => string;
500
+ readonly more: () => string;
501
+ readonly page: (n: number) => string;
502
+ readonly category: (path: string) => string;
503
+ readonly author: (slug: string) => string;
504
+ readonly post: (slug: string) => string;
505
+ readonly tag: (slug: string) => string;
506
+ }[T]>;
507
+ readonly team: <T extends "index" | "member" | "vcard" = "index">(name?: T, ...params: Parameters<{
508
+ readonly index: () => string;
509
+ readonly member: (slug: string) => string;
510
+ readonly vcard: (slug: string) => string;
511
+ }[T]>) => ReturnType<{
512
+ readonly index: () => string;
513
+ readonly member: (slug: string) => string;
514
+ readonly vcard: (slug: string) => string;
515
+ }[T]>;
516
+ readonly page: (slugOrPage: string | Page) => string;
517
+ readonly search: () => string;
518
+ readonly find: (routeType: ("team-member" | "page" | "author" | "category" | "blog" | "post" | "tag") | string, slugOrPath: string | undefined) => string | undefined;
519
+ readonly fromCMSCollection: (collectionSlug: string, doc: {
520
+ slug: string;
521
+ } & Partial<Page>) => string | undefined;
522
+ };
523
+ cmsToHref: (link: Partial<CmsLink>, locale: Locale, homePage: HomePageRef) => string | undefined;
524
+ getHomePage: (locale: Locale) => Promise<WiredPage | null>;
525
+ getHomePageRef: (locale: Locale) => Promise<HomePageRef>;
526
+ getBlogListing: (locale: Locale) => Promise<BlogListing>;
527
+ /** Wires a single post document, e.g. a draft for live preview. */
528
+ wirePost: (post: Post | undefined, locale: Locale) => Promise<WiredPost | undefined>;
529
+ formProxy: {
530
+ cms: FormProxyCmsClient;
531
+ submissionLimiter: RateLimiter;
532
+ uploadLimiter: RateLimiter;
533
+ buildProxyDeps: (context: RequestContext, rateLimiter: RateLimiter) => Promise<ProxyDeps<FormProxyCmsClient>>;
534
+ handleFormSubmission: typeof handleFormSubmission;
535
+ handleFormUpload: typeof handleFormUpload;
536
+ };
537
+ /** The fetches `astro.config.mjs` makes before the build starts. */
538
+ config: {
539
+ fetchProjectSettings(): Promise<{
540
+ project: Project;
541
+ appearance: Appearance;
542
+ options: Options$1;
543
+ }>;
544
+ redirects(): Promise<Record<string, string>>;
545
+ fetchIconBuffer(icon: Media): Promise<false | Buffer<ArrayBuffer>>;
546
+ };
547
+ };
548
+
549
+ /** What every collection service is built from. Internal to the package. */
550
+ interface Context {
551
+ api: Api;
552
+ locales: Locales;
553
+ options: ClientOptions<any>;
554
+ cms: (locale: Locale) => Cms;
555
+ }
556
+
557
+ type MenuCollectionData = {
558
+ pages: WiredPage[];
559
+ categories: WiredCategory[];
560
+ tags: WiredTag[];
561
+ teamMembers: WiredTeamMember[];
562
+ };
563
+ /** The words the menu adds of its own. Site copy, so the site supplies it. */
564
+ type MenuLabels = {
565
+ /** Heads the team-members submenu: the site's `team.title`. */
566
+ team: string;
567
+ };
568
+ declare function resolveMenuItems(mainMenu: CmsMainMenu, data: MenuCollectionData, routes: Routes, labels: MenuLabels): CmsMainMenu;
569
+
570
+ export { type Api, type AppearanceFallbacks, type BlogListing, type CategoryBreadcrumb, type Client, type ClientOptions, type Cms, CmsLink, CmsMainMenu, type CmsResult, type CmsWith, type Collection, type CountedCategory, DEFAULT_APPEARANCE_FALLBACKS, type Doc, type Extension, type ExtensionContext, type Extensions, type FallbackMode, type FetchCmsCollectionOptions, type FetchCmsGlobalOptions, type FormOwnershipCheck, type FormProxy, type FormProxyCmsClient, HomePageRef, Locale, LocaleOptions, Locales, MAX_SUBMISSION_BYTES, type MenuCollectionData, type MenuLabels, type ProxyDeps, type RateLimiter, type RequestContext, type ResolveUploadUrl, Routes, type SubmissionsCmsClient, type UploadUse, type UploadsCmsClient, type WiredAppearance, type WiredArea, type WiredAuthor, type WiredCategory, type WiredPage, type WiredPost, type WiredQuestion, type WiredTag, type WiredTeamMember, createClient, createCollectionLoader, createFormProxy, createFormProxyCmsClient, createGlobalLoader, createRateLimiter, handleFormSubmission, handleFormUpload, mapWithConcurrency, refusedFieldNames, resolveMenuItems };