fontdue-js 3.0.0-alpha13 → 3.0.0-alpha14

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/CHANGELOG.md CHANGED
@@ -7,7 +7,7 @@ In alpha on the `alpha` dist-tag — install with `npm install fontdue-js@alpha`
7
7
  - `fontdue-js/preview` — `handlePreviewRequest` (a Web-standard enter/exit route handler), `readPreviewToken`, `previewAuthHeaders`, and the cookie/endpoint constants.
8
8
  - `fontdue-js/preview/server` — `runWithPreview`, which holds the preview token in `AsyncLocalStorage` for the duration of a request so every server fetch and preload forwards it automatically, and forces preview responses out of shared/CDN caches so an admin's render is never served to the public.
9
9
 
10
- Next.js uses draft mode rather than ambient context, and needs no per-render setup call: mounting `<FontdueProvider>` wires every server fetch and the embedded components' server preloads to forward the token, apply the cache tags for `/api/revalidate`, and serve a live render while previewing — including embeds rendered inside a Server Component, which now preload server-side with the admin token instead of falling back to a client refetch. `configureFontduePreview()` (from `fontdue-js/next`) stays exported for when you want the resolved endpoint inside a render (e.g. a `metadataBase` fallback), but it's optional. See the README "Admin preview".
10
+ Next.js uses draft mode rather than ambient context, and needs no per-render setup call at all: mounting `<FontdueProvider>` wires every server fetch and the embedded components' server preloads to forward the token, apply the cache tags for `/api/revalidate`, and serve a live render while previewing — including embeds rendered inside a Server Component, which preload server-side with the admin token instead of falling back to a client refetch. See the README "Admin preview".
11
11
  - **One server fetch for every framework.** `fontdue-js/server` exports `createFontdueFetch({ url?, headers?, cacheTags? })` — a ready-made server-side GraphQL fetcher with URL resolution, error handling, `FontdueNotFoundError`, and automatic preview-token forwarding. Each input resolves per call from the explicit option, the per-render config (`runWithPreview` elsewhere, or the config the Next adapter resolves per render once `<FontdueProvider>` is mounted), then the environment, and passing `cacheTags` opts the fetch into Next's data cache + `/api/revalidate` (inert in other runtimes) — so Next and the other frameworks now share the same fetcher rather than Next hand-rolling its own. See the README "Server-side GraphQL fetches".
12
12
  - **Server-rendered embeds.** Components now render their full HTML on the server where the framework supports it (v2 hydrated some empty and fetched on the client), via the `load{Component}Query()` preload helpers.
13
13
  - **Migrating from v2 (Next.js)** is a small, mechanical upgrade — see the README "Migrating a Next.js site from v2". `useFontStyle` is renamed to `useFont` (the old name still works as an alias).
package/README.md CHANGED
@@ -438,7 +438,7 @@ export default async function Page() {
438
438
 
439
439
  `createFontdueFetch()` resolves its config per fetch from Next's request context: it points your fetches at your site (`NEXT_PUBLIC_FONTDUE_URL`), applies the cache tags that tie them into `/api/revalidate`, and — when a logged-in admin is previewing — forwards the admin token and serves the render live (see [Admin preview](#admin-preview)). Resolving per fetch means soft navigations that re-render only the page segment are covered too, with nothing to repeat per entry point.
440
440
 
441
- Route handlers (robots/sitemap) aren't React renders, so they read the endpoint from `fontdueEndpoint()` (from `fontdue-js/next`; shape: `type FontdueEndpoint`) and pass it to `createFontdueFetch({ url, headers, cacheTags })` directly. `configureFontduePreview()` is still exported if you want the resolved endpoint inside a render (e.g. a `metadataBase` fallback), but it's optional now that mounting the provider wires the config up.
441
+ Route handlers (robots/sitemap) run outside a React render, but the same `createFontdueFetch()` still resolves your site from `NEXT_PUBLIC_FONTDUE_URL`, so they need no extra setup. Their fetches aren't added to Next's data cache (there's no per-render config to tag them), which is what you want for robots/sitemap they're cheap and rarely change.
442
442
 
443
443
  ## Migrating a Next.js site from v2
444
444
 
@@ -174,11 +174,8 @@ describe('configureFontdueRender', () => {
174
174
  const {
175
175
  configureFontdueRender
176
176
  } = await importTenant();
177
- const {
178
- getFontdueSlotConfig
179
- } = await import("../relay/serverConfig.js");
177
+ // Invalid domain returns null before any setFontdueServerConfig call.
180
178
  expect(configureFontdueRender('not a domain')).toBeNull();
181
- expect(getFontdueSlotConfig()).toBeUndefined();
182
179
  });
183
180
  it('sets the per-render server config and returns the endpoint', async () => {
184
181
  stubMultiTenant({
@@ -202,8 +199,7 @@ describe('configureFontdueRender', () => {
202
199
  'x-forwarded-host': 'acme.fontdue.com',
203
200
  'x-fontdue-proxy-secret': 's3cret'
204
201
  },
205
- cacheTags: ['graphql:acme.fontdue.com'],
206
- domain: 'acme.fontdue.com'
202
+ cacheTags: ['graphql:acme.fontdue.com']
207
203
  });
208
204
  });
209
205
  });
@@ -283,62 +279,11 @@ describe('__prepareFontdueRender', () => {
283
279
  expect(config.cacheTags).toBeUndefined();
284
280
  });
285
281
  });
286
- describe('configureFontduePreview (single-tenant)', () => {
287
- async function captureConfig() {
288
- let captured;
289
- vi.doMock('../relay/serverConfig', async importActual => ({
290
- ...(await importActual()),
291
- setFontdueServerConfig: c => {
292
- captured = c;
293
- }
294
- }));
295
- const {
296
- configureFontduePreview
297
- } = await importTenant();
298
- const endpoint = await configureFontduePreview();
299
- vi.doUnmock('../relay/serverConfig');
300
- return {
301
- captured,
302
- endpoint
303
- };
304
- }
305
- it('public render: configures the NEXT_PUBLIC_FONTDUE_URL site with cache tags', async () => {
306
- var _captured$headers;
307
- stubSingleTenant('https://acme.fontdue.com');
308
- const {
309
- captured,
310
- endpoint
311
- } = await captureConfig();
312
- expect(endpoint.origin).toBe('https://acme.fontdue.com');
313
- expect(captured.url).toBe('https://acme.fontdue.com');
314
- expect(captured.cacheTags).toEqual(['graphql:acme.fontdue.com']);
315
- expect((_captured$headers = captured.headers) === null || _captured$headers === void 0 ? void 0 : _captured$headers.authorization).toBeUndefined();
316
- });
317
- it('preview render: forwards the token and drops cache tags', async () => {
318
- var _captured$headers2;
319
- stubSingleTenant('https://acme.fontdue.com');
320
- draft.enabled = true;
321
- draft.token = 'admin-tok';
322
- const {
323
- captured
324
- } = await captureConfig();
325
- expect((_captured$headers2 = captured.headers) === null || _captured$headers2 === void 0 ? void 0 : _captured$headers2.authorization).toBe('Bearer admin-tok');
326
- expect(captured.cacheTags).toBeUndefined();
327
- });
328
- it('throws a helpful error when NEXT_PUBLIC_FONTDUE_URL is unset', async () => {
329
- stubMultiTenant();
330
- const {
331
- configureFontduePreview
332
- } = await importTenant();
333
- await expect(configureFontduePreview()).rejects.toThrow(/NEXT_PUBLIC_FONTDUE_URL/);
334
- });
335
- });
336
282
  describe('single-tenant ambient resolver (no per-render call)', () => {
337
283
  // Importing registerSingleTenantResolver registers the resolver as a module
338
284
  // side effect — the FontdueProvider RSC entrypoint does this import, so
339
- // merely mounting the provider wires it up. No
340
- // configureFontduePreview()/__prepareFontdueRender() call is made in any of
341
- // these tests; the config is pulled at fetch time.
285
+ // merely mounting the provider wires it up. No per-render setup call is made
286
+ // in any of these tests; the config is pulled at fetch time.
342
287
  it('public render: feeds the env URL + cache tags, no token', async () => {
343
288
  var _config$headers3;
344
289
  stubSingleTenant('https://acme.fontdue.com');
@@ -415,45 +360,6 @@ describe('single-tenant ambient resolver (no per-render call)', () => {
415
360
  expect(config === null || config === void 0 ? void 0 : (_config$headers5 = config.headers) === null || _config$headers5 === void 0 ? void 0 : _config$headers5.authorization).toBeUndefined();
416
361
  });
417
362
  });
418
- describe('fontdueEndpoint', () => {
419
- it('multi-tenant: throws when no render config was set', async () => {
420
- stubMultiTenant({
421
- origin: 'http://app:4000'
422
- });
423
- const {
424
- fontdueEndpoint
425
- } = await importTenant();
426
- expect(() => fontdueEndpoint()).toThrow(/__prepareFontdueRender/);
427
- });
428
- it('single-tenant: derives the endpoint from NEXT_PUBLIC_FONTDUE_URL', async () => {
429
- stubSingleTenant('https://acme.fontdue.com');
430
- const {
431
- fontdueEndpoint
432
- } = await importTenant();
433
- expect(fontdueEndpoint()).toEqual({
434
- domain: 'acme.fontdue.com',
435
- origin: 'https://acme.fontdue.com',
436
- headers: {},
437
- tags: ['graphql', 'graphql:acme.fontdue.com']
438
- });
439
- });
440
- it('uses the render-configured domain when set', async () => {
441
- stubMultiTenant({
442
- origin: 'http://app:4000'
443
- });
444
- vi.doMock('../relay/serverConfig', async importActual => ({
445
- ...(await importActual()),
446
- getFontdueSlotConfig: () => ({
447
- domain: 'acme.fontdue.com'
448
- })
449
- }));
450
- const {
451
- fontdueEndpoint
452
- } = await importTenant();
453
- expect(fontdueEndpoint().headers['x-forwarded-host']).toBe('acme.fontdue.com');
454
- vi.doUnmock('../relay/serverConfig');
455
- });
456
- });
457
363
 
458
364
  // withFontdue detects the route-tree shape from the working directory; give
459
365
  // it one with or without src/app/[domain].
@@ -1 +1 @@
1
- export { configureFontduePreview, fontdueEndpoint, __prepareFontdueRender, type FontdueEndpoint, } from './tenant.js';
1
+ export { __prepareFontdueRender, type FontdueEndpoint } from './tenant.js';
@@ -2,19 +2,17 @@
2
2
  // The config-time wrapper lives in 'fontdue-js/next/config' and the deploy
3
3
  // hook route handler in 'fontdue-js/next/revalidate'.
4
4
 
5
- // Public foundry-facing API. Single-tenant apps need no per-render setup call
5
+ // Single-tenant apps need NO per-render setup and nothing from this entrypoint:
6
6
  // mounting <FontdueProvider> registers the ambient resolver that configures
7
- // every server fetch (see ../components/FontdueProvider/index.server.tsx ->
8
- // registerSingleTenantResolver). configureFontduePreview() remains exported as
9
- // an optional explicit hook (e.g. to get the resolved endpoint in a render);
10
- // route handlers (sitemap, robots) read fontdueEndpoint() since they run
11
- // outside React.
7
+ // every server fetch — your own (createFontdueFetch) and the embedded
8
+ // components' preloads, including the admin preview token (see
9
+ // ../components/FontdueProvider/index.server.tsx -> registerSingleTenantResolver).
12
10
  //
13
- // __prepareFontdueRender is the internal multi-tenant API used only by the
14
- // Fontdue-hosted next-template — not part of the foundry surface. The
15
- // remaining tenant/config helpers (isMultiTenant, isValidDomain,
16
- // endpointForDomain, fontdueServerConfig, configureFontdueRender,
17
- // setFontdueServerConfig/getFontdueSlotConfig) are deliberately not re-exported
18
- // here; the modules that need them import them directly from './tenant.js' /
19
- // '../relay/serverConfig.js'.
20
- export { configureFontduePreview, fontdueEndpoint, __prepareFontdueRender } from './tenant.js';
11
+ // __prepareFontdueRender is the internal multi-tenant API (double-underscored)
12
+ // used only by the Fontdue-hosted next-template — not part of the foundry
13
+ // surface — and FontdueEndpoint is its return type. The remaining tenant/config
14
+ // helpers (isMultiTenant, isValidDomain, endpointForDomain, fontdueServerConfig,
15
+ // configureFontdueRender, setFontdueServerConfig) are deliberately not
16
+ // re-exported here; the modules that need them import them directly from
17
+ // './tenant.js' / '../relay/serverConfig.js'.
18
+ export { __prepareFontdueRender } from './tenant.js';
@@ -28,9 +28,8 @@ registerAmbientConfigResolver(async () => {
28
28
  } = await import('./tenant.js');
29
29
  // Multi-tenant drives config through the React.cache slot
30
30
  // (__prepareFontdueRender), which needs the per-request route param, so the
31
- // resolver stays out of its way. The slot also wins the merge, so an
32
- // explicit configureFontduePreview()/__prepareFontdueRender call still
33
- // overrides this when present.
31
+ // resolver stays out of its way. The slot also wins the merge, so a
32
+ // multi-tenant __prepareFontdueRender call still overrides this when present.
34
33
  if (isMultiTenant || !singleTenantUrl) return undefined;
35
34
  return buildRenderConfig(new URL(singleTenantUrl).host);
36
35
  });
@@ -22,7 +22,5 @@ interface RenderProps {
22
22
  params: Promise<Record<string, string | string[] | undefined>>;
23
23
  }
24
24
  export declare function buildRenderConfig(domain: string): Promise<FontdueServerConfig>;
25
- export declare function configureFontduePreview(): Promise<FontdueEndpoint>;
26
25
  export declare function __prepareFontdueRender(props: RenderProps): Promise<FontdueEndpoint>;
27
- export declare function fontdueEndpoint(): FontdueEndpoint;
28
26
  export {};
@@ -20,13 +20,14 @@
20
20
  //
21
21
  // The fontdue-js components embedded in pages fetch the same way: their
22
22
  // server-side preloads read the per-render config set by
23
- // __prepareFontdueRender (or configureFontduePreview in single-tenant apps),
24
- // and in the browser they fetch the relative /graphql on the page's own
25
- // origin so multi-tenant mode needs no NEXT_PUBLIC_FONTDUE_URL at all.
23
+ // __prepareFontdueRender (multi-tenant) or, in single-tenant apps, the ambient
24
+ // resolver that <FontdueProvider> registers no per-render setup call and in
25
+ // the browser they fetch the relative /graphql on the page's own origin, so
26
+ // multi-tenant mode needs no NEXT_PUBLIC_FONTDUE_URL at all.
26
27
 
27
28
  import { notFound, unstable_rethrow } from 'next/navigation';
28
29
  import { cookies, draftMode } from 'next/headers';
29
- import { setFontdueServerConfig, getFontdueSlotConfig } from '../relay/serverConfig.js';
30
+ import { setFontdueServerConfig } from '../relay/serverConfig.js';
30
31
  import { PREVIEW_TOKEN_COOKIE, previewAuthHeaders } from '../preview/index.js';
31
32
  export const isMultiTenant = process.env.FONTDUE_MULTI_TENANT === '1';
32
33
  export const singleTenantUrl = process.env.NEXT_PUBLIC_FONTDUE_URL;
@@ -90,8 +91,7 @@ export function fontdueServerConfig(domain) {
90
91
  return {
91
92
  url: origin,
92
93
  headers,
93
- cacheTags: [`graphql:${domain}`],
94
- domain
94
+ cacheTags: [`graphql:${domain}`]
95
95
  };
96
96
  }
97
97
 
@@ -134,32 +134,6 @@ async function applyRenderConfig(domain) {
134
134
  setFontdueServerConfig(await buildRenderConfig(domain));
135
135
  }
136
136
 
137
- // Single-tenant render setup — the one line at the top of every page, layout
138
- // and generateMetadata that fetches Fontdue data or renders Fontdue components:
139
- //
140
- // await configureFontduePreview();
141
- //
142
- // It points this render's server-side fetches — your own (createFontdueFetch)
143
- // AND the embedded components' preloads (type testers, store, character viewer)
144
- // — at your Fontdue site (NEXT_PUBLIC_FONTDUE_URL), and when a logged-in admin
145
- // has entered preview it forwards their token and serves a live (uncached)
146
- // render so hidden (unpublished) fonts show up. Public renders stay cached and
147
- // tagged for /api/revalidate.
148
- //
149
- // Call it per entry point, not just the layout: a soft navigation re-renders
150
- // only the page segment with a fresh render store. Reading draft mode is
151
- // static-safe — it only forces dynamic rendering when an admin is actually
152
- // previewing, so static generation keeps working otherwise.
153
- //
154
- // Returns the endpoint, handy for e.g. a metadataBase fallback. Route handlers
155
- // (robots/sitemap) aren't React renders, so call fontdueEndpoint()
156
- // there instead.
157
- export async function configureFontduePreview() {
158
- const domain = new URL(requireSingleTenantUrl()).host;
159
- await applyRenderConfig(domain);
160
- return endpointForDomain(domain);
161
- }
162
-
163
137
  // Internal multi-tenant API (double-underscored, not part of the public
164
138
  // foundry surface): the one line at the top of every page, layout and
165
139
  // generateMetadata body in the multi-tenant [domain] route tree (the
@@ -169,8 +143,9 @@ export async function configureFontduePreview() {
169
143
  //
170
144
  // Reads the request's site from the route param, 404s anything that isn't a
171
145
  // plain hostname, and configures the render (including preview) for that site.
172
- // Like configureFontduePreview, it runs per entry point. Route handlers are not
173
- // React renders — use the returned endpoint explicitly there.
146
+ // It runs per entry point: a soft navigation re-renders only the page segment
147
+ // with a fresh render store. Route handlers are not React renders — use the
148
+ // returned endpoint explicitly there.
174
149
  export async function __prepareFontdueRender(props) {
175
150
  const {
176
151
  domain
@@ -217,23 +192,6 @@ async function readPreviewHeaders() {
217
192
  }
218
193
  }
219
194
 
220
- // Endpoint for the app's own GraphQL fetches in this render pass: whatever
221
- // __prepareFontdueRender configured, or — in a single-tenant app without the
222
- // [domain] tree, where __prepareFontdueRender is never called — the
223
- // NEXT_PUBLIC_FONTDUE_URL site. Throwing rather than guessing in
224
- // multi-tenant mode turns a forgotten __prepareFontdueRender into an
225
- // unmissable error instead of a silent wrong-site fetch on soft
226
- // navigations.
227
- export function fontdueEndpoint() {
228
- var _getFontdueSlotConfig;
229
- const domain = (_getFontdueSlotConfig = getFontdueSlotConfig()) === null || _getFontdueSlotConfig === void 0 ? void 0 : _getFontdueSlotConfig.domain;
230
- if (domain) return endpointForDomain(domain);
231
- if (isMultiTenant) {
232
- throw new Error('fontdue-js/next: no render config set — call __prepareFontdueRender(props) at the top of every page, layout and generateMetadata that fetches.');
233
- }
234
- return endpointForDomain(new URL(requireSingleTenantUrl()).host);
235
- }
236
-
237
195
  // The single-tenant ambient resolver that lets a foundry skip per-render setup
238
196
  // is registered from ./registerSingleTenantResolver.ts (imported as a side
239
197
  // effect by the FontdueProvider react-server entrypoint), not here — so this
@@ -7,7 +7,7 @@ import { PREVIEW_HEADER, hasPreviewMarkerCookie } from '../preview/constants.js'
7
7
  // (defineVersionPlugin in .babelrc.cjs) with the literal package.json#version.
8
8
  // Exported so UI (the admin toolbar) can surface it without re-reading the
9
9
  // build-time global in a 'use client' module.
10
- export const version = "3.0.0-alpha13";
10
+ export const version = "3.0.0-alpha14";
11
11
  const IS_SERVER = typeof window === typeof undefined;
12
12
 
13
13
  // Read env from either process.env (Node/Next.js) or import.meta.env (Vite/Astro).
@@ -5,17 +5,10 @@ export interface FontdueServerConfig {
5
5
  headers?: Record<string, string>;
6
6
  /** Extra Next.js fetch cache tags applied to every server-side GraphQL fetch. */
7
7
  cacheTags?: string[];
8
- /**
9
- * The site domain this render is for, when known. Set by
10
- * fontdue-js/next's __prepareFontdueRender so render-scoped helpers
11
- * (fontdueEndpoint) can recover it.
12
- */
13
- domain?: string;
14
8
  }
15
9
  type MaybePromise<T> = T | Promise<T>;
16
10
  type AmbientConfigResolver = () => MaybePromise<FontdueServerConfig | undefined>;
17
11
  export declare function setFontdueServerConfig(config: FontdueServerConfig): void;
18
12
  export declare function registerAmbientConfigResolver(resolver: AmbientConfigResolver): void;
19
- export declare function getFontdueSlotConfig(): FontdueServerConfig | undefined;
20
13
  export declare function resolveFontdueServerConfig(): Promise<FontdueServerConfig | undefined>;
21
14
  export {};
@@ -99,18 +99,6 @@ function mergeServerConfig(ambient, slot) {
99
99
  };
100
100
  }
101
101
 
102
- // Slot-only read of the render-scoped config. It deliberately does NOT run the
103
- // ambient resolver: the single-tenant Next resolver is async (it awaits
104
- // draftMode()/cookies()), so a synchronous read could never reflect it without
105
- // silently dropping the preview token. Anything that needs the resolved
106
- // url/headers — every server-side fetch — must use resolveFontdueServerConfig()
107
- // instead. This exists only to recover the multi-tenant `domain` the slot
108
- // carries (set by __prepareFontdueRender), which fontdueEndpoint() reads from a
109
- // synchronous path.
110
- export function getFontdueSlotConfig() {
111
- return store().getSlot().current;
112
- }
113
-
114
102
  // Asynchronous read used by the server-side fetch paths (the Relay network
115
103
  // layer and createFontdueFetch). Awaits the ambient resolver so a resolver that
116
104
  // reads request-scoped async APIs (Next's draftMode()/cookies()) can feed
@@ -15,9 +15,10 @@
15
15
  // 1. Ambient context (preferred): whatever set the per-render config.
16
16
  // - Astro / React Router etc.: runWithPreview (fontdue-js/preview/server)
17
17
  // rides the token through AsyncLocalStorage.
18
- // - Next: configureFontduePreview / __prepareFontdueRender
19
- // (fontdue-js/next) write the site URL, cache tags, and preview token
20
- // into the render-scoped slot.
18
+ // - Next: the single-tenant ambient resolver (registered by mounting
19
+ // <FontdueProvider>, no per-render call) or __prepareFontdueRender
20
+ // (fontdue-js/next, multi-tenant) supply the site URL, cache tags, and
21
+ // preview token for the render.
21
22
  // Either way:
22
23
  //
23
24
  // const fetchGraphql = createFontdueFetch(); // once, at module scope
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fontdue-js",
3
- "version": "3.0.0-alpha13",
3
+ "version": "3.0.0-alpha14",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "npm run relay && run-p build-js build-css build-ts",