@inneropen/marvin-astro 1.1.0 → 1.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 +175 -9
- package/dist/embeds-BTNryQA6.d.ts +79 -0
- package/dist/index.d.ts +42 -3
- package/dist/index.js +143 -9
- package/dist/index.js.map +1 -1
- package/dist/renderers/index.d.ts +119 -0
- package/dist/renderers/index.js +251 -0
- package/dist/renderers/index.js.map +1 -0
- package/dist/types.d.ts +8 -1
- package/package.json +16 -6
- package/src/chrome.ts +242 -0
- package/src/client.ts +119 -0
- package/src/components/Embed.astro +119 -0
- package/src/components/EmbedLoader.astro +24 -0
- package/src/components/EntryRenderer.astro +40 -0
- package/src/components/FormRenderer.astro +188 -0
- package/src/components/LinkCard.astro +42 -0
- package/src/components/embed-frame.ts +168 -0
- package/src/components/embeds.css +137 -0
- package/src/components/index.ts +29 -0
- package/src/components/integration.ts +62 -0
- package/src/components/markdown.ts +4 -0
- package/src/components/registry.ts +34 -0
- package/src/components/renderers/ArticleRenderer.astro +56 -0
- package/src/components/renderers/FaqRenderer.astro +26 -0
- package/src/components/renderers/NavigationRenderer.astro +27 -0
- package/src/components/renderers/PageRenderer.astro +51 -0
- package/src/config.ts +149 -0
- package/src/embeds.ts +137 -0
- package/src/env.d.ts +5 -0
- package/src/fetch.ts +198 -0
- package/src/fields.ts +330 -0
- package/src/format.ts +63 -0
- package/src/index.ts +169 -0
- package/src/markdown.ts +84 -0
- package/src/normalize.ts +284 -0
- package/src/renderers/debug.ts +6 -0
- package/src/renderers/index.ts +48 -0
- package/src/renderers/registry.ts +133 -0
- package/src/renderers/resolve.ts +134 -0
- package/src/renderers/types.ts +71 -0
- package/src/renderers/validation-types.ts +23 -0
- package/src/renderers/validation.ts +55 -0
- package/src/repository.ts +190 -0
- package/src/sections.ts +39 -0
- package/src/site.ts +222 -0
- package/src/types.ts +188 -0
package/README.md
CHANGED
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
Site integration for [Marvin CMS](https://github.com/inneropen) on Astro: content repositories
|
|
4
4
|
with static fallbacks, site chrome from collections, and payload normalization.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Two packages, two concerns:
|
|
7
7
|
|
|
8
8
|
| Package | Concern |
|
|
9
9
|
|---|---|
|
|
10
10
|
| `@inneropen/marvin-sdk` | transport — HTTP client, entries, collections, assets |
|
|
11
|
-
|
|
|
12
|
-
|
|
11
|
+
| **`@inneropen/marvin-astro`** | **site integration — repositories, chrome, normalization, and the Astro components (forms, media embeds, entry-type renderers)** |
|
|
12
|
+
|
|
13
|
+
`@inneropen/marvin-renderers-core` has been folded in here — see
|
|
14
|
+
[Migrating from @inneropen/marvin-renderers-core](#migrating-from-inneropenmarvin-renderers-core).
|
|
13
15
|
|
|
14
16
|
A new site wires up to a Marvin workspace by installing one package, setting three env vars, and
|
|
15
17
|
writing only its own transform functions.
|
|
@@ -73,6 +75,9 @@ const { site, mainNavigation } = await marvin.getSiteChrome();
|
|
|
73
75
|
|
|
74
76
|
`posts.all()` · `posts.bySlug(slug)` · `posts.featured()` · `posts.allFeatured()` · `posts.reset()`
|
|
75
77
|
|
|
78
|
+
`bySlug` serves from the loaded list once `all()` has resolved — no extra request per detail
|
|
79
|
+
page — and only fetches the entry directly when nothing is loaded yet or the slug isn't in it.
|
|
80
|
+
|
|
76
81
|
## Why `hydrate`
|
|
77
82
|
|
|
78
83
|
The collection endpoint returns `PublishedEntryListItem`, which carries core fields and
|
|
@@ -82,6 +87,14 @@ which includes it.
|
|
|
82
87
|
So if a transform reads any schema-defined field — anything beyond title/slug/summary/metadata —
|
|
83
88
|
`hydrate: true` is required or those fields come back `undefined`. It costs one request per entry.
|
|
84
89
|
|
|
90
|
+
Those requests run at most `hydrateConcurrency` at a time (default 6) rather than all at once, and
|
|
91
|
+
a read that fails is retried with backoff (3 attempts) before the entry is dropped, so a single
|
|
92
|
+
transient failure neither loses an item nor latches the backend off for the rest of the build.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
createMarvinContent({ hydrateConcurrency: 4 });
|
|
96
|
+
```
|
|
97
|
+
|
|
85
98
|
## Field precedence
|
|
86
99
|
|
|
87
100
|
Every reader on `f` resolves `data_json` first and `metadata_json` second. An empty string counts
|
|
@@ -93,7 +106,8 @@ fall-through a legacy value that *is* set would never surface.
|
|
|
93
106
|
| `f.string(key)` `f.number(key)` `f.bool(key)` `f.list(key)` | scalars; `bool` reads `"true"`/`"1"`/`"yes"` |
|
|
94
107
|
| `f.oneOf(key, allowed, fallback)` | enum guard — replaces per-field `normalizeStatus`-style helpers |
|
|
95
108
|
| `f.raw(key)` `f.data()` `f.metadata()` | untyped escape hatches |
|
|
96
|
-
| `f.markdown(key?, { softBreaks })` | renders to HTML; `undefined` when there is nothing to render |
|
|
109
|
+
| `f.markdown(key?, { softBreaks, embeds })` | renders to HTML, with [media embeds](#media-embeds); `undefined` when there is nothing to render |
|
|
110
|
+
| `f.embed(key)` | the resolved embed for an `embed` field — render with `<Embed>` |
|
|
97
111
|
| `f.date(key)` `f.publishedAt()` | display date ("Mon DD, YYYY") / raw ISO stamp |
|
|
98
112
|
| `f.image(options)` `f.images(options)` `f.icon(options)` | resolved `{src, alt, focalPoint}` |
|
|
99
113
|
| `f.asset(options)` `f.assetByRole(...roles)` `f.assets()` | raw asset placements |
|
|
@@ -132,6 +146,153 @@ The default prefixes the entry's own non-navigation collection: an entry in `wor
|
|
|
132
146
|
becomes `/workshop-reference/<slug>`, an entry in no other collection becomes `/<slug>`. Override
|
|
133
147
|
when routes don't mirror collections.
|
|
134
148
|
|
|
149
|
+
## Media embeds
|
|
150
|
+
|
|
151
|
+
Paste a YouTube, Vimeo, Spotify, SoundCloud, Apple Music/Podcasts, Tidal or podcast link on its
|
|
152
|
+
own line in a markdown field and Marvin resolves it on the server. The published entry then
|
|
153
|
+
carries `embeds`, keyed by the URL exactly as written, each with Marvin-built `html` for the
|
|
154
|
+
site's embed mode and the structured fields behind it. The stored markdown never changes, so a
|
|
155
|
+
site that doesn't render embeds just shows the link.
|
|
156
|
+
|
|
157
|
+
**Markdown.** `f.markdown()` passes the entry's embeds automatically. A paragraph that is exactly
|
|
158
|
+
a URL with an `embeds` entry renders as that embed's `html`; `<url>`, `[text](url)`, a URL inside
|
|
159
|
+
a sentence and anything in code stay as they are. Opt a field out with `f.markdown('body',
|
|
160
|
+
{ embeds: false })`. Where you call the renderer yourself, pass the embeds:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { entryEmbeds } from '@inneropen/marvin-astro';
|
|
164
|
+
|
|
165
|
+
const bodyHtml = await marvin.renderMarkdown(body, { embeds: entryEmbeds(entry) });
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**`embed` fields.** An `embed` field's value is the provider URL; `f.embed(key)` returns its
|
|
169
|
+
resolved embed. Render it with `<Embed>`, which builds the player, facade or link card from the
|
|
170
|
+
structured fields:
|
|
171
|
+
|
|
172
|
+
```astro
|
|
173
|
+
---
|
|
174
|
+
import { Embed } from '@inneropen/marvin-astro/components';
|
|
175
|
+
const site = await marvin.getSite();
|
|
176
|
+
---
|
|
177
|
+
<Embed
|
|
178
|
+
embed={post.video}
|
|
179
|
+
mode={site.embeds?.mode}
|
|
180
|
+
consentText={site.embeds?.consentText}
|
|
181
|
+
frameSources={site.embeds?.frameSources}
|
|
182
|
+
/>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**The loader and CSS.** In `click_to_load` mode (Marvin's default) an embed is a facade — a
|
|
186
|
+
button and a plain link — and nothing loads from the provider until the visitor clicks. Put
|
|
187
|
+
`<EmbedLoader />` once in your layout:
|
|
188
|
+
|
|
189
|
+
```astro
|
|
190
|
+
---
|
|
191
|
+
import { EmbedLoader } from '@inneropen/marvin-astro/components';
|
|
192
|
+
---
|
|
193
|
+
<body>
|
|
194
|
+
<slot />
|
|
195
|
+
<EmbedLoader />
|
|
196
|
+
</body>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
On click (or Enter/Space) it swaps the button for the iframe. It builds the iframe only from the
|
|
200
|
+
facade's `data-marvin-embed-attrs`, keeps only `src`, `title`, `allow`, `sandbox`,
|
|
201
|
+
`referrerpolicy`, `loading` and `style`, refuses any `src` that isn't https on a host in
|
|
202
|
+
`data-marvin-embed-hosts`, and never parses HTML. It also brings the base `.marvin-embed` CSS:
|
|
203
|
+
responsive aspect ratio for video, fixed height for audio, facade and link-card styles. The
|
|
204
|
+
rules have zero specificity, so any site rule wins; theme through custom properties:
|
|
205
|
+
|
|
206
|
+
```css
|
|
207
|
+
.marvin-embed, .marvin-embed-link {
|
|
208
|
+
--marvin-embed-radius: 0;
|
|
209
|
+
--marvin-embed-bg: var(--ink);
|
|
210
|
+
--marvin-embed-fg: var(--paper);
|
|
211
|
+
--marvin-embed-max-width: 48rem;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The full list (`--marvin-embed-aspect`, `-height`, `-margin`, `-muted`, `-border`, `-focus`, …)
|
|
216
|
+
is at the top of `src/components/embeds.css`. For CSS without the loader (a `direct`-mode site),
|
|
217
|
+
`import '@inneropen/marvin-astro/components/embeds.css'`.
|
|
218
|
+
|
|
219
|
+
**CSP.** `site.embeds.frameSources` lists every origin the site's embeds may frame. Use it for a
|
|
220
|
+
`frame-src` directive:
|
|
221
|
+
|
|
222
|
+
```astro
|
|
223
|
+
---
|
|
224
|
+
const site = await marvin.getSite();
|
|
225
|
+
const frameSrc = ["'self'", ...(site.embeds?.frameSources ?? [])].join(' ');
|
|
226
|
+
---
|
|
227
|
+
<meta http-equiv="Content-Security-Policy" content={`frame-src ${frameSrc}`} />
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The loader script is small enough that Astro inlines it; if your CSP forbids inline scripts, hash
|
|
231
|
+
it or set `vite.build.assetsInlineLimit: 0` so Astro emits it as a file.
|
|
232
|
+
|
|
233
|
+
The embed types (`MarvinEmbed`, `MarvinSiteEmbeds`, …) are declared in this package, so none of
|
|
234
|
+
this needs a newer SDK.
|
|
235
|
+
|
|
236
|
+
## Components
|
|
237
|
+
|
|
238
|
+
`@inneropen/marvin-astro/components` ships Astro source, compiled by your site's build.
|
|
239
|
+
|
|
240
|
+
| Component | Use |
|
|
241
|
+
|---|---|
|
|
242
|
+
| `FormRenderer` | a public form from a submittable entry type's `formSchema`; unstyled, progressive-enhancement submit |
|
|
243
|
+
| `Embed`, `LinkCard`, `EmbedLoader` | media embeds (above) |
|
|
244
|
+
| `EntryRenderer` | picks the renderer for an entry from its entry type's `rendering` |
|
|
245
|
+
| `PageRenderer`, `ArticleRenderer`, `FaqRenderer`, `NavigationRenderer` | unstyled, semantic core renderers with `data-renderer`/`data-role` hooks |
|
|
246
|
+
|
|
247
|
+
Page and Article render the markdown body (with embeds); pass `renderMarkdown={marvin.renderMarkdown}`
|
|
248
|
+
to use your site's markdown options.
|
|
249
|
+
|
|
250
|
+
The registry and logic: `getRenderer`, `astroRegistry`, `coreRendererPackage` from
|
|
251
|
+
`@inneropen/marvin-astro/components` (or `/components/registry`), and the framework-agnostic
|
|
252
|
+
helpers — `resolveRendererName`, `resolveRendererConfig`, `extractBody`, `extractField`,
|
|
253
|
+
`getFeaturedAsset`, `createRegistry`, `createPackageRegistry`, `createRendererPackage`,
|
|
254
|
+
`validateRenderers`, … — from `@inneropen/marvin-astro/renderers`.
|
|
255
|
+
|
|
256
|
+
Build-time check that every rendered entry type has a renderer:
|
|
257
|
+
|
|
258
|
+
```js
|
|
259
|
+
// astro.config.mjs
|
|
260
|
+
import { marvinIntegration } from '@inneropen/marvin-astro/components/integration';
|
|
261
|
+
import { astroRegistry } from '@inneropen/marvin-astro/components/registry';
|
|
262
|
+
|
|
263
|
+
export default defineConfig({
|
|
264
|
+
integrations: [marvinIntegration({ registry: astroRegistry })],
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Options: `registry` (required), `apiUrl`, `siteToken` (default to the `MARVIN_*` env vars),
|
|
269
|
+
`strict` (throw instead of warn), `ignore` (entry-type slugs to skip).
|
|
270
|
+
|
|
271
|
+
Marvin's system entry types name their renderer package `@inneropen/marvin-renderers-core`; the
|
|
272
|
+
core renderers still answer to that name (and to `@inneropen/marvin-astro`).
|
|
273
|
+
|
|
274
|
+
## Migrating from @inneropen/marvin-renderers-core
|
|
275
|
+
|
|
276
|
+
The components and logic moved here with their names unchanged. Swap the import paths, then
|
|
277
|
+
remove `@inneropen/marvin-renderers-core` from `package.json`:
|
|
278
|
+
|
|
279
|
+
| Before (`@inneropen/marvin-renderers-core…`) | After (`@inneropen/marvin-astro…`) |
|
|
280
|
+
|---|---|
|
|
281
|
+
| `/astro` | `/components` |
|
|
282
|
+
| `/astro/registry` | `/components/registry` |
|
|
283
|
+
| `/astro/integration` | `/components/integration` |
|
|
284
|
+
| `/astro/renderers/PageRenderer.astro` (etc.) | `/components/renderers/PageRenderer.astro` |
|
|
285
|
+
| `/logic` | `/renderers` |
|
|
286
|
+
|
|
287
|
+
```diff
|
|
288
|
+
-import { FormRenderer } from '@inneropen/marvin-renderers-core/astro';
|
|
289
|
+
+import { FormRenderer } from '@inneropen/marvin-astro/components';
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Behaviour changes: Page/Article now render the body as markdown instead of injecting it raw, and
|
|
293
|
+
`EntryRenderer` renders core entry types without a custom registry (it silently rendered only
|
|
294
|
+
its slot before).
|
|
295
|
+
|
|
135
296
|
## The failure latch
|
|
136
297
|
|
|
137
298
|
A static build asks for content once per path. When the backend is down that means N failed
|
|
@@ -151,8 +312,8 @@ marvin.backend.clearLatch();
|
|
|
151
312
|
|
|
152
313
|
## SEO head (optional)
|
|
153
314
|
|
|
154
|
-
|
|
155
|
-
Astro stays an optional peer dependency.
|
|
315
|
+
`SeoHead` ships behind its own export path (as do the [components](#components)), so the core
|
|
316
|
+
package stays pure TypeScript and Astro stays an optional peer dependency.
|
|
156
317
|
|
|
157
318
|
```astro
|
|
158
319
|
---
|
|
@@ -176,18 +337,23 @@ verification tags. No styling, no site coupling.
|
|
|
176
337
|
| `@inneropen/marvin-astro` | `createMarvinContent` and every helper below it |
|
|
177
338
|
| `@inneropen/marvin-astro/types` | resolved types only (`ApiSite`, `ApiSeo`, `ApiSiteChrome`, …) |
|
|
178
339
|
| `@inneropen/marvin-astro/astro` | `SeoHead` |
|
|
340
|
+
| `@inneropen/marvin-astro/components` | `FormRenderer`, `Embed`, `LinkCard`, `EmbedLoader`, `EntryRenderer`, the core renderers, registry, `marvinIntegration` |
|
|
341
|
+
| `@inneropen/marvin-astro/components/registry` | `getRenderer`, `astroRegistry`, `coreRendererPackage` |
|
|
342
|
+
| `@inneropen/marvin-astro/components/integration` | `marvinIntegration` (for `astro.config.mjs`) |
|
|
343
|
+
| `@inneropen/marvin-astro/components/*` | individual components, `renderers/*.astro`, `embeds.css` |
|
|
344
|
+
| `@inneropen/marvin-astro/renderers` | renderer logic (compiled): resolve, registry, validation |
|
|
179
345
|
|
|
180
346
|
Beyond `createMarvinContent`, the pieces are usable on their own: `createBackend`,
|
|
181
347
|
`createFetcher`, `createRepository`, `createSiteLoader`, `createChromeLoader`,
|
|
182
|
-
`createFieldAccessor`, `createMarkdownRenderer`, `
|
|
183
|
-
the whole `normalize` surface.
|
|
348
|
+
`createFieldAccessor`, `createMarkdownRenderer`, `entryEmbeds`, `findEmbed`, `siteEmbeds`,
|
|
349
|
+
`formatDisplayDate`, `selectValuesForPage`, and the whole `normalize` surface.
|
|
184
350
|
|
|
185
351
|
## Development
|
|
186
352
|
|
|
187
353
|
```bash
|
|
188
354
|
npm install
|
|
189
355
|
npm run typecheck
|
|
190
|
-
npm test # vitest, fixture-driven, no network
|
|
356
|
+
npm test # vitest, fixture-driven, no network; .astro components via Astro's Container API
|
|
191
357
|
npm run build # tsup → dist/ with .d.ts
|
|
192
358
|
```
|
|
193
359
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media embeds: the published shapes Marvin hands a site, read tolerantly.
|
|
3
|
+
*
|
|
4
|
+
* Marvin resolves a pasted provider link (YouTube, Vimeo, Spotify, …) on the server and publishes
|
|
5
|
+
* the result next to the entry as `entry.embeds`, keyed by the URL exactly as the author wrote it.
|
|
6
|
+
* Stored markdown is never rewritten: a site that knows about embeds swaps the link for the
|
|
7
|
+
* player; an older site just shows the link.
|
|
8
|
+
*
|
|
9
|
+
* These types are declared here rather than imported from `@inneropen/marvin-sdk` so the package
|
|
10
|
+
* works with SDKs that predate embeds. Kind and status are open-ended so a newer server value
|
|
11
|
+
* doesn't break an older site's types.
|
|
12
|
+
*/
|
|
13
|
+
type MarvinEmbedKind = 'video' | 'audio' | 'podcast' | 'playlist';
|
|
14
|
+
type MarvinEmbedStatus = 'ok' | 'link' | 'unavailable';
|
|
15
|
+
type MarvinEmbedMode = 'direct' | 'click_to_load';
|
|
16
|
+
/**
|
|
17
|
+
* A Marvin-built iframe. Every value comes from Marvin's provider registry. Exactly one of
|
|
18
|
+
* `aspectRatio` / `height` is set; the other is `null`.
|
|
19
|
+
*/
|
|
20
|
+
type MarvinEmbedIframe = {
|
|
21
|
+
src: string;
|
|
22
|
+
title: string;
|
|
23
|
+
allow?: string | null;
|
|
24
|
+
sandbox?: string | null;
|
|
25
|
+
referrerpolicy?: string | null;
|
|
26
|
+
/** CSS aspect ratio for video players, e.g. `"16/9"` (`"9/16"` for Shorts); `null` otherwise. */
|
|
27
|
+
aspectRatio?: string | null;
|
|
28
|
+
/** Fixed pixel height for audio / podcast / playlist players; `null` for video. */
|
|
29
|
+
height?: number | null;
|
|
30
|
+
};
|
|
31
|
+
/** The plain link every embed falls back to. */
|
|
32
|
+
type MarvinEmbedLink = {
|
|
33
|
+
href: string;
|
|
34
|
+
title: string;
|
|
35
|
+
providerName: string;
|
|
36
|
+
};
|
|
37
|
+
/** Optional fields arrive as `null` rather than omitted; every reader here treats both alike. */
|
|
38
|
+
type MarvinEmbed = {
|
|
39
|
+
/** The URL exactly as written in the content. */
|
|
40
|
+
url: string;
|
|
41
|
+
canonicalUrl?: string | null;
|
|
42
|
+
/** Provider key, e.g. `youtube`. */
|
|
43
|
+
provider: string;
|
|
44
|
+
/** Display name, e.g. `YouTube`. */
|
|
45
|
+
providerName: string;
|
|
46
|
+
kind: MarvinEmbedKind | (string & {});
|
|
47
|
+
/** `ok` has an iframe; `link` and `unavailable` render as a link card. */
|
|
48
|
+
status: MarvinEmbedStatus | (string & {});
|
|
49
|
+
title?: string | null;
|
|
50
|
+
authorName?: string | null;
|
|
51
|
+
thumbnailUrl?: string | null;
|
|
52
|
+
iframe?: MarvinEmbedIframe | null;
|
|
53
|
+
link: MarvinEmbedLink;
|
|
54
|
+
/** Marvin-built HTML for the site's embed mode (an iframe, a click-to-load facade, or a link). */
|
|
55
|
+
html: string;
|
|
56
|
+
};
|
|
57
|
+
/** `entry.embeds`: every resolved embed on the entry, keyed by the URL as written. */
|
|
58
|
+
type MarvinEmbeds = Record<string, MarvinEmbed>;
|
|
59
|
+
/**
|
|
60
|
+
* `site.embeds` (next to `site.seo`): the site's embed privacy mode, consent text (`{provider}`
|
|
61
|
+
* already substituted per embed in the published `html`) and the frame sources a CSP must allow.
|
|
62
|
+
*/
|
|
63
|
+
type MarvinSiteEmbeds = {
|
|
64
|
+
mode: MarvinEmbedMode;
|
|
65
|
+
consentText: string;
|
|
66
|
+
/** Origins/hosts the site's embeds may frame — feed these into a `frame-src` directive. */
|
|
67
|
+
frameSources: string[];
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* The entry's `embeds` map, or `undefined` when it has none (an older server, or no embeds).
|
|
71
|
+
* Values that aren't objects carrying a URL are dropped.
|
|
72
|
+
*/
|
|
73
|
+
declare function entryEmbeds(entry: unknown): MarvinEmbeds | undefined;
|
|
74
|
+
/** The embed for `url` as written, tolerating surrounding whitespace. */
|
|
75
|
+
declare function findEmbed(embeds: MarvinEmbeds | null | undefined, url: string | null | undefined): MarvinEmbed | undefined;
|
|
76
|
+
/** Normalize `site.embeds`; `undefined` when the server doesn't send it. */
|
|
77
|
+
declare function siteEmbeds(raw: unknown): MarvinSiteEmbeds | undefined;
|
|
78
|
+
|
|
79
|
+
export { type MarvinEmbeds as M, type MarvinEmbed as a, type MarvinEmbedIframe as b, type MarvinEmbedKind as c, type MarvinEmbedLink as d, type MarvinEmbedMode as e, type MarvinEmbedStatus as f, type MarvinSiteEmbeds as g, entryEmbeds as h, findEmbed as i, siteEmbeds as s };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { MarkedOptions } from 'marked';
|
|
2
|
+
import { M as MarvinEmbeds, a as MarvinEmbed } from './embeds-BTNryQA6.js';
|
|
3
|
+
export { b as MarvinEmbedIframe, c as MarvinEmbedKind, d as MarvinEmbedLink, e as MarvinEmbedMode, f as MarvinEmbedStatus, g as MarvinSiteEmbeds, h as entryEmbeds, i as findEmbed, s as siteEmbeds } from './embeds-BTNryQA6.js';
|
|
2
4
|
import { MarvinContentEntry, ApiSite, ApiSeo, ApiSiteChrome, ApiNavigationLink, ApiSocialLink, AssetSelectOptions, ApiImage, ApiResourceLink, SectionLanding } from './types.js';
|
|
3
5
|
export { ApiActionVariant, ApiHero, ApiHeroAction, ApiPageAction } from './types.js';
|
|
4
6
|
import * as _inneropen_marvin_sdk from '@inneropen/marvin-sdk';
|
|
5
7
|
import { MarvinClient, MarvinEntry, Entry, PublishedCollectionSummary, Collection, CollectionEntry, MarvinSite, Workspace, MarvinAsset, MarvinResource } from '@inneropen/marvin-sdk';
|
|
6
8
|
|
|
7
9
|
type MarkdownOptions = MarkedOptions;
|
|
8
|
-
|
|
10
|
+
/** Per-call options for a {@link MarkdownRenderer}. */
|
|
11
|
+
type RenderMarkdownOptions = {
|
|
12
|
+
/**
|
|
13
|
+
* The entry's `embeds` map. A paragraph that is exactly a URL with an entry here renders as
|
|
14
|
+
* that embed's Marvin-built HTML instead of a link.
|
|
15
|
+
*/
|
|
16
|
+
embeds?: MarvinEmbeds | null;
|
|
17
|
+
};
|
|
18
|
+
type MarkdownRenderer = (markdown: string | string[] | null | undefined, options?: RenderMarkdownOptions) => Promise<string>;
|
|
9
19
|
/**
|
|
10
20
|
* A markdown renderer with its own `marked` instance — options are per-site, not global, so two
|
|
11
21
|
* `createMarvinContent()` instances in one process can't clobber each other's settings.
|
|
@@ -13,6 +23,12 @@ type MarkdownRenderer = (markdown: string | string[] | null | undefined) => Prom
|
|
|
13
23
|
* `breaks: false` is the default because CMS body copy is authored as prose: a soft-wrapped
|
|
14
24
|
* paragraph should render as one paragraph. Where authored line breaks must survive (a landing
|
|
15
25
|
* intro, an address block), pass `breaks: true` or pre-convert with a trailing double space.
|
|
26
|
+
*
|
|
27
|
+
* Pass the entry's `embeds` to turn a bare provider URL on its own line into its player:
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* await renderMarkdown(body, { embeds: entryEmbeds(entry) });
|
|
31
|
+
* ```
|
|
16
32
|
*/
|
|
17
33
|
declare function createMarkdownRenderer(options?: MarkdownOptions): MarkdownRenderer;
|
|
18
34
|
/** Turn single newlines into hard breaks, leaving paragraph breaks alone. */
|
|
@@ -39,6 +55,8 @@ declare const ENV_KEYS: {
|
|
|
39
55
|
};
|
|
40
56
|
/** Dev default for the network-failure latch: retry a downed backend after 30s. */
|
|
41
57
|
declare const DEFAULT_DEV_RETRY_MS = 30000;
|
|
58
|
+
/** How many entry reads `hydrate` keeps in flight at once. */
|
|
59
|
+
declare const DEFAULT_HYDRATE_CONCURRENCY = 6;
|
|
42
60
|
type MarvinAstroConfig = {
|
|
43
61
|
apiUrl?: string;
|
|
44
62
|
siteClientToken?: string;
|
|
@@ -51,6 +69,12 @@ type MarvinAstroConfig = {
|
|
|
51
69
|
* consistently rather than half-succeed with some pages live and some pages static.
|
|
52
70
|
*/
|
|
53
71
|
retryAfterMs?: number;
|
|
72
|
+
/**
|
|
73
|
+
* Maximum entry reads in flight at once while hydrating a collection. Firing every read at
|
|
74
|
+
* once makes a large collection time out on itself; the default of 6 keeps a 280-entry
|
|
75
|
+
* hydration well inside the SDK's per-request timeout.
|
|
76
|
+
*/
|
|
77
|
+
hydrateConcurrency?: number;
|
|
54
78
|
logger?: MarvinLogger;
|
|
55
79
|
/** Markdown rendering options; defaults to `{ gfm: true, breaks: false }`. */
|
|
56
80
|
markdown?: MarkdownOptions;
|
|
@@ -73,6 +97,7 @@ type ResolvedConfig = {
|
|
|
73
97
|
workspaceSlug: string;
|
|
74
98
|
debug: boolean;
|
|
75
99
|
retryAfterMs: number;
|
|
100
|
+
hydrateConcurrency: number;
|
|
76
101
|
logger: MarvinLogger;
|
|
77
102
|
markdown?: MarkdownOptions;
|
|
78
103
|
now: () => number;
|
|
@@ -282,6 +307,11 @@ type IconFieldOptions = {
|
|
|
282
307
|
type MarkdownFieldOptions = {
|
|
283
308
|
/** Keep authored single newlines as hard breaks. Default `false`. */
|
|
284
309
|
softBreaks?: boolean;
|
|
310
|
+
/**
|
|
311
|
+
* Render a bare provider URL on its own line as its player, using the entry's `embeds`.
|
|
312
|
+
* Default `true`; `false` keeps every URL a plain link.
|
|
313
|
+
*/
|
|
314
|
+
embeds?: boolean;
|
|
285
315
|
};
|
|
286
316
|
type ResourceFieldOptions = {
|
|
287
317
|
/** Keep only these resource types, e.g. `['material', 'construction']`. */
|
|
@@ -329,6 +359,12 @@ declare function createFieldAccessor(entry: MarvinContentEntry, context: FieldAc
|
|
|
329
359
|
* caller can omit the property rather than emit an empty string.
|
|
330
360
|
*/
|
|
331
361
|
markdown(key?: string, options?: MarkdownFieldOptions): Promise<string | undefined>;
|
|
362
|
+
/**
|
|
363
|
+
* The resolved embed for an `embed` field (its value is the provider URL), or `undefined`
|
|
364
|
+
* when the field is empty or the server hasn't resolved it. Render it with `<Embed>` from
|
|
365
|
+
* `@inneropen/marvin-astro/components`, or output `embed.html`.
|
|
366
|
+
*/
|
|
367
|
+
embed(key: string): MarvinEmbed | undefined;
|
|
332
368
|
/**
|
|
333
369
|
* The entry's primary image. Checks, in order: a hand-authored `metadata_json.featuredImage`,
|
|
334
370
|
* the exact `preferRoles`, a role/usage match over the entry's image assets, and finally the
|
|
@@ -404,7 +440,10 @@ type RepositoryOptions<T> = {
|
|
|
404
440
|
type Repository<T> = {
|
|
405
441
|
/** Every item, resolved once per process. */
|
|
406
442
|
all(): Promise<T[]>;
|
|
407
|
-
/**
|
|
443
|
+
/**
|
|
444
|
+
* One item by slug. Served from `all()` once that has loaded; otherwise a direct entry
|
|
445
|
+
* fetch, falling back to a scan of `all()`.
|
|
446
|
+
*/
|
|
408
447
|
bySlug(slug: string): Promise<T | undefined>;
|
|
409
448
|
/** The first featured item, or the first item when none is marked. */
|
|
410
449
|
featured(): Promise<T | undefined>;
|
|
@@ -573,4 +612,4 @@ declare function createMarvinContent(options?: MarvinContentOptions): {
|
|
|
573
612
|
reset(): void;
|
|
574
613
|
};
|
|
575
614
|
|
|
576
|
-
export { ApiImage, ApiNavigationLink, ApiResourceLink, ApiSeo, ApiSite, ApiSiteChrome, ApiSocialLink, AssetSelectOptions, type ChromeLoader, type ChromeOptions, DEFAULT_DEV_RETRY_MS, ENV_KEYS, type FieldAccessor, type HrefContext, type HydratedEntry, type IconFieldOptions, type ImageFieldOptions, type MarkdownFieldOptions, type MarkdownOptions, type MarkdownRenderer, type MarvinAstroConfig, type MarvinBackend, type MarvinContent, MarvinContentEntry, type MarvinContentOptions, type MarvinFetcher, type MarvinLogger, type NavigationContext, type NavigationLinkInput, type Repository, type RepositoryContext, type RepositoryOptions, type ResolveHref, type ResolvedConfig, type ResourceFieldOptions, SectionLanding, type SectionLandingOptions, type SiteLoader, type SiteOptions, type SocialLinkInput, asNumber, asRecord, asString, asStringArray, assetAlt, assetField, assetUrl, buildFallbackSite, buildSeo, collectionRole, collectionSlugs, createBackend, createChromeLoader, createFetcher, createFieldAccessor, createMarkdownRenderer, createMarvinContent, createRepository, createSiteLoader, defaultResolveHref, describeConfig, entryAssets, entryData, entryField, entryMetadata, entryResources, errorMessage, field, focalPoint, formatDisplayDate, isExternalHref, isNetworkFailure, loadSectionLanding, metadataLink, preserveSoftBreaks, readEnv, resolveConfig, resourceField, resourceRole, selectAssetByRole, selectEntryAsset, selectFeaturedAsset, selectIconAsset, selectImageAsset, selectValuesForPage, socialLinkFromKey, toNavigationLink };
|
|
615
|
+
export { ApiImage, ApiNavigationLink, ApiResourceLink, ApiSeo, ApiSite, ApiSiteChrome, ApiSocialLink, AssetSelectOptions, type ChromeLoader, type ChromeOptions, DEFAULT_DEV_RETRY_MS, DEFAULT_HYDRATE_CONCURRENCY, ENV_KEYS, type FieldAccessor, type HrefContext, type HydratedEntry, type IconFieldOptions, type ImageFieldOptions, type MarkdownFieldOptions, type MarkdownOptions, type MarkdownRenderer, type MarvinAstroConfig, type MarvinBackend, type MarvinContent, MarvinContentEntry, type MarvinContentOptions, MarvinEmbed, MarvinEmbeds, type MarvinFetcher, type MarvinLogger, type NavigationContext, type NavigationLinkInput, type RenderMarkdownOptions, type Repository, type RepositoryContext, type RepositoryOptions, type ResolveHref, type ResolvedConfig, type ResourceFieldOptions, SectionLanding, type SectionLandingOptions, type SiteLoader, type SiteOptions, type SocialLinkInput, asNumber, asRecord, asString, asStringArray, assetAlt, assetField, assetUrl, buildFallbackSite, buildSeo, collectionRole, collectionSlugs, createBackend, createChromeLoader, createFetcher, createFieldAccessor, createMarkdownRenderer, createMarvinContent, createRepository, createSiteLoader, defaultResolveHref, describeConfig, entryAssets, entryData, entryField, entryMetadata, entryResources, errorMessage, field, focalPoint, formatDisplayDate, isExternalHref, isNetworkFailure, loadSectionLanding, metadataLink, preserveSoftBreaks, readEnv, resolveConfig, resourceField, resourceRole, selectAssetByRole, selectEntryAsset, selectFeaturedAsset, selectIconAsset, selectImageAsset, selectValuesForPage, socialLinkFromKey, toNavigationLink };
|