@ampless/runtime 1.0.0-alpha.50 → 1.0.0-alpha.51

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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Post, MediaMetadata, Config, ThemeModule, ThemeManifest, PluginPackageManifest } from 'ampless';
1
+ import { Post, MediaMetadata, AmplessPlugin, ContentFieldRenderer, PluginPublicRenderContext, Config, ThemeModule, ThemeManifest, PluginPackageManifest } from 'ampless';
2
2
  export { Config, Post, ThemeManifest } from 'ampless';
3
3
  import { Metadata } from 'next';
4
4
  import { ReactNode } from 'react';
@@ -142,6 +142,112 @@ interface PluginSettingsApi {
142
142
  }
143
143
  declare function createPluginSettings(storage: StorageApi): PluginSettingsApi;
144
144
 
145
+ /**
146
+ * Registry of `contentFields` renderers, plus a per-plugin context
147
+ * resolver. Built once per request from `cms.config.plugins` by
148
+ * `createPluginHead.contentFieldsRegistry` and threaded through every
149
+ * `renderBody(post, { contentFields, ctxForPlugin })` call.
150
+ *
151
+ * The map values capture the original plugin so the runtime can rebind
152
+ * `this` and resolve the right `PluginPublicRenderContext` for each
153
+ * renderer at call time.
154
+ */
155
+ interface ContentFieldRegistry {
156
+ tiptap: ReadonlyMap<string, {
157
+ plugin: AmplessPlugin;
158
+ renderer: Extract<ContentFieldRenderer, {
159
+ kind: 'tiptap';
160
+ }>;
161
+ }>;
162
+ markdownUrl: ReadonlyArray<{
163
+ plugin: AmplessPlugin;
164
+ renderer: Extract<ContentFieldRenderer, {
165
+ kind: 'markdown-url';
166
+ }>;
167
+ }>;
168
+ }
169
+ /**
170
+ * Build a `ContentFieldRegistry` from a list of plugins. Eagerly errors
171
+ * on duplicate `nodeType` / `pattern.source` registration across
172
+ * plugins so the misuse surfaces at config time, not at the first
173
+ * render that happens to walk the conflicting node.
174
+ */
175
+ declare function buildContentFieldRegistry(plugins: readonly AmplessPlugin[]): ContentFieldRegistry;
176
+ interface RenderBodyOptions {
177
+ contentFields?: ContentFieldRegistry;
178
+ /**
179
+ * Resolver invoked with the matched plugin to obtain the
180
+ * `PluginPublicRenderContext` for the renderer call. Wired by
181
+ * `createAmpless` so plugin renderers see the same `setting()`-bound
182
+ * ctx as `publicHead` / `publicBodyEnd`.
183
+ */
184
+ ctxForPlugin?: (plugin: AmplessPlugin) => PluginPublicRenderContext;
185
+ }
186
+ /**
187
+ * Async + ReactNode-shaped post body renderer. The runtime threads its
188
+ * `contentFields` registry + per-plugin ctx resolver in via `opts`; a
189
+ * raw direct caller (tests etc.) can omit `opts` to fall back to the
190
+ * default embed-free behaviour.
191
+ *
192
+ * Themes should call this through `ampless.renderBody(post)` (which
193
+ * supplies both opts) rather than calling this low-level function
194
+ * directly.
195
+ */
196
+ declare function renderBody(post: Post, opts?: RenderBodyOptions): ReactNode;
197
+ /**
198
+ * Sync string-shaped renderer used by `routes/raw.ts` and by format
199
+ * converters that need a `string` output. Skips the `contentFields`
200
+ * registry entirely — the raw route serves `format: 'html'` posts
201
+ * directly, and the format converters operate on tiptap / markdown
202
+ * source that doesn't expand embed shortcuts.
203
+ */
204
+ declare function renderBodyHtmlString(post: Post): string;
205
+ /**
206
+ * Convert a tiptap doc to its HTML form. Same renderer the public
207
+ * site uses. Defensive: tiptap accepts an HTML string as initial
208
+ * content and parses it on mount, but won't fire onUpdate until the
209
+ * user edits, so a format-switch chain (e.g. markdown -> tiptap ->
210
+ * markdown without editing) can still hand us a raw HTML string
211
+ * here. In that case, return it as-is rather than walking it as a
212
+ * malformed tiptap node and producing empty output.
213
+ */
214
+ declare function tiptapToHtml(doc: unknown): string;
215
+ /** Convert markdown to HTML using marked + GFM. */
216
+ declare function markdownToHtml(md: string): string;
217
+ /**
218
+ * Walk a tiptap doc and emit Markdown. Mirrors `renderTiptapString` in
219
+ * shape but produces markdown syntax. Loses anything markdown can't
220
+ * express (data attributes, image display modes, custom marks).
221
+ *
222
+ * Notes on info loss:
223
+ * - underline / highlight are not in GFM, so they fall back to the
224
+ * literal `<u>` / `<mark>` HTML tags (preserved as-is across round trips).
225
+ * - paragraph / heading textAlign cannot be expressed in markdown and
226
+ * is therefore lost on conversion.
227
+ *
228
+ * Same defensive path as tiptapToHtml: a string input means tiptap
229
+ * hasn't emitted JSON yet (the body is still the HTML we handed it).
230
+ * Route through htmlToMarkdown so the content survives.
231
+ */
232
+ declare function tiptapToMarkdown(doc: unknown): string;
233
+ /**
234
+ * Regex-based HTML -> Markdown converter. Handles the tag set the
235
+ * editor produces (`<p>` `<h1>`-`<h6>` `<strong>` `<em>` `<a>`
236
+ * `<img>` `<ul>` `<ol>` `<li>` `<code>` `<pre>` `<blockquote>` `<hr>`
237
+ * `<br>` `<u>` `<mark>` `<table>` task-list `<ul data-type="taskList">`).
238
+ * Decorative containers like `<div style="text-align:...">` are dropped.
239
+ *
240
+ * Tables are reduced to GFM pipe syntax via convertHtmlTable. Complex
241
+ * nested content inside cells (lists, other tables) is flattened to
242
+ * plain text.
243
+ *
244
+ * Not a full library, there are known limits like nested formatting
245
+ * inside list items potentially merging. Acceptable for a v0.x
246
+ * format-switch convenience; complex HTML round-trips shouldn't be
247
+ * relied on.
248
+ */
249
+ declare function htmlToMarkdown(html: string): string;
250
+
145
251
  interface PluginHeadApi {
146
252
  /**
147
253
  * React children safe to drop into `<head>`. Async because admin-
@@ -170,6 +276,33 @@ interface PluginHeadApi {
170
276
  * templates. Themes never call `dangerouslySetInnerHTML` themselves.
171
277
  */
172
278
  renderHtmlForPost(post: Post): Promise<PublicHtmlForPostResult>;
279
+ /**
280
+ * Page-level scripts aggregated across all installed plugins'
281
+ * `publicPostScript(post, ctx)` (Phase 7 `publicPostScript`
282
+ * capability). Themes invoke this via
283
+ * `ampless.publicPostScriptsForPage(posts)` after rendering post
284
+ * body / featured body. Descriptors are deduped by stable `id` so a
285
+ * widget script (e.g. x.com `widgets.js`) emits at most once per
286
+ * page regardless of how many embeds appear.
287
+ */
288
+ renderPostScriptsForPage(posts: readonly Post[]): Promise<ReactNode>;
289
+ /**
290
+ * In-body content renderer registry (Phase 7 `contentFields`
291
+ * capability). Built once at `createPluginHead` construction time so
292
+ * duplicate `nodeType` / `pattern.source` registrations throw eagerly
293
+ * (config error). Threaded into `rendering.ts:renderBody()` via
294
+ * `Ampless.renderBody`. Returns `null` when no plugin registered any
295
+ * `contentFields`.
296
+ */
297
+ readonly contentFieldsRegistry: ContentFieldRegistry;
298
+ /**
299
+ * Resolve the per-plugin `PluginPublicRenderContext` snapshot used by
300
+ * `contentFields` renderers. Same `settings()` binding the public
301
+ * surfaces (`publicHead` / `publicBodyEnd`) use. Async because it
302
+ * reads the S3 site-settings cache once per request via
303
+ * `pluginSettings.loadAll()`.
304
+ */
305
+ contextForPlugins(): Promise<(plugin: AmplessPlugin) => PluginPublicRenderContext>;
173
306
  }
174
307
  /**
175
308
  * Position-bucketed result of `renderHtmlForPost`. Themes embed these
@@ -325,53 +458,6 @@ declare function loadPackageManifest(packageName: string): PluginPackageManifest
325
458
  */
326
459
  declare const SUPPORTED_API_VERSION: 1;
327
460
 
328
- declare function renderBody(post: Post): string;
329
- /**
330
- * Convert a tiptap doc to its HTML form. Same renderer the public
331
- * site uses. Defensive: tiptap accepts an HTML string as initial
332
- * content and parses it on mount, but won't fire onUpdate until the
333
- * user edits, so a format-switch chain (e.g. markdown -> tiptap ->
334
- * markdown without editing) can still hand us a raw HTML string
335
- * here. In that case, return it as-is rather than walking it as a
336
- * malformed tiptap node and producing empty output.
337
- */
338
- declare function tiptapToHtml(doc: unknown): string;
339
- /** Convert markdown to HTML using marked + GFM. */
340
- declare function markdownToHtml(md: string): string;
341
- /**
342
- * Walk a tiptap doc and emit Markdown. Mirrors `renderTiptap` in
343
- * shape but produces markdown syntax. Loses anything markdown can't
344
- * express (data attributes, image display modes, custom marks).
345
- *
346
- * Notes on info loss:
347
- * - underline / highlight are not in GFM, so they fall back to the
348
- * literal `<u>` / `<mark>` HTML tags (preserved as-is across round trips).
349
- * - paragraph / heading textAlign cannot be expressed in markdown and
350
- * is therefore lost on conversion.
351
- *
352
- * Same defensive path as tiptapToHtml: a string input means tiptap
353
- * hasn't emitted JSON yet (the body is still the HTML we handed it).
354
- * Route through htmlToMarkdown so the content survives.
355
- */
356
- declare function tiptapToMarkdown(doc: unknown): string;
357
- /**
358
- * Regex-based HTML -> Markdown converter. Handles the tag set the
359
- * editor produces (`<p>` `<h1>`-`<h6>` `<strong>` `<em>` `<a>`
360
- * `<img>` `<ul>` `<ol>` `<li>` `<code>` `<pre>` `<blockquote>` `<hr>`
361
- * `<br>` `<u>` `<mark>` `<table>` task-list `<ul data-type="taskList">`).
362
- * Decorative containers like `<div style="text-align:...">` are dropped.
363
- *
364
- * Tables are reduced to GFM pipe syntax via convertHtmlTable. Complex
365
- * nested content inside cells (lists, other tables) is flattened to
366
- * plain text.
367
- *
368
- * Not a full library, there are known limits like nested formatting
369
- * inside list items potentially merging. Acceptable for a v0.x
370
- * format-switch convenience; complex HTML round-trips shouldn't be
371
- * relied on.
372
- */
373
- declare function htmlToMarkdown(html: string): string;
374
-
375
461
  type AmplifyServerContext = Parameters<typeof getProperties>[0];
376
462
  /** Metadata sufficient to choose between stream-back and 302 fallback. */
377
463
  interface ResolvedAssetMeta {
@@ -490,7 +576,40 @@ interface Ampless {
490
576
  * `dangerouslySetInnerHTML` themselves.
491
577
  */
492
578
  publicHtmlForPost(post: Post): Promise<PublicHtmlForPostResult>;
493
- renderBody(post: Post): string;
579
+ /**
580
+ * Page-level scripts aggregated across all installed plugins'
581
+ * `publicPostScript(post, ctx)` (Phase 7 `publicPostScript`
582
+ * capability). Themes render this in the post / home layout
583
+ * (typically just after the post body) so widgets like x.com's
584
+ * `widgets.js` load once per page even when several embedded posts
585
+ * appear. Descriptors are deduped by stable `id` so multiple posts
586
+ * needing the same script collapse into a single tag.
587
+ */
588
+ publicPostScriptsForPage(posts: readonly Post[]): Promise<ReactNode>;
589
+ /**
590
+ * Render a post body into a `ReactNode`. Async because admin-managed
591
+ * `settings.public` values feed into `contentFields` renderers via
592
+ * the same S3 site-settings cache the public head/body surfaces use;
593
+ * fetched once per request (Next.js fetch dedup on the
594
+ * `site-settings` cache tag). Themes call this directly in their
595
+ * post / home layouts:
596
+ *
597
+ * <div>{await ampless.renderBody(post)}</div>
598
+ *
599
+ * Phase 7 alpha breaking: this signature replaced the prior sync
600
+ * `string` return shape. The sync string flavour is still available
601
+ * via `renderBodyHtmlString` for routes (raw HTML response) that
602
+ * cannot await — but those callsites bypass the `contentFields`
603
+ * registry on purpose.
604
+ */
605
+ renderBody(post: Post): Promise<ReactNode>;
606
+ /**
607
+ * Sync string-shaped renderer used by the raw route handler. Skips
608
+ * the `contentFields` registry — the raw route serves
609
+ * `format: 'html', metadata.no_layout: true` posts whose bodies are
610
+ * complete HTML documents that don't expand embed shortcuts.
611
+ */
612
+ renderBodyHtmlString(post: Post): string;
494
613
  renderThemeCss(cssVars: Record<string, string>): string;
495
614
  publicAssetUrl(key: string): string;
496
615
  isStorageConfigured(): boolean;
@@ -516,4 +635,4 @@ interface Ampless {
516
635
  */
517
636
  declare function createAmpless(opts: CreateAmplessOpts): Ampless;
518
637
 
519
- export { type Ampless, type AmplessOutputs, COLOR_SCHEME_SETTING_KEY, type ColorScheme, type CreateAmplessOpts, DEFAULT_COLOR_SCHEME, type DataOutput, type EffectiveSiteSettings, type EffectiveThemeConfig, type ListPostsByTagOptions, type ListPostsOptions, type ListPostsResult, type MediaApi, type PluginHeadApi, type PluginSettingsApi, type PluginSettingsSnapshot, type PostsApi, type PublicHtmlForPostResult, type PublicMediaShape, type PublicPostConnectionShape, type PublicPostShape, type ResolvedAssetMeta, type ResolvedMedia, type ResolvedTheme, SUPPORTED_API_VERSION, type SeoApi, type SiteSettingsApi, type StorageOutput, type StreamS3Options, type ThemeActiveApi, type ThemeConfigApi, type ThemesRegistry, _resetStreamS3Cache, createAmpless, createPluginHead, createPluginSettings, escapeJsonLdInlineBody, htmlToMarkdown, loadPackageManifest, markdownToHtml, renderBody, renderThemeCss, streamS3Object, streamS3ObjectWithRunner, tiptapToHtml, tiptapToMarkdown, validateColorScheme };
638
+ export { type Ampless, type AmplessOutputs, COLOR_SCHEME_SETTING_KEY, type ColorScheme, type ContentFieldRegistry, type CreateAmplessOpts, DEFAULT_COLOR_SCHEME, type DataOutput, type EffectiveSiteSettings, type EffectiveThemeConfig, type ListPostsByTagOptions, type ListPostsOptions, type ListPostsResult, type MediaApi, type PluginHeadApi, type PluginSettingsApi, type PluginSettingsSnapshot, type PostsApi, type PublicHtmlForPostResult, type PublicMediaShape, type PublicPostConnectionShape, type PublicPostShape, type RenderBodyOptions, type ResolvedAssetMeta, type ResolvedMedia, type ResolvedTheme, SUPPORTED_API_VERSION, type SeoApi, type SiteSettingsApi, type StorageOutput, type StreamS3Options, type ThemeActiveApi, type ThemeConfigApi, type ThemesRegistry, _resetStreamS3Cache, buildContentFieldRegistry, createAmpless, createPluginHead, createPluginSettings, escapeJsonLdInlineBody, htmlToMarkdown, loadPackageManifest, markdownToHtml, renderBody, renderBodyHtmlString, renderThemeCss, streamS3Object, streamS3ObjectWithRunner, tiptapToHtml, tiptapToMarkdown, validateColorScheme };