fontdue-js 3.0.0-alpha6 → 3.0.0-alpha7

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 (35) hide show
  1. package/dist/__generated__/orderTrackingUpdateOrderTrackingMutation.graphql.d.ts +27 -0
  2. package/dist/__generated__/orderTrackingUpdateOrderTrackingMutation.graphql.js +72 -0
  3. package/dist/components/BuyButton/index.js +8 -2
  4. package/dist/components/Cart/orderTracking.d.ts +10 -0
  5. package/dist/components/Cart/orderTracking.js +43 -0
  6. package/dist/components/CartButton/index.js +16 -4
  7. package/dist/components/CharacterViewer/index.js +8 -2
  8. package/dist/components/CustomerLoginForm/index.js +17 -9
  9. package/dist/components/FontdueProvider/index.d.ts +10 -1
  10. package/dist/components/FontdueProvider/index.js +1 -0
  11. package/dist/components/FontdueProvider/index.server.d.ts +2 -1
  12. package/dist/components/FontdueProvider/index.server.js +16 -0
  13. package/dist/components/NewsletterSignup/index.js +4 -1
  14. package/dist/components/TestFontsForm/index.js +4 -1
  15. package/dist/components/TypeTesters/index.js +8 -2
  16. package/dist/relay/environment.js +10 -4
  17. package/dist/relay/loadSerializableQuery.d.ts +3 -1
  18. package/dist/relay/loadSerializableQuery.js +2 -2
  19. package/dist/relay/serverConfig.d.ts +10 -0
  20. package/dist/relay/serverConfig.js +38 -0
  21. package/package.json +2 -1
  22. package/dist/__generated__/TypeTesterStyleSelectData_viewer.graphql.d.ts +0 -42
  23. package/dist/__generated__/TypeTesterStyleSelectData_viewer.graphql.js +0 -166
  24. package/dist/__generated__/TypeTester_viewer.graphql.d.ts +0 -17
  25. package/dist/__generated__/TypeTester_viewer.graphql.js +0 -40
  26. package/dist/__generated__/TypeTesters_viewer.graphql.d.ts +0 -17
  27. package/dist/__generated__/TypeTesters_viewer.graphql.js +0 -40
  28. package/dist/components/FontdueContextProvider/index.server.d.ts +0 -4
  29. package/dist/components/FontdueContextProvider/index.server.js +0 -7
  30. package/dist/components/FontdueProvider/useAuxUIOwner.d.ts +0 -1
  31. package/dist/components/FontdueProvider/useAuxUIOwner.js +0 -28
  32. package/dist/components/TypeTester/TypeTesterStandalone.preload.d.ts +0 -14
  33. package/dist/components/TypeTester/TypeTesterStandalone.preload.js +0 -20
  34. package/dist/config.d.ts +0 -7
  35. package/dist/config.js +0 -31
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @generated SignedSource<<017a8a724b3a0fd0918153ce04d07f68>>
3
+ * @lightSyntaxTransform
4
+ * @nogrep
5
+ */
6
+ import { ConcreteRequest } from 'relay-runtime';
7
+ export type UpdateOrderTrackingInput = {
8
+ analyticsConsent?: boolean | null;
9
+ anonymousId?: string | null;
10
+ fbc?: string | null;
11
+ fbp?: string | null;
12
+ url?: string | null;
13
+ };
14
+ export type orderTrackingUpdateOrderTrackingMutation$variables = {
15
+ input: UpdateOrderTrackingInput;
16
+ };
17
+ export type orderTrackingUpdateOrderTrackingMutation$data = {
18
+ readonly updateOrderTracking: {
19
+ readonly success: boolean | null;
20
+ } | null;
21
+ };
22
+ export type orderTrackingUpdateOrderTrackingMutation = {
23
+ response: orderTrackingUpdateOrderTrackingMutation$data;
24
+ variables: orderTrackingUpdateOrderTrackingMutation$variables;
25
+ };
26
+ declare const node: ConcreteRequest;
27
+ export default node;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ /**
8
+ * @generated SignedSource<<017a8a724b3a0fd0918153ce04d07f68>>
9
+ * @lightSyntaxTransform
10
+ * @nogrep
11
+ */
12
+
13
+ /* tslint:disable */
14
+ /* eslint-disable */
15
+ // @ts-nocheck
16
+
17
+ const node = function () {
18
+ var v0 = [{
19
+ "defaultValue": null,
20
+ "kind": "LocalArgument",
21
+ "name": "input"
22
+ }],
23
+ v1 = [{
24
+ "alias": null,
25
+ "args": [{
26
+ "kind": "Variable",
27
+ "name": "input",
28
+ "variableName": "input"
29
+ }],
30
+ "concreteType": "UpdateOrderTrackingPayload",
31
+ "kind": "LinkedField",
32
+ "name": "updateOrderTracking",
33
+ "plural": false,
34
+ "selections": [{
35
+ "alias": null,
36
+ "args": null,
37
+ "kind": "ScalarField",
38
+ "name": "success",
39
+ "storageKey": null
40
+ }],
41
+ "storageKey": null
42
+ }];
43
+ return {
44
+ "fragment": {
45
+ "argumentDefinitions": v0 /*: any*/,
46
+ "kind": "Fragment",
47
+ "metadata": null,
48
+ "name": "orderTrackingUpdateOrderTrackingMutation",
49
+ "selections": v1 /*: any*/,
50
+ "type": "RootMutationType",
51
+ "abstractKey": null
52
+ },
53
+ "kind": "Request",
54
+ "operation": {
55
+ "argumentDefinitions": v0 /*: any*/,
56
+ "kind": "Operation",
57
+ "name": "orderTrackingUpdateOrderTrackingMutation",
58
+ "selections": v1 /*: any*/
59
+ },
60
+ "params": {
61
+ "cacheID": "c6d6059215c6ed30315c881dff80e9a7",
62
+ "id": null,
63
+ "metadata": {},
64
+ "name": "orderTrackingUpdateOrderTrackingMutation",
65
+ "operationKind": "mutation",
66
+ "text": "mutation orderTrackingUpdateOrderTrackingMutation(\n $input: UpdateOrderTrackingInput!\n) {\n updateOrderTracking(input: $input) {\n success\n }\n}\n"
67
+ }
68
+ };
69
+ }();
70
+ node.hash = "d59a127a7f140424f507ae549731bac7";
71
+ var _default = node;
72
+ exports.default = _default;
@@ -85,7 +85,10 @@ export function BuyButtonPreloadedIDQueryRenderer(_ref3) {
85
85
  preloadedQuery,
86
86
  ...rest
87
87
  } = _ref3;
88
- const queryRef = useSerializablePreloadedQuery(preloadedQuery);
88
+ // The query node lets the hook commit the payload into the store, so
89
+ // usePreloadedQuery resolves synchronously during SSR instead of
90
+ // refetching (the response cache only exists in the browser).
91
+ const queryRef = useSerializablePreloadedQuery(preloadedQuery, 'store-or-network', idQuery);
89
92
  const data = usePreloadedQuery(idQuery, queryRef);
90
93
  return /*#__PURE__*/React.createElement(BuyButtonComponent, _extends({}, rest, data));
91
94
  }
@@ -108,7 +111,10 @@ export function BuyButtonPreloadedSlugQueryRenderer(_ref5) {
108
111
  preloadedQuery,
109
112
  ...rest
110
113
  } = _ref5;
111
- const queryRef = useSerializablePreloadedQuery(preloadedQuery);
114
+ // The query node lets the hook commit the payload into the store, so
115
+ // usePreloadedQuery resolves synchronously during SSR instead of
116
+ // refetching (the response cache only exists in the browser).
117
+ const queryRef = useSerializablePreloadedQuery(preloadedQuery, 'store-or-network', slugQuery);
112
118
  const data = usePreloadedQuery(slugQuery, queryRef);
113
119
  return /*#__PURE__*/React.createElement(BuyButtonComponent, _extends({}, rest, {
114
120
  collection: ((_data$viewer2 = data.viewer) === null || _data$viewer2 === void 0 ? void 0 : (_data$viewer2$slug = _data$viewer2.slug) === null || _data$viewer2$slug === void 0 ? void 0 : _data$viewer2$slug.collection) ?? null
@@ -0,0 +1,10 @@
1
+ import { Environment } from 'relay-runtime';
2
+ /**
3
+ * Stores the buyer's analytics context (cookie consent, Meta browser IDs,
4
+ * checkout page URL) on the current order. The server emits the purchase
5
+ * event from a Stripe webhook — outside the browser — so this is how that
6
+ * event respects the consent banner and carries attribution.
7
+ *
8
+ * Fire-and-forget: tracking must never break checkout.
9
+ */
10
+ export declare function sendOrderTracking(environment: Environment): void;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.sendOrderTracking = sendOrderTracking;
7
+ var _orderTrackingUpdateOrderTrackingMutation2 = _interopRequireDefault(require("../../__generated__/orderTrackingUpdateOrderTrackingMutation.graphql"));
8
+ var _reactRelay = require("react-relay");
9
+ var _consent = require("../ConsentBanner/consent");
10
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
+ function readCookie(name) {
12
+ const match = document.cookie.match(new RegExp('(?:^|;\\s*)' + name + '=([^;]*)'));
13
+ return match ? decodeURIComponent(match[1]) : undefined;
14
+ }
15
+
16
+ /**
17
+ * Stores the buyer's analytics context (cookie consent, Meta browser IDs,
18
+ * checkout page URL) on the current order. The server emits the purchase
19
+ * event from a Stripe webhook — outside the browser — so this is how that
20
+ * event respects the consent banner and carries attribution.
21
+ *
22
+ * Fire-and-forget: tracking must never break checkout.
23
+ */
24
+ function sendOrderTracking(environment) {
25
+ try {
26
+ (0, _reactRelay.commitMutation)(environment, {
27
+ mutation: (_orderTrackingUpdateOrderTrackingMutation2.default.hash && _orderTrackingUpdateOrderTrackingMutation2.default.hash !== "d59a127a7f140424f507ae549731bac7" && console.error("The definition of 'orderTrackingUpdateOrderTrackingMutation' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _orderTrackingUpdateOrderTrackingMutation2.default),
28
+ variables: {
29
+ input: {
30
+ analyticsConsent: (0, _consent.hasConsent)('analytics'),
31
+ anonymousId: (0, _consent.getClientAnonymousId)(),
32
+ fbp: readCookie('_fbp'),
33
+ fbc: readCookie('_fbc'),
34
+ url: window.location.href
35
+ }
36
+ },
37
+ onCompleted: () => undefined,
38
+ onError: () => undefined
39
+ });
40
+ } catch {
41
+ // Ignore — see above.
42
+ }
43
+ }
@@ -3,7 +3,7 @@
3
3
  function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
4
4
  import _CartButtonQuery from "../../__generated__/CartButtonQuery.graphql.js";
5
5
  import _CartButton_order from "../../__generated__/CartButton_order.graphql.js";
6
- import React, { useEffect, useCallback, useContext, useState } from 'react';
6
+ import React, { Suspense, useEffect, useCallback, useContext, useState } from 'react';
7
7
  import { useDispatch } from 'react-redux';
8
8
  import { graphql, useFragment, useLazyLoadQuery } from 'react-relay';
9
9
  import ComponentsContext from '../ComponentsContext.js';
@@ -101,14 +101,26 @@ function CartButtonLazyQueryRenderer(props) {
101
101
  }
102
102
  // Cart contents are per-customer-session, so a server-side preload from a
103
103
  // CDN-cached SSG/prerender environment (no session cookies) just caches an
104
- // empty cart and delays the real one. CartButton always lazy-fetches on
105
- // hydration same pattern as StoreModal.
104
+ // empty cart and delays the real one. CartButton lazy-fetches on hydration —
105
+ // and only after mount: useLazyLoadQuery would otherwise also run during the
106
+ // SSR pass, where the fetch is sessionless (and in multi-tenant setups may
107
+ // have no resolvable GraphQL URL at all). Until mount it renders the empty
108
+ // cart state, which is also the Suspense fallback while the real fetch runs,
109
+ // so server HTML and first client render agree.
106
110
  export default function CartButton(_ref2) {
107
111
  let {
108
112
  config,
109
113
  ...props
110
114
  } = _ref2;
115
+ const [mounted, setMounted] = useState(false);
116
+ useEffect(() => setMounted(true), []);
111
117
  return /*#__PURE__*/React.createElement(EnsureFontdueContext, {
112
118
  config: config
113
- }, /*#__PURE__*/React.createElement(CartButtonLazyQueryRenderer, props));
119
+ }, mounted ? /*#__PURE__*/React.createElement(Suspense, {
120
+ fallback: /*#__PURE__*/React.createElement(CartButtonComponent, _extends({}, props, {
121
+ order: null
122
+ }))
123
+ }, /*#__PURE__*/React.createElement(CartButtonLazyQueryRenderer, props)) : /*#__PURE__*/React.createElement(CartButtonComponent, _extends({}, props, {
124
+ order: null
125
+ })));
114
126
  }
@@ -446,7 +446,10 @@ export function CharacterViewerPreloadedIDQueryRenderer(_ref8) {
446
446
  preloadedQuery,
447
447
  ...rest
448
448
  } = _ref8;
449
- const queryRef = useSerializablePreloadedQuery(preloadedQuery);
449
+ // The query node lets the hook commit the payload into the store, so
450
+ // usePreloadedQuery resolves synchronously during SSR instead of
451
+ // refetching (the response cache only exists in the browser).
452
+ const queryRef = useSerializablePreloadedQuery(preloadedQuery, 'store-or-network', idQuery);
450
453
  const data = usePreloadedQuery(idQuery, queryRef);
451
454
  if (!data.node) return null;
452
455
  return /*#__PURE__*/React.createElement(CharacterViewerComponent, _extends({}, rest, {
@@ -473,7 +476,10 @@ export function CharacterViewerPreloadedSlugQueryRenderer(_ref10) {
473
476
  preloadedQuery,
474
477
  ...rest
475
478
  } = _ref10;
476
- const queryRef = useSerializablePreloadedQuery(preloadedQuery);
479
+ // The query node lets the hook commit the payload into the store, so
480
+ // usePreloadedQuery resolves synchronously during SSR instead of
481
+ // refetching (the response cache only exists in the browser).
482
+ const queryRef = useSerializablePreloadedQuery(preloadedQuery, 'store-or-network', slugQuery);
477
483
  const data = usePreloadedQuery(slugQuery, queryRef);
478
484
  const collection = data === null || data === void 0 ? void 0 : (_data$viewer2 = data.viewer) === null || _data$viewer2 === void 0 ? void 0 : (_data$viewer2$slug = _data$viewer2.slug) === null || _data$viewer2$slug === void 0 ? void 0 : _data$viewer2$slug.fontCollection;
479
485
  if (!collection) return null;
@@ -9,7 +9,6 @@ const loginMutation = (_CustomerLoginFormLoginMutation.hash && _CustomerLoginFor
9
9
  const query = (_CustomerLoginFormQuery.hash && _CustomerLoginFormQuery.hash !== "f32c7b0b485a2b2a02ddf2cbc5b87a3c" && console.error("The definition of 'CustomerLoginFormQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _CustomerLoginFormQuery);
10
10
  const DEFAULT_SUBMITTED_LABEL = '<p>Submitted!</p><p>Please check your email inbox for a link to log in.</p><p>If you don\u2019t receive an email, please contact us to retrieve your order information.</p>';
11
11
  const CustomerLoginForm = _ref => {
12
- var _data$viewer, _data$viewer$settings;
13
12
  let {
14
13
  submitLabel = 'Submit'
15
14
  } = _ref;
@@ -18,8 +17,6 @@ const CustomerLoginForm = _ref => {
18
17
  const [submitting, setSubmitting] = useState(false);
19
18
  const [submitted, setSubmitted] = useState(false);
20
19
  const environment = useRelayEnvironment();
21
- const data = useLazyLoadQuery(query, {});
22
- const submittedLabel = ((_data$viewer = data.viewer) === null || _data$viewer === void 0 ? void 0 : (_data$viewer$settings = _data$viewer.settings) === null || _data$viewer$settings === void 0 ? void 0 : _data$viewer$settings.customerLoginSubmittedLabel) || DEFAULT_SUBMITTED_LABEL;
23
20
  const handleSubmit = e => {
24
21
  e.preventDefault();
25
22
  setSubmitting(true);
@@ -54,12 +51,7 @@ const CustomerLoginForm = _ref => {
54
51
  className: "login-form"
55
52
  }, error && /*#__PURE__*/React.createElement("div", {
56
53
  className: "login-form__errors"
57
- }, error), submitted ? /*#__PURE__*/React.createElement("div", {
58
- className: "login-form__submitted",
59
- dangerouslySetInnerHTML: {
60
- __html: submittedLabel
61
- }
62
- }) : /*#__PURE__*/React.createElement("form", {
54
+ }, error), submitted ? /*#__PURE__*/React.createElement(SubmittedMessage, null) : /*#__PURE__*/React.createElement("form", {
63
55
  className: "login-form__form",
64
56
  onSubmit: handleSubmit
65
57
  }, /*#__PURE__*/React.createElement("div", {
@@ -79,4 +71,20 @@ const CustomerLoginForm = _ref => {
79
71
  className: "submit-button__arrow"
80
72
  }, " \u2192")))));
81
73
  };
74
+
75
+ // The settings label is only shown after a submit, which can only happen in
76
+ // the browser — fetching it here (not in the form component) keeps the lazy
77
+ // query out of the SSR pass, where it has no session and, in multi-tenant
78
+ // setups, possibly no resolvable GraphQL URL.
79
+ function SubmittedMessage() {
80
+ var _data$viewer, _data$viewer$settings;
81
+ const data = useLazyLoadQuery(query, {});
82
+ const submittedLabel = ((_data$viewer = data.viewer) === null || _data$viewer === void 0 ? void 0 : (_data$viewer$settings = _data$viewer.settings) === null || _data$viewer$settings === void 0 ? void 0 : _data$viewer$settings.customerLoginSubmittedLabel) || DEFAULT_SUBMITTED_LABEL;
83
+ return /*#__PURE__*/React.createElement("div", {
84
+ className: "login-form__submitted",
85
+ dangerouslySetInnerHTML: {
86
+ __html: submittedLabel
87
+ }
88
+ });
89
+ }
82
90
  export default CustomerLoginForm;
@@ -2,11 +2,20 @@ import React from 'react';
2
2
  export { default as loadFontdueProviderQuery } from '../../loadFontdueProviderQuery.js';
3
3
  import type { FontdueContextProvider_props } from '../FontdueContextProvider/index.js';
4
4
  import { SerializablePreloadedQuery } from '../../relay/loadSerializableQuery.js';
5
+ import type { FontdueServerConfig } from '../../relay/serverConfig.js';
5
6
  import { FontdueProviderQuery } from '../../__generated__/FontdueProviderQuery.graphql.js';
6
7
  export declare const fontdueProviderQuery: import("react-relay").GraphQLTaggedNode;
7
8
  export type FontdueProviderPreloadedQuery = SerializablePreloadedQuery<FontdueProviderQuery>;
8
9
  export interface FontdueProvider_props extends FontdueContextProvider_props {
9
10
  preloadedQuery?: FontdueProviderPreloadedQuery;
11
+ /**
12
+ * Per-render config for server-side fetches (url, headers, cacheTags).
13
+ * Only honoured by the React Server Components entrypoint, which writes it
14
+ * to the render-scoped config store and strips it before this client
15
+ * component — it may carry internal headers that must never reach the
16
+ * browser. Ignored when the client provider is used directly.
17
+ */
18
+ serverConfig?: FontdueServerConfig;
10
19
  }
11
- declare const FontdueProvider: ({ children, preloadedQuery, ...rest }: FontdueProvider_props) => React.JSX.Element;
20
+ declare const FontdueProvider: ({ children, preloadedQuery, serverConfig: _serverConfig, ...rest }: FontdueProvider_props) => React.JSX.Element;
12
21
  export default FontdueProvider;
@@ -22,6 +22,7 @@ const FontdueProvider = _ref => {
22
22
  let {
23
23
  children,
24
24
  preloadedQuery,
25
+ serverConfig: _serverConfig,
25
26
  ...rest
26
27
  } = _ref;
27
28
  return /*#__PURE__*/React.createElement(FontdueContextProvider, rest, /*#__PURE__*/React.createElement(ErrorBoundary, {
@@ -2,5 +2,6 @@ import React from 'react';
2
2
  import type { FontdueProvider_props } from './index.js';
3
3
  export type { FontdueProvider_props } from './index.js';
4
4
  export type { FontdueProviderPreloadedQuery } from '../../loadFontdueProviderQuery.js';
5
+ export type { FontdueServerConfig } from '../../relay/serverConfig.js';
5
6
  export declare function loadFontdueProviderQuery(): never;
6
- export default function FontdueProviderServer({ preloadedQuery, ...rest }: FontdueProvider_props): Promise<React.JSX.Element>;
7
+ export default function FontdueProviderServer({ preloadedQuery, serverConfig, ...rest }: FontdueProvider_props): Promise<React.JSX.Element>;
@@ -2,6 +2,7 @@ function _extends() { return _extends = Object.assign ? Object.assign.bind() : f
2
2
  import React from 'react';
3
3
  import FontdueProvider from './index.js';
4
4
  import loadFontdueProviderQueryImpl from '../../loadFontdueProviderQuery.js';
5
+ import { setFontdueServerConfig } from '../../relay/serverConfig.js';
5
6
  // Stub for the RSC export condition. The default `<FontdueProvider>` server
6
7
  // entrypoint (this file) awaits the query for consumers, so RSC users should
7
8
  // never call it manually. Re-exporting a throwing stub makes the mistake
@@ -14,11 +15,26 @@ export function loadFontdueProviderQuery() {
14
15
 
15
16
  // RSC entry. When no preloadedQuery is passed, fetch one server-side so the
16
17
  // Next.js App Router path stays "drop the provider in your layout, done."
18
+ //
19
+ // `serverConfig` (and, as a shorthand, the `url` prop) is written to the
20
+ // per-render server config store before anything fetches, so every Fontdue
21
+ // server component below the provider — and the provider's own query —
22
+ // fetches against it. It is intentionally NOT forwarded to the client
23
+ // component: it can carry internal headers (e.g. proxy auth) that must not
24
+ // be serialized into the RSC payload. Note a soft navigation re-renders only
25
+ // the page segment, not a layout-hosted provider — pages that contain
26
+ // Fontdue components should call setFontdueServerConfig themselves.
17
27
  export default async function FontdueProviderServer(_ref) {
18
28
  let {
19
29
  preloadedQuery,
30
+ serverConfig,
20
31
  ...rest
21
32
  } = _ref;
33
+ if (serverConfig || rest.url) {
34
+ setFontdueServerConfig(serverConfig ?? {
35
+ url: rest.url
36
+ });
37
+ }
22
38
  const resolved = preloadedQuery ?? (await loadFontdueProviderQueryImpl());
23
39
  return /*#__PURE__*/React.createElement(FontdueProvider, _extends({}, rest, {
24
40
  preloadedQuery: resolved
@@ -182,7 +182,10 @@ export function NewsletterSignupPreloadedQueryRenderer(_ref3) {
182
182
  preloadedQuery,
183
183
  ...props
184
184
  } = _ref3;
185
- const queryRef = useSerializablePreloadedQuery(preloadedQuery);
185
+ // The query node lets the hook commit the payload into the store, so
186
+ // usePreloadedQuery resolves synchronously during SSR instead of
187
+ // refetching (the response cache only exists in the browser).
188
+ const queryRef = useSerializablePreloadedQuery(preloadedQuery, 'store-or-network', query);
186
189
  const data = usePreloadedQuery(query, queryRef);
187
190
  return /*#__PURE__*/React.createElement(NewsletterSignupComponent, _extends({
188
191
  data: data
@@ -202,7 +202,10 @@ export function TestFontsFormPreloadedQueryRenderer(_ref4) {
202
202
  preloadedQuery,
203
203
  ...rest
204
204
  } = _ref4;
205
- const queryRef = useSerializablePreloadedQuery(preloadedQuery);
205
+ // The query node lets the hook commit the payload into the store, so
206
+ // usePreloadedQuery resolves synchronously during SSR instead of
207
+ // refetching (the response cache only exists in the browser).
208
+ const queryRef = useSerializablePreloadedQuery(preloadedQuery, 'store-or-network', query);
206
209
  const data = usePreloadedQuery(query, queryRef);
207
210
  return /*#__PURE__*/React.createElement(TestFontsFormComponent, _extends({
208
211
  data: data
@@ -191,7 +191,10 @@ export function TypeTestersPreloadedIDQueryRenderer(_ref3) {
191
191
  preloadedQuery,
192
192
  ...rest
193
193
  } = _ref3;
194
- const queryRef = useSerializablePreloadedQuery(preloadedQuery);
194
+ // The query node lets the hook commit the payload into the store, so
195
+ // usePreloadedQuery resolves synchronously during SSR instead of
196
+ // refetching (the response cache only exists in the browser).
197
+ const queryRef = useSerializablePreloadedQuery(preloadedQuery, 'store-or-network', idQuery);
195
198
  const data = usePreloadedQuery(idQuery, queryRef);
196
199
  return /*#__PURE__*/React.createElement(TypeTestersComponent, _extends({}, data, rest));
197
200
  }
@@ -219,7 +222,10 @@ export function TypeTestersPreloadedSlugQueryRenderer(_ref5) {
219
222
  preloadedQuery,
220
223
  ...rest
221
224
  } = _ref5;
222
- const queryRef = useSerializablePreloadedQuery(preloadedQuery);
225
+ // The query node lets the hook commit the payload into the store, so
226
+ // usePreloadedQuery resolves synchronously during SSR instead of
227
+ // refetching (the response cache only exists in the browser).
228
+ const queryRef = useSerializablePreloadedQuery(preloadedQuery, 'store-or-network', slugQuery);
223
229
  const data = usePreloadedQuery(slugQuery, queryRef);
224
230
  return /*#__PURE__*/React.createElement(TypeTestersComponent, _extends({
225
231
  collection: ((_data$viewer$slug = data.viewer.slug) === null || _data$viewer$slug === void 0 ? void 0 : _data$viewer$slug.collection) ?? null
@@ -1,9 +1,10 @@
1
1
  import { Environment, Network, RecordSource, Store, QueryResponseCache } from 'relay-runtime';
2
2
  import { handlePossibleCorsError } from '../corsError.js';
3
+ import { getFontdueServerConfig } from './serverConfig.js';
3
4
 
4
5
  // `__FONTDUE_JS_VERSION__` is replaced by an inline babel plugin
5
6
  // (defineVersionPlugin in .babelrc.cjs) with the literal package.json#version.
6
- const version = "3.0.0-alpha6";
7
+ const version = "3.0.0-alpha7";
7
8
  const IS_SERVER = typeof window === typeof undefined;
8
9
 
9
10
  // Read env from either process.env (Node/Next.js) or import.meta.env (Vite/Astro).
@@ -37,9 +38,13 @@ const CACHE_TTL = 10 * 1000; // 10 seconds, to resolve preloaded results
37
38
 
38
39
  export function createNetworkFetch(options) {
39
40
  return async function networkFetch(request, variables) {
40
- const base = (options === null || options === void 0 ? void 0 : options.url) ?? FONTDUE_URL;
41
+ // Per-render server config (set via setFontdueServerConfig or the
42
+ // FontdueProvider server entrypoint). Resolved per call, not per
43
+ // createNetworkFetch, because module-level fetchers outlive renders.
44
+ const serverConfig = IS_SERVER ? getFontdueServerConfig() : undefined;
45
+ const base = (options === null || options === void 0 ? void 0 : options.url) ?? (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.url) ?? FONTDUE_URL;
41
46
  if (IS_SERVER && (base == null || base === '')) {
42
- throw new Error('fontdue-js: no Fontdue URL configured for server-side fetch. ' + 'Set FONTDUE_URL / PUBLIC_FONTDUE_URL / VITE_FONTDUE_URL in your environment, ' + 'or pass `url` explicitly to loadSerializableQuery (or its component-level wrappers).');
47
+ throw new Error('fontdue-js: no Fontdue URL configured for server-side fetch. ' + 'Set FONTDUE_URL / PUBLIC_FONTDUE_URL / VITE_FONTDUE_URL in your environment, ' + 'pass `url` to loadSerializableQuery, or call setFontdueServerConfig ' + '(or render <FontdueProvider url=…>) earlier in the server render.');
43
48
  }
44
49
  const url = `${base ?? ''}/graphql`;
45
50
  for (let attempt = 0; attempt <= 2; attempt++) {
@@ -48,6 +53,7 @@ export function createNetworkFetch(options) {
48
53
  method: 'POST',
49
54
  credentials: 'include',
50
55
  headers: {
56
+ ...(serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.headers),
51
57
  Accept: 'application/json',
52
58
  'Content-Type': 'application/json',
53
59
  'fontdue-stripe-integration': (options === null || options === void 0 ? void 0 : options.stripeIntegration) ?? STRIPE_INTEGRATION ?? 'dynamic',
@@ -59,7 +65,7 @@ export function createNetworkFetch(options) {
59
65
  }),
60
66
  // @ts-ignore
61
67
  next: {
62
- tags: ['graphql', `operation:${request.name}`]
68
+ tags: ['graphql', ...((serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.cacheTags) ?? []), `operation:${request.name}`]
63
69
  }
64
70
  });
65
71
  const json = await resp.json();
@@ -43,5 +43,7 @@ export interface SerializablePreloadedQuery<TQuery extends OperationType> {
43
43
  type RequireAllWithNull<T> = {
44
44
  [K in keyof T]-?: T[K] | null;
45
45
  };
46
- export default function loadSerializableQuery<TQuery extends OperationType>(query: GraphQLTaggedNode, variables: RequireAllWithNull<VariablesOf<TQuery>>): Promise<SerializablePreloadedQuery<TQuery>>;
46
+ export default function loadSerializableQuery<TQuery extends OperationType>(query: GraphQLTaggedNode, variables: RequireAllWithNull<VariablesOf<TQuery>>, options?: {
47
+ url?: string;
48
+ }): Promise<SerializablePreloadedQuery<TQuery>>;
47
49
  export {};
@@ -40,9 +40,9 @@ import { createNetworkFetch } from './environment.js';
40
40
  // Call into raw network fetch to get serializable GraphQL query response
41
41
  // This response will be sent to the client to "warm" the QueryResponseCache
42
42
  // to avoid the client fetches.
43
- export default async function loadSerializableQuery(query, variables) {
43
+ export default async function loadSerializableQuery(query, variables, options) {
44
44
  if (!('params' in query)) throw new Error('Params not found in query, is it a fragment instead of a query?');
45
- const fetcher = createNetworkFetch();
45
+ const fetcher = createNetworkFetch(options);
46
46
  const response = await fetcher(query.params, variables);
47
47
  return {
48
48
  params: query.params,
@@ -0,0 +1,10 @@
1
+ export interface FontdueServerConfig {
2
+ /** Base URL for server-side GraphQL fetches, e.g. https://acme.fontdue.com */
3
+ url?: string;
4
+ /** Extra headers sent with every server-side GraphQL fetch. */
5
+ headers?: Record<string, string>;
6
+ /** Extra Next.js fetch cache tags applied to every server-side GraphQL fetch. */
7
+ cacheTags?: string[];
8
+ }
9
+ export declare function setFontdueServerConfig(config: FontdueServerConfig): void;
10
+ export declare function getFontdueServerConfig(): FontdueServerConfig | undefined;
@@ -0,0 +1,38 @@
1
+ // Per-render configuration for server-side GraphQL fetches.
2
+ //
3
+ // React Server Components have no context API, so a `<FontdueProvider url=…>`
4
+ // prop cannot reach the nested server components that preload queries — the
5
+ // prop historically only configured client-side fetches. What RSC does have
6
+ // is React.cache(): a memoization store scoped to one server render pass and
7
+ // shared by every server component in it. We use a single cache cell as a
8
+ // render-scoped mutable slot: the host app (or the FontdueProvider server
9
+ // entrypoint) writes the config early in the render, and every later
10
+ // server-side fetch in the same pass reads it.
11
+ //
12
+ // Unlike AsyncLocalStorage-based approaches (e.g. Next's headers()), reading
13
+ // this store does not opt the route into dynamic rendering, so static
14
+ // generation and ISR keep working. Each render pass gets a fresh store, so
15
+ // concurrent requests cannot leak config into each other.
16
+ //
17
+ // Caveat that callers must own: the write has to happen in the same render
18
+ // pass, before the fetch. A provider in a layout covers full-page renders,
19
+ // but an App Router soft navigation re-renders only the page segment — set
20
+ // the config at the top of each page (or in a shared data helper every page
21
+ // calls) when pages contain Fontdue components.
22
+ //
23
+ // Outside an RSC render (client bundles, SSR setups without React.cache
24
+ // semantics) the slot degrades to a throwaway object: writes become no-ops
25
+ // and the environment variables remain the configuration source.
26
+
27
+ import * as React from 'react';
28
+ const getSlot = typeof React.cache === 'function' ? React.cache(() => ({
29
+ current: undefined
30
+ })) : () => ({
31
+ current: undefined
32
+ });
33
+ export function setFontdueServerConfig(config) {
34
+ getSlot().current = config;
35
+ }
36
+ export function getFontdueServerConfig() {
37
+ return getSlot().current;
38
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fontdue-js",
3
- "version": "3.0.0-alpha6",
3
+ "version": "3.0.0-alpha7",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "npm run relay && run-p build-js build-css build-ts",
@@ -82,6 +82,7 @@
82
82
  },
83
83
  "exports": {
84
84
  ".": "./dist/index.js",
85
+ "./server": "./dist/relay/serverConfig.js",
85
86
  "./fontdue.css": "./dist/fontdue.css",
86
87
  "./BuyButton": {
87
88
  "react-server": "./dist/components/BuyButton/index.server.js",
@@ -1,42 +0,0 @@
1
- /**
2
- * @generated SignedSource<<de2a1d20c5692c0b9d1b3fb9726732b0>>
3
- * @lightSyntaxTransform
4
- * @nogrep
5
- */
6
- import { ReaderFragment } from 'relay-runtime';
7
- import { FragmentRefs } from "relay-runtime";
8
- export type TypeTesterStyleSelectData_viewer$data = {
9
- readonly families?: {
10
- readonly edges: ReadonlyArray<{
11
- readonly node: {
12
- readonly featureStyle: {
13
- readonly supportedLanguages: ReadonlyArray<string> | null;
14
- } | null;
15
- readonly fontStyles: ReadonlyArray<{
16
- readonly cssStretch: string | null;
17
- readonly cssStyle: string | null;
18
- readonly cssWeight: string | null;
19
- readonly id: string;
20
- readonly name: string;
21
- readonly variableInstances: ReadonlyArray<{
22
- readonly coordinates: ReadonlyArray<{
23
- readonly axis: string;
24
- readonly value: number;
25
- }>;
26
- readonly name: string;
27
- }> | null;
28
- }>;
29
- readonly id: string;
30
- readonly isVariableFont: boolean;
31
- readonly name: string;
32
- } | null;
33
- } | null> | null;
34
- } | null;
35
- readonly " $fragmentType": "TypeTesterStyleSelectData_viewer";
36
- };
37
- export type TypeTesterStyleSelectData_viewer$key = {
38
- readonly " $data"?: TypeTesterStyleSelectData_viewer$data;
39
- readonly " $fragmentSpreads": FragmentRefs<"TypeTesterStyleSelectData_viewer">;
40
- };
41
- declare const node: ReaderFragment;
42
- export default node;
@@ -1,166 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
- /**
8
- * @generated SignedSource<<de2a1d20c5692c0b9d1b3fb9726732b0>>
9
- * @lightSyntaxTransform
10
- * @nogrep
11
- */
12
-
13
- /* tslint:disable */
14
- /* eslint-disable */
15
- // @ts-nocheck
16
-
17
- const node = function () {
18
- var v0 = {
19
- "alias": null,
20
- "args": null,
21
- "kind": "ScalarField",
22
- "name": "id",
23
- "storageKey": null
24
- },
25
- v1 = {
26
- "alias": null,
27
- "args": null,
28
- "kind": "ScalarField",
29
- "name": "name",
30
- "storageKey": null
31
- };
32
- return {
33
- "argumentDefinitions": [{
34
- "defaultValue": true,
35
- "kind": "LocalArgument",
36
- "name": "selectable"
37
- }],
38
- "kind": "Fragment",
39
- "metadata": null,
40
- "name": "TypeTesterStyleSelectData_viewer",
41
- "selections": [{
42
- "condition": "selectable",
43
- "kind": "Condition",
44
- "passingValue": true,
45
- "selections": [{
46
- "alias": "families",
47
- "args": [{
48
- "kind": "Literal",
49
- "name": "collectionTypes",
50
- "value": ["FAMILY"]
51
- }, {
52
- "kind": "Literal",
53
- "name": "first",
54
- "value": 999
55
- }],
56
- "concreteType": "FontCollectionConnection",
57
- "kind": "LinkedField",
58
- "name": "fontCollections",
59
- "plural": false,
60
- "selections": [{
61
- "alias": null,
62
- "args": null,
63
- "concreteType": "FontCollectionEdge",
64
- "kind": "LinkedField",
65
- "name": "edges",
66
- "plural": true,
67
- "selections": [{
68
- "alias": null,
69
- "args": null,
70
- "concreteType": "FontCollection",
71
- "kind": "LinkedField",
72
- "name": "node",
73
- "plural": false,
74
- "selections": [v0 /*: any*/, v1 /*: any*/, {
75
- "alias": null,
76
- "args": null,
77
- "kind": "ScalarField",
78
- "name": "isVariableFont",
79
- "storageKey": null
80
- }, {
81
- "alias": null,
82
- "args": null,
83
- "concreteType": "FontStyle",
84
- "kind": "LinkedField",
85
- "name": "featureStyle",
86
- "plural": false,
87
- "selections": [{
88
- "alias": null,
89
- "args": null,
90
- "kind": "ScalarField",
91
- "name": "supportedLanguages",
92
- "storageKey": null
93
- }],
94
- "storageKey": null
95
- }, {
96
- "alias": null,
97
- "args": null,
98
- "concreteType": "FontStyle",
99
- "kind": "LinkedField",
100
- "name": "fontStyles",
101
- "plural": true,
102
- "selections": [v0 /*: any*/, v1 /*: any*/, {
103
- "alias": null,
104
- "args": null,
105
- "kind": "ScalarField",
106
- "name": "cssWeight",
107
- "storageKey": null
108
- }, {
109
- "alias": null,
110
- "args": null,
111
- "kind": "ScalarField",
112
- "name": "cssStyle",
113
- "storageKey": null
114
- }, {
115
- "alias": null,
116
- "args": null,
117
- "kind": "ScalarField",
118
- "name": "cssStretch",
119
- "storageKey": null
120
- }, {
121
- "alias": null,
122
- "args": null,
123
- "concreteType": "VariableInstance",
124
- "kind": "LinkedField",
125
- "name": "variableInstances",
126
- "plural": true,
127
- "selections": [v1 /*: any*/, {
128
- "alias": null,
129
- "args": null,
130
- "concreteType": "VariableSetting",
131
- "kind": "LinkedField",
132
- "name": "coordinates",
133
- "plural": true,
134
- "selections": [{
135
- "alias": null,
136
- "args": null,
137
- "kind": "ScalarField",
138
- "name": "axis",
139
- "storageKey": null
140
- }, {
141
- "alias": null,
142
- "args": null,
143
- "kind": "ScalarField",
144
- "name": "value",
145
- "storageKey": null
146
- }],
147
- "storageKey": null
148
- }],
149
- "storageKey": null
150
- }],
151
- "storageKey": null
152
- }],
153
- "storageKey": null
154
- }],
155
- "storageKey": null
156
- }],
157
- "storageKey": "fontCollections(collectionTypes:[\"FAMILY\"],first:999)"
158
- }]
159
- }],
160
- "type": "Viewer",
161
- "abstractKey": null
162
- };
163
- }();
164
- node.hash = "5e5834d9202f90bdde5a0a7db49e8639";
165
- var _default = node;
166
- exports.default = _default;
@@ -1,17 +0,0 @@
1
- /**
2
- * @generated SignedSource<<abd37f03c566e5adecd035ce761cfe89>>
3
- * @lightSyntaxTransform
4
- * @nogrep
5
- */
6
- import { ReaderFragment } from 'relay-runtime';
7
- import { FragmentRefs } from "relay-runtime";
8
- export type TypeTester_viewer$data = {
9
- readonly " $fragmentSpreads": FragmentRefs<"TypeTesterStyleSelectData_viewer">;
10
- readonly " $fragmentType": "TypeTester_viewer";
11
- };
12
- export type TypeTester_viewer$key = {
13
- readonly " $data"?: TypeTester_viewer$data;
14
- readonly " $fragmentSpreads": FragmentRefs<"TypeTester_viewer">;
15
- };
16
- declare const node: ReaderFragment;
17
- export default node;
@@ -1,40 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
- /**
8
- * @generated SignedSource<<abd37f03c566e5adecd035ce761cfe89>>
9
- * @lightSyntaxTransform
10
- * @nogrep
11
- */
12
-
13
- /* tslint:disable */
14
- /* eslint-disable */
15
- // @ts-nocheck
16
-
17
- const node = {
18
- "argumentDefinitions": [{
19
- "defaultValue": null,
20
- "kind": "LocalArgument",
21
- "name": "selectable"
22
- }],
23
- "kind": "Fragment",
24
- "metadata": null,
25
- "name": "TypeTester_viewer",
26
- "selections": [{
27
- "args": [{
28
- "kind": "Variable",
29
- "name": "selectable",
30
- "variableName": "selectable"
31
- }],
32
- "kind": "FragmentSpread",
33
- "name": "TypeTesterStyleSelectData_viewer"
34
- }],
35
- "type": "Viewer",
36
- "abstractKey": null
37
- };
38
- node.hash = "26c2b3145462407675ab9b202b0d1b4c";
39
- var _default = node;
40
- exports.default = _default;
@@ -1,17 +0,0 @@
1
- /**
2
- * @generated SignedSource<<989cf2f044377f9f42d9f840f839af4b>>
3
- * @lightSyntaxTransform
4
- * @nogrep
5
- */
6
- import { ReaderFragment } from 'relay-runtime';
7
- import { FragmentRefs } from "relay-runtime";
8
- export type TypeTesters_viewer$data = {
9
- readonly " $fragmentSpreads": FragmentRefs<"TypeTester_viewer">;
10
- readonly " $fragmentType": "TypeTesters_viewer";
11
- };
12
- export type TypeTesters_viewer$key = {
13
- readonly " $data"?: TypeTesters_viewer$data;
14
- readonly " $fragmentSpreads": FragmentRefs<"TypeTesters_viewer">;
15
- };
16
- declare const node: ReaderFragment;
17
- export default node;
@@ -1,40 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
- /**
8
- * @generated SignedSource<<989cf2f044377f9f42d9f840f839af4b>>
9
- * @lightSyntaxTransform
10
- * @nogrep
11
- */
12
-
13
- /* tslint:disable */
14
- /* eslint-disable */
15
- // @ts-nocheck
16
-
17
- const node = {
18
- "argumentDefinitions": [{
19
- "defaultValue": null,
20
- "kind": "LocalArgument",
21
- "name": "selectable"
22
- }],
23
- "kind": "Fragment",
24
- "metadata": null,
25
- "name": "TypeTesters_viewer",
26
- "selections": [{
27
- "args": [{
28
- "kind": "Variable",
29
- "name": "selectable",
30
- "variableName": "selectable"
31
- }],
32
- "kind": "FragmentSpread",
33
- "name": "TypeTester_viewer"
34
- }],
35
- "type": "Viewer",
36
- "abstractKey": null
37
- };
38
- node.hash = "8fbadb67d6506fee0f3ccc09624517c6";
39
- var _default = node;
40
- exports.default = _default;
@@ -1,4 +0,0 @@
1
- import React from 'react';
2
- import type { FontdueContextProvider_props } from './index.js';
3
- export type { FontdueContextProvider_props } from './index.js';
4
- export default function FontdueContextProviderServer(props: FontdueContextProvider_props): Promise<React.JSX.Element>;
@@ -1,7 +0,0 @@
1
- import React from 'react';
2
- import FontdueContextProvider from './index.js';
3
- // RSC entry. The client component does the work; this exists so the
4
- // `react-server` export condition resolves to a server-renderable file.
5
- export default async function FontdueContextProviderServer(props) {
6
- return /*#__PURE__*/React.createElement(FontdueContextProvider, props);
7
- }
@@ -1 +0,0 @@
1
- export default function useAuxUIOwner(): boolean;
@@ -1,28 +0,0 @@
1
- 'use client';
2
-
3
- import { useEffect, useState } from 'react';
4
-
5
- // Module-level claim: only one FontdueProvider on a page renders
6
- // auxiliary UI (TestModeBanner, ThemeConfig, Tracking, ConsentBanner,
7
- // ServerConfigProvider). Starts false on both server and client to avoid
8
- // hydration mismatch; the first provider to run `useEffect` wins and flips
9
- // its own state to true. Relinquishes the claim on unmount so HMR and
10
- // route changes don't leak.
11
-
12
- let ownerId = null;
13
- export default function useAuxUIOwner() {
14
- const [isOwner, setIsOwner] = useState(false);
15
- useEffect(() => {
16
- const myId = {};
17
- if (ownerId == null) {
18
- ownerId = myId;
19
- setIsOwner(true);
20
- }
21
- return () => {
22
- if (ownerId === myId) {
23
- ownerId = null;
24
- }
25
- };
26
- }, []);
27
- return isOwner;
28
- }
@@ -1,14 +0,0 @@
1
- import React from 'react';
2
- import { LoadSerializableQueryOptions, SerializablePreloadedQuery } from '../../relay/loadSerializableQuery.js';
3
- import { TypeTesterStandaloneQuery } from '../../__generated__/TypeTesterStandaloneQuery.graphql.js';
4
- import { TypeTesterStandalonePreloadedQueryRenderer } from './TypeTesterStandalone.js';
5
- export type TypeTesterPreloadedQuery = SerializablePreloadedQuery<TypeTesterStandaloneQuery>;
6
- export interface LoadTypeTesterQueryVariables {
7
- familyName: string;
8
- styleName: string;
9
- selectable?: boolean;
10
- }
11
- export declare function loadTypeTesterQuery(variables: LoadTypeTesterQueryVariables, options?: LoadSerializableQueryOptions): Promise<TypeTesterPreloadedQuery>;
12
- type RendererProps = React.ComponentProps<typeof TypeTesterStandalonePreloadedQueryRenderer>;
13
- export declare function TypeTesterPreloaded(props: RendererProps): React.JSX.Element;
14
- export {};
@@ -1,20 +0,0 @@
1
- import React from 'react';
2
- import loadSerializableQuery from '../../relay/loadSerializableQuery.js';
3
- import TypeTesterStandaloneQueryNode from '../../__generated__/TypeTesterStandaloneQuery.graphql.js';
4
- import FontdueContextProvider from '../FontdueContextProvider/index.js';
5
- import { TypeTesterStandalonePreloadedQueryRenderer } from './TypeTesterStandalone.js';
6
- export async function loadTypeTesterQuery(variables, options) {
7
- return loadSerializableQuery(TypeTesterStandaloneQueryNode, {
8
- familyName: variables.familyName,
9
- styleName: variables.styleName,
10
- selectable: variables.selectable ?? true
11
- }, options);
12
- }
13
- // Self-wraps with FontdueContextProvider so consumers don't need to wire up
14
- // Relay env / Redux store / contexts at the call site. Aux UI (theme,
15
- // test-mode banner, consent, tracking) is NOT rendered here — that belongs
16
- // to a single layout-level `<FontdueProvider>` per page. State is shared
17
- // across multiple preloaded components on a page via module-level singletons.
18
- export function TypeTesterPreloaded(props) {
19
- return /*#__PURE__*/React.createElement(FontdueContextProvider, null, /*#__PURE__*/React.createElement(TypeTesterStandalonePreloadedQueryRenderer, props));
20
- }
package/dist/config.d.ts DELETED
@@ -1,7 +0,0 @@
1
- import type { Config } from './components/ConfigContext.js';
2
- export interface FontdueConfig extends Config {
3
- url?: string;
4
- stripeIntegration?: 'card-element' | 'dynamic';
5
- }
6
- export declare function configure(opts: Partial<FontdueConfig>): void;
7
- export declare function getConfig(): Readonly<FontdueConfig>;
package/dist/config.js DELETED
@@ -1,31 +0,0 @@
1
- // Module-level global configuration. Consumers call `configure()` once at
2
- // app startup (Astro layout, Next layout, Vite entry) and every fontdue-js
3
- // entry point reads defaults from here.
4
- //
5
- // Precedence (low to high):
6
- // 1. Server admin config (from the tenant dashboard, fetched via Relay)
7
- // 2. configure({...}) — module-level code defaults
8
- // 3. <FontdueProvider config={...}> — per-instance prop override
9
- //
10
- // Per-request isolation note: `configure` sets module-level state. That's
11
- // fine for single-tenant apps. Multi-tenant servers that switch URL per
12
- // request should pass `url` explicitly to loadSerializableQuery rather than
13
- // rely on configure().
14
-
15
- // Network-layer + UI config in one bag. UI fields come from `Config` (the
16
- // shape passed to ConfigContext); URL + stripeIntegration are network-layer.
17
-
18
- let current = {};
19
-
20
- // Shallow-merges with prior values. Calling `configure({tracking: {...}})`
21
- // replaces the whole `tracking` subtree; pass nested fields fully populated
22
- // when partial-updating.
23
- export function configure(opts) {
24
- current = {
25
- ...current,
26
- ...opts
27
- };
28
- }
29
- export function getConfig() {
30
- return current;
31
- }