fimo-next 0.21.0-experimental.1

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 (34) hide show
  1. package/README.md +18 -0
  2. package/dist/config.d.ts +15 -0
  3. package/dist/config.js +33 -0
  4. package/dist/runtime/next/client-runtime.d.ts +10 -0
  5. package/dist/runtime/next/client-runtime.js +36 -0
  6. package/dist/runtime/next/client.d.ts +20 -0
  7. package/dist/runtime/next/client.js +16 -0
  8. package/dist/runtime/next/connection/browser.d.ts +9 -0
  9. package/dist/runtime/next/connection/browser.js +21 -0
  10. package/dist/runtime/next/connection/contract.d.ts +41 -0
  11. package/dist/runtime/next/connection/contract.js +72 -0
  12. package/dist/runtime/next/connection/edge.d.ts +7 -0
  13. package/dist/runtime/next/connection/edge.js +14 -0
  14. package/dist/runtime/next/connection/node.d.ts +7 -0
  15. package/dist/runtime/next/connection/node.js +54 -0
  16. package/dist/runtime/next/facade.d.ts +8 -0
  17. package/dist/runtime/next/facade.js +34 -0
  18. package/dist/runtime/next/labels.d.ts +7 -0
  19. package/dist/runtime/next/labels.js +9 -0
  20. package/dist/runtime/next/preview.d.ts +13 -0
  21. package/dist/runtime/next/preview.js +33 -0
  22. package/dist/runtime/next/primitives/Text.d.ts +10 -0
  23. package/dist/runtime/next/primitives/Text.js +18 -0
  24. package/dist/runtime/next/primitives/server.d.ts +11 -0
  25. package/dist/runtime/next/primitives/server.js +51 -0
  26. package/dist/runtime/next/provider.d.ts +32 -0
  27. package/dist/runtime/next/provider.js +72 -0
  28. package/dist/runtime/next/server-provider.d.ts +17 -0
  29. package/dist/runtime/next/server-provider.js +12 -0
  30. package/dist/runtime/next/server-runtime.d.ts +3 -0
  31. package/dist/runtime/next/server-runtime.js +23 -0
  32. package/dist/runtime/next/server.d.ts +23 -0
  33. package/dist/runtime/next/server.js +18 -0
  34. package/package.json +73 -0
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # fimo-next
2
+
3
+ The Next.js runtime for Fimo projects. Install it alongside `fimo` and
4
+ `fimo-react` at the same version, plus `@tanstack/react-query`:
5
+
6
+ ```bash
7
+ npm install fimo fimo-react fimo-next @tanstack/react-query
8
+ ```
9
+
10
+ Render `FimoProvider` once at the application root: from `fimo-next` in the
11
+ App Router root layout (a Server Component), or from `fimo-next/client` in
12
+ `pages/_app.tsx`. It resolves the connection to the linked Fimo project on the
13
+ server and passes only public connection data to the browser; the application
14
+ never manages Fimo runtime variables.
15
+
16
+ Server Components, data functions, and route handlers import from `fimo-next`;
17
+ Client Components import from `fimo-next/client`. The Next configuration
18
+ wrapper for hosted preview origins is available from `fimo-next/config`.
@@ -0,0 +1,15 @@
1
+ export interface FimoNextConfigContext {
2
+ readonly defaultConfig?: object;
3
+ readonly [key: string]: unknown;
4
+ }
5
+ export type FimoNextConfigFactory<TConfig extends object = object> = (phase: string, context: FimoNextConfigContext) => TConfig | Promise<TConfig>;
6
+ export type FimoNextConfig<TConfig extends object = object> = TConfig | FimoNextConfigFactory<TConfig>;
7
+ /**
8
+ * Connect `next dev` to Fimo's hosted preview gateway. The platform supplies
9
+ * the allowed preview origins at runtime, so applications do not need to know
10
+ * which preview provider Fimo currently uses. Every other phase returns the
11
+ * application config unchanged: the Fimo connection is resolved by
12
+ * `FimoProvider` and the server runtime, not by this config wrapper.
13
+ */
14
+ export declare function withFimo<TConfig extends object>(applicationConfig: FimoNextConfig<TConfig>): FimoNextConfigFactory<TConfig>;
15
+ //# sourceMappingURL=config.d.ts.map
package/dist/config.js ADDED
@@ -0,0 +1,33 @@
1
+ const NEXT_DEVELOPMENT_PHASE = 'phase-development-server';
2
+ const PREVIEW_ORIGINS_ENV = 'FIMO_PREVIEW_ALLOWED_ORIGINS';
3
+ function readPreviewOrigins() {
4
+ return (process.env[PREVIEW_ORIGINS_ENV] ?? '')
5
+ .split(',')
6
+ .map((origin) => origin.trim())
7
+ .filter(Boolean);
8
+ }
9
+ /**
10
+ * Connect `next dev` to Fimo's hosted preview gateway. The platform supplies
11
+ * the allowed preview origins at runtime, so applications do not need to know
12
+ * which preview provider Fimo currently uses. Every other phase returns the
13
+ * application config unchanged: the Fimo connection is resolved by
14
+ * `FimoProvider` and the server runtime, not by this config wrapper.
15
+ */
16
+ export function withFimo(applicationConfig) {
17
+ return async (phase, context) => {
18
+ const config = typeof applicationConfig === 'function' ? await applicationConfig(phase, context) : applicationConfig;
19
+ if (phase !== NEXT_DEVELOPMENT_PHASE) {
20
+ return config;
21
+ }
22
+ const previewOrigins = readPreviewOrigins();
23
+ if (previewOrigins.length === 0) {
24
+ return config;
25
+ }
26
+ const configuredOrigins = config.allowedDevOrigins;
27
+ const allowedDevOrigins = [
28
+ ...(Array.isArray(configuredOrigins) ? configuredOrigins : []),
29
+ ...previewOrigins,
30
+ ].filter((origin, index, origins) => origins.indexOf(origin) === index);
31
+ return { ...config, allowedDevOrigins };
32
+ };
33
+ }
@@ -0,0 +1,10 @@
1
+ import type { ContentRuntime } from 'fimo/content';
2
+ import type { FimoConnection } from './connection/contract.js';
3
+ /**
4
+ * Install the connection the generated client modules read from. Called by
5
+ * the provider during render, so it is in place before any descendant's
6
+ * effect starts a query or submits a form.
7
+ */
8
+ export declare function connectClientRuntime(connection: FimoConnection): void;
9
+ export declare const nextClientContentRuntime: ContentRuntime;
10
+ //# sourceMappingURL=client-runtime.d.ts.map
@@ -0,0 +1,36 @@
1
+ let activeConnection = null;
2
+ /**
3
+ * Install the connection the generated client modules read from. Called by
4
+ * the provider during render, so it is in place before any descendant's
5
+ * effect starts a query or submits a form.
6
+ */
7
+ export function connectClientRuntime(connection) {
8
+ activeConnection = connection;
9
+ }
10
+ function requireConnection() {
11
+ if (!activeConnection) {
12
+ throw new Error('Fimo is not connected. Render FimoProvider above the component that reads content, labels, or forms.');
13
+ }
14
+ return activeConnection;
15
+ }
16
+ export const nextClientContentRuntime = {
17
+ apiBase() {
18
+ return requireConnection().apiUrl;
19
+ },
20
+ defaultLocale() {
21
+ // Provider and call-level locales are application-owned. Omitting a locale
22
+ // lets the tenant API use the project's configured default.
23
+ return undefined;
24
+ },
25
+ headers(extra) {
26
+ const headers = { ...extra };
27
+ const env = requireConnection().env;
28
+ if (env) {
29
+ headers['X-Fimo-Env'] = env;
30
+ }
31
+ return headers;
32
+ },
33
+ fetch(input, init) {
34
+ return globalThis.fetch(input, init);
35
+ },
36
+ };
@@ -0,0 +1,20 @@
1
+ import type { CollectionContentClient, ContentClientConfig, SingletonContentClient } from 'fimo-react/content';
2
+ export declare function createContentClient<T extends {
3
+ id: string;
4
+ }, TInput extends object>(config: ContentClientConfig): CollectionContentClient<T, TInput>;
5
+ export declare function createSingletonClient<T extends {
6
+ id: string;
7
+ }, TInput extends object>(config: ContentClientConfig): SingletonContentClient<T, TInput>;
8
+ export type { CollectionContentClient, InfiniteListResult, SingletonContentClient } from 'fimo-react/content';
9
+ export type { FormClient, FormSubmissionResult } from 'fimo/forms';
10
+ export declare const formClient: import("fimo/forms").FormClient;
11
+ export { createContentCore, createSingletonCore } from 'fimo/content';
12
+ export type { CollectionContentCore, CollectionPage, CollectionPageInfo, ContentClientConfig, ContentClientHandle, ContentModuleMeta, ContentModuleRef, ContentReadOptions, ContentResult, ContentRuntime, ContentSchema, ContentSchemaField, SingletonContentCore, } from 'fimo/content';
13
+ export { FIMO_PARTS, FIMO_SOURCE, FimoBoolean, FimoDate, FimoMedia, FimoRichText, FimoString, getFimoParts, getFimoSource, hasFimoSource, isFimoDate, isFimoString, } from 'fimo/content';
14
+ export { Boolean, Date, DateTime, Image, Json, RichText, StaticImage, Text, Video } from 'fimo-react/primitives';
15
+ export type { BoolOps, DateOps, Field, Fields, FilterFor, FimoPart, FimoPolymorphicReference, FimoReference, FimoRichTextContent, FimoSource, NullOnlyOps, NumberOps, Populate, Populated, PopulatedResult, Projected, ProjectedWithPopulate, Query, ReferenceField, ReferenceMap, ScalarOps, Sort, SortField, StringOps, Where, WithReferences, } from 'fimo/content';
16
+ export type { FimoRichTextComponents, FimoRichTextMarkProps, FimoRichTextNodeProps } from 'fimo-react/primitives';
17
+ export { FimoProvider, useLabels } from './provider.js';
18
+ export type { FimoProviderProps } from './provider.js';
19
+ export type { Labels, LabelsSnapshot } from 'fimo/labels';
20
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1,16 @@
1
+ 'use client';
2
+ import { createReactContentClient, createReactSingletonClient } from 'fimo-react/content';
3
+ import { createFormClient } from 'fimo/forms';
4
+ import { nextClientContentRuntime } from './client-runtime.js';
5
+ import { useNextContentLocale } from './provider.js';
6
+ export function createContentClient(config) {
7
+ return createReactContentClient(config, nextClientContentRuntime, useNextContentLocale);
8
+ }
9
+ export function createSingletonClient(config) {
10
+ return createReactSingletonClient(config, nextClientContentRuntime, useNextContentLocale);
11
+ }
12
+ export const formClient = createFormClient(nextClientContentRuntime);
13
+ export { createContentCore, createSingletonCore } from 'fimo/content';
14
+ export { FIMO_PARTS, FIMO_SOURCE, FimoBoolean, FimoDate, FimoMedia, FimoRichText, FimoString, getFimoParts, getFimoSource, hasFimoSource, isFimoDate, isFimoString, } from 'fimo/content';
15
+ export { Boolean, Date, DateTime, Image, Json, RichText, StaticImage, Text, Video } from 'fimo-react/primitives';
16
+ export { FimoProvider, useLabels } from './provider.js';
@@ -0,0 +1,9 @@
1
+ import { type ConnectionResolver } from './contract.js';
2
+ /**
3
+ * The browser never discovers a connection; it reads the one the server
4
+ * rendered into the document. Remembered after the first read so a provider
5
+ * that remounts during client-side navigation does not depend on the script
6
+ * still being in the DOM.
7
+ */
8
+ export declare const resolveConnection: ConnectionResolver;
9
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1,21 @@
1
+ import { FIMO_CONNECTION_SCRIPT_ID, parseConnection, } from './contract.js';
2
+ let cached = null;
3
+ /**
4
+ * The browser never discovers a connection; it reads the one the server
5
+ * rendered into the document. Remembered after the first read so a provider
6
+ * that remounts during client-side navigation does not depend on the script
7
+ * still being in the DOM.
8
+ */
9
+ export const resolveConnection = () => {
10
+ if (cached) {
11
+ return cached;
12
+ }
13
+ const text = document.getElementById(FIMO_CONNECTION_SCRIPT_ID)?.textContent;
14
+ const connection = text ? parseConnection(text) : null;
15
+ if (!connection) {
16
+ throw new Error('Fimo is not connected in the browser. Render FimoProvider once at the application root: ' +
17
+ "from 'fimo-next' in the App Router root layout, or from 'fimo-next/client' in pages/_app.");
18
+ }
19
+ cached = connection;
20
+ return connection;
21
+ };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The connection between a Next application and its Fimo project. Every
3
+ * field is public: the browser receives the whole object, so nothing
4
+ * server-only (tokens, the management API URL, project secrets) may ever be
5
+ * added here.
6
+ */
7
+ export interface FimoConnection {
8
+ /** Tenant content API base URL for the active Fimo Branch, no trailing slash. */
9
+ readonly apiUrl: string;
10
+ /** Branch name sent as `X-Fimo-Env`; omitted when the URL already pins a Branch. */
11
+ readonly env?: string;
12
+ /** Locale reads fall back to when neither the call nor the provider names one. */
13
+ readonly defaultLocale?: string;
14
+ /** Studio preview script; present only inside a Fimo-hosted preview. */
15
+ readonly previewScriptUrl?: string;
16
+ }
17
+ /**
18
+ * Implemented once per runtime (`node`, `edge`, `browser`) and selected
19
+ * through the `#connection` import map in `package.json`. The node and edge
20
+ * resolvers discover the connection; the browser resolver reads the one the
21
+ * server rendered.
22
+ */
23
+ export type ConnectionResolver = () => FimoConnection;
24
+ export declare const FIMO_CONNECTION_SCRIPT_ID = "fimo-connection";
25
+ type ExplicitEnv = Readonly<Record<string, string | undefined>>;
26
+ /** Public connection fields other than the URL, from explicit env only. */
27
+ export declare function explicitConnectionOptions(env: ExplicitEnv): Omit<FimoConnection, 'apiUrl'>;
28
+ /** The explicitly configured content API URL, or `undefined` to derive one. */
29
+ export declare function explicitContentApiUrl(env: ExplicitEnv): string | undefined;
30
+ /** `apiUrl` first, then the optional fields in a fixed order, without `undefined` holes. */
31
+ export declare function normalizeConnection(connection: FimoConnection): FimoConnection;
32
+ /**
33
+ * Canonical JSON for an inline `<script type="application/json">`. The server
34
+ * and the hydrating browser must produce the same text, so the output depends
35
+ * only on the field values. `<` is escaped so a value can never close the tag.
36
+ */
37
+ export declare function serializeConnection(connection: FimoConnection): string;
38
+ export declare function parseConnection(text: string): FimoConnection | null;
39
+ export declare const NOT_CONNECTED_MESSAGE: string;
40
+ export {};
41
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1,72 @@
1
+ export const FIMO_CONNECTION_SCRIPT_ID = 'fimo-connection';
2
+ /**
3
+ * Explicit platform or custom-host values, read the same way on every server
4
+ * runtime. Hosted sandboxes and builds inject the first alias of each pair;
5
+ * the `NEXT_PUBLIC_*` aliases stay honored so a value a project set under the
6
+ * former browser name keeps winning over derivation.
7
+ */
8
+ const EXPLICIT_KEYS = {
9
+ apiUrl: ['FIMO_CONTENT_API_URL', 'NEXT_PUBLIC_FIMO_API_URL'],
10
+ env: ['FIMO_ENV', 'NEXT_PUBLIC_FIMO_ENV'],
11
+ defaultLocale: ['FIMO_DEFAULT_LOCALE', 'NEXT_PUBLIC_FIMO_DEFAULT_LOCALE'],
12
+ previewScriptUrl: ['FIMO_PREVIEW_SCRIPT_URL', 'NEXT_PUBLIC_FIMO_PREVIEW_SCRIPT_URL'],
13
+ };
14
+ function firstValue(env, keys) {
15
+ for (const key of keys) {
16
+ const value = env[key]?.trim();
17
+ if (value) {
18
+ return value;
19
+ }
20
+ }
21
+ return undefined;
22
+ }
23
+ /** Public connection fields other than the URL, from explicit env only. */
24
+ export function explicitConnectionOptions(env) {
25
+ return {
26
+ env: firstValue(env, EXPLICIT_KEYS.env),
27
+ defaultLocale: firstValue(env, EXPLICIT_KEYS.defaultLocale),
28
+ previewScriptUrl: firstValue(env, EXPLICIT_KEYS.previewScriptUrl),
29
+ };
30
+ }
31
+ /** The explicitly configured content API URL, or `undefined` to derive one. */
32
+ export function explicitContentApiUrl(env) {
33
+ return firstValue(env, EXPLICIT_KEYS.apiUrl)?.replace(/\/$/, '');
34
+ }
35
+ /** `apiUrl` first, then the optional fields in a fixed order, without `undefined` holes. */
36
+ export function normalizeConnection(connection) {
37
+ const normalized = { apiUrl: connection.apiUrl };
38
+ for (const key of ['env', 'defaultLocale', 'previewScriptUrl']) {
39
+ if (connection[key]) {
40
+ normalized[key] = connection[key];
41
+ }
42
+ }
43
+ return normalized;
44
+ }
45
+ /**
46
+ * Canonical JSON for an inline `<script type="application/json">`. The server
47
+ * and the hydrating browser must produce the same text, so the output depends
48
+ * only on the field values. `<` is escaped so a value can never close the tag.
49
+ */
50
+ export function serializeConnection(connection) {
51
+ return JSON.stringify(normalizeConnection(connection)).replace(/</g, '\\u003c');
52
+ }
53
+ export function parseConnection(text) {
54
+ try {
55
+ const parsed = JSON.parse(text);
56
+ if (!parsed || typeof parsed !== 'object' || typeof parsed.apiUrl !== 'string') {
57
+ return null;
58
+ }
59
+ const { apiUrl, env, defaultLocale, previewScriptUrl } = parsed;
60
+ return normalizeConnection({
61
+ apiUrl: apiUrl,
62
+ env: typeof env === 'string' ? env : undefined,
63
+ defaultLocale: typeof defaultLocale === 'string' ? defaultLocale : undefined,
64
+ previewScriptUrl: typeof previewScriptUrl === 'string' ? previewScriptUrl : undefined,
65
+ });
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ }
71
+ export const NOT_CONNECTED_MESSAGE = 'This Next.js application is not connected to a Fimo project. ' +
72
+ 'Run `fimo link` in the project directory, or set FIMO_CONTENT_API_URL to the tenant content API URL.';
@@ -0,0 +1,7 @@
1
+ import { type ConnectionResolver } from './contract.js';
2
+ /**
3
+ * Edge runtimes have no file system or git, so only the explicit platform env
4
+ * can connect them. Hosted builds always set it.
5
+ */
6
+ export declare const resolveConnection: ConnectionResolver;
7
+ //# sourceMappingURL=edge.d.ts.map
@@ -0,0 +1,14 @@
1
+ import { explicitConnectionOptions, explicitContentApiUrl, normalizeConnection, } from './contract.js';
2
+ const EDGE_NOT_CONNECTED_MESSAGE = 'This Next.js Edge runtime is not connected to Fimo. ' +
3
+ 'Configure FIMO_CONTENT_API_URL in the deployment environment; Edge runtimes cannot read a local Fimo link.';
4
+ /**
5
+ * Edge runtimes have no file system or git, so only the explicit platform env
6
+ * can connect them. Hosted builds always set it.
7
+ */
8
+ export const resolveConnection = () => {
9
+ const apiUrl = explicitContentApiUrl(process.env);
10
+ if (!apiUrl) {
11
+ throw new Error(EDGE_NOT_CONNECTED_MESSAGE);
12
+ }
13
+ return normalizeConnection({ apiUrl, ...explicitConnectionOptions(process.env) });
14
+ };
@@ -0,0 +1,7 @@
1
+ import { type ConnectionResolver } from './contract.js';
2
+ /**
3
+ * Server-side connection discovery: explicit platform/custom-host env first,
4
+ * then the linked project and its active git branch. Memoized per process.
5
+ */
6
+ export declare const resolveConnection: ConnectionResolver;
7
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1,54 @@
1
+ import { findStaleFimoOverride, resolveLocalRuntimeEnv, staleOverrideMessage } from 'fimo/config';
2
+ import { explicitConnectionOptions, explicitContentApiUrl, NOT_CONNECTED_MESSAGE, normalizeConnection, } from './contract.js';
3
+ let cache = null;
4
+ let warnedStaleOverride = false;
5
+ function cacheKey() {
6
+ // Explicit env and cwd are the inputs; the git branch is not, on purpose —
7
+ // a checkout is a restart-level switch, the same way a dev server reads its
8
+ // env once at startup.
9
+ return JSON.stringify([
10
+ process.cwd(),
11
+ process.env.FIMO_CONTENT_API_URL,
12
+ process.env.NEXT_PUBLIC_FIMO_API_URL,
13
+ process.env.FIMO_ENV,
14
+ process.env.NEXT_PUBLIC_FIMO_ENV,
15
+ process.env.FIMO_DEFAULT_LOCALE,
16
+ process.env.NEXT_PUBLIC_FIMO_DEFAULT_LOCALE,
17
+ process.env.FIMO_PREVIEW_SCRIPT_URL,
18
+ process.env.NEXT_PUBLIC_FIMO_PREVIEW_SCRIPT_URL,
19
+ ]);
20
+ }
21
+ function discover() {
22
+ const explicit = explicitContentApiUrl(process.env);
23
+ const options = explicitConnectionOptions(process.env);
24
+ if (explicit) {
25
+ // The one override that silently points a checkout at the wrong Branch:
26
+ // a Fimo-minted URL from an older CLI's env file. A user-typed URL
27
+ // (`http://localhost:4000`) is the override this precedence exists for.
28
+ if (!warnedStaleOverride && process.env.NODE_ENV !== 'production') {
29
+ const derived = resolveLocalRuntimeEnv({ cwd: process.cwd() });
30
+ const stale = derived && findStaleFimoOverride({ FIMO_CONTENT_API_URL: explicit }, derived);
31
+ if (stale) {
32
+ warnedStaleOverride = true;
33
+ console.warn(staleOverrideMessage(stale));
34
+ }
35
+ }
36
+ return normalizeConnection({ apiUrl: explicit, ...options });
37
+ }
38
+ const derived = resolveLocalRuntimeEnv({ cwd: process.cwd() });
39
+ if (derived) {
40
+ return normalizeConnection({ apiUrl: derived.tenantApiUrl, ...options, env: derived.env });
41
+ }
42
+ throw new Error(NOT_CONNECTED_MESSAGE);
43
+ }
44
+ /**
45
+ * Server-side connection discovery: explicit platform/custom-host env first,
46
+ * then the linked project and its active git branch. Memoized per process.
47
+ */
48
+ export const resolveConnection = () => {
49
+ const key = cacheKey();
50
+ if (cache?.key !== key) {
51
+ cache = { key, connection: discover() };
52
+ }
53
+ return cache.connection;
54
+ };
@@ -0,0 +1,8 @@
1
+ import type { ContentClientHandle } from 'fimo/content';
2
+ export type NextContentRegistry = Readonly<Record<string, ContentClientHandle>>;
3
+ export interface CreateFimoOptions<TContent extends NextContentRegistry> {
4
+ content: TContent;
5
+ locale?: string;
6
+ }
7
+ export declare function createFimo<const TContent extends NextContentRegistry>({ content, locale, }: CreateFimoOptions<TContent>): TContent;
8
+ //# sourceMappingURL=facade.d.ts.map
@@ -0,0 +1,34 @@
1
+ function withLocale(options, locale) {
2
+ return { ...options, locale: options?.locale ?? locale };
3
+ }
4
+ function isCollectionClient(client) {
5
+ return 'list' in client && typeof client.list === 'function';
6
+ }
7
+ function isSingletonClient(client) {
8
+ return 'get' in client && typeof client.get === 'function';
9
+ }
10
+ function bindLocale(client, locale) {
11
+ if (isCollectionClient(client)) {
12
+ return {
13
+ ...client,
14
+ getById: (id, options) => client.getById(id, withLocale(options, locale)),
15
+ getBySlug: (slug, options) => client.getBySlug(slug, withLocale(options, locale)),
16
+ getByField: (fieldName, value, options) => client.getByField(fieldName, value, withLocale(options, locale)),
17
+ list: (params) => client.list(withLocale(params, locale)),
18
+ get: (params) => client.get(withLocale(params, locale)),
19
+ };
20
+ }
21
+ if (isSingletonClient(client)) {
22
+ return {
23
+ ...client,
24
+ get: (options) => client.get(withLocale(options, locale)),
25
+ };
26
+ }
27
+ return client;
28
+ }
29
+ export function createFimo({ content, locale, }) {
30
+ if (locale === undefined) {
31
+ return content;
32
+ }
33
+ return Object.fromEntries(Object.entries(content).map(([key, client]) => [key, bindLocale(client, locale)]));
34
+ }
@@ -0,0 +1,7 @@
1
+ import { type GetLabelsOptions } from 'fimo/labels';
2
+ /**
3
+ * Read the labels for an application-resolved locale. When omitted, the request
4
+ * carries no locale and the Fimo API answers with the project default locale.
5
+ */
6
+ export declare function getLabels(options?: GetLabelsOptions): Promise<import("fimo/labels").Labels>;
7
+ //# sourceMappingURL=labels.d.ts.map
@@ -0,0 +1,9 @@
1
+ import { fetchLabels } from 'fimo/labels';
2
+ import { nextServerContentRuntime } from './server-runtime.js';
3
+ /**
4
+ * Read the labels for an application-resolved locale. When omitted, the request
5
+ * carries no locale and the Fimo API answers with the project default locale.
6
+ */
7
+ export function getLabels(options) {
8
+ return fetchLabels(nextServerContentRuntime, options);
9
+ }
@@ -0,0 +1,13 @@
1
+ interface NextPreviewBrowser {
2
+ readonly window: Pick<Window, 'addEventListener' | 'location' | 'parent' | 'removeEventListener' | 'sessionStorage'>;
3
+ readonly document: Pick<Document, 'referrer'>;
4
+ }
5
+ type NextPreviewConnection = {
6
+ readonly locale: string;
7
+ readonly navigate: (to: string) => unknown;
8
+ readonly pathname: string;
9
+ readonly previewScriptUrl: string | undefined;
10
+ };
11
+ export declare function connectNextPreview({ locale, navigate, pathname, previewScriptUrl }: NextPreviewConnection, browser?: NextPreviewBrowser): () => void;
12
+ export {};
13
+ //# sourceMappingURL=preview.d.ts.map
@@ -0,0 +1,33 @@
1
+ import { getPreviewLocationChange, getPreviewParentOrigin, getSafePreviewNavigationPath, isPreviewRuntimeEnabled, } from 'fimo/preview';
2
+ export function connectNextPreview({ locale, navigate, pathname, previewScriptUrl }, browser = { window, document }) {
3
+ if (!isPreviewRuntimeEnabled(previewScriptUrl)) {
4
+ return () => undefined;
5
+ }
6
+ const parentOrigin = getPreviewParentOrigin(browser);
7
+ const sendLocationChange = () => {
8
+ browser.window.parent.postMessage({
9
+ type: 'nav/locationChange',
10
+ payload: getPreviewLocationChange({ pathname, search: browser.window.location.search, hash: browser.window.location.hash }, locale, parentOrigin),
11
+ }, parentOrigin ?? '*');
12
+ };
13
+ const handleMessage = (event) => {
14
+ if (event.source !== browser.window.parent || !parentOrigin || event.origin !== parentOrigin) {
15
+ return;
16
+ }
17
+ if (event.data?.type === 'nav/navigate') {
18
+ const target = getSafePreviewNavigationPath(event.data?.payload?.to, browser.window.location.origin);
19
+ if (target) {
20
+ navigate(target);
21
+ }
22
+ return;
23
+ }
24
+ if (event.data?.type === 'nav/read-routes') {
25
+ sendLocationChange();
26
+ }
27
+ };
28
+ browser.window.addEventListener('message', handleMessage);
29
+ sendLocationChange();
30
+ return () => {
31
+ browser.window.removeEventListener('message', handleMessage);
32
+ };
33
+ }
@@ -0,0 +1,10 @@
1
+ import type { TextProps } from 'fimo-react/primitives/server';
2
+ import { type ElementType } from 'react';
3
+ /**
4
+ * Server-safe text primitive for Next.js Server Components.
5
+ *
6
+ * It intentionally shares the same public props as the interactive React
7
+ * primitive while rendering source metadata without client hooks.
8
+ */
9
+ export declare function Text<T extends ElementType = 'span'>({ value, as, children, ...props }: TextProps<T>): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=Text.d.ts.map
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { getFimoParts, getFimoSource } from 'fimo/content';
3
+ import { Fragment } from 'react';
4
+ /**
5
+ * Server-safe text primitive for Next.js Server Components.
6
+ *
7
+ * It intentionally shares the same public props as the interactive React
8
+ * primitive while rendering source metadata without client hooks.
9
+ */
10
+ export function Text({ value, as, children, ...props }) {
11
+ const Component = as || 'span';
12
+ const parts = getFimoParts(value);
13
+ if (parts && parts.length > 1 && !children) {
14
+ return (_jsx(Component, { ...props, children: parts.map((part, index) => part.source ? (_jsx("span", { "data-fimo-source": part.source, children: part.text }, index)) : (_jsx(Fragment, { children: part.text }, index))) }));
15
+ }
16
+ const text = String(value);
17
+ return (_jsx(Component, { ...props, "data-fimo-source": getFimoSource(value), children: children ? children(text) : text }));
18
+ }
@@ -0,0 +1,11 @@
1
+ import type { BooleanProps, DateProps, ImageProps, JsonProps, RichTextProps, VideoProps } from 'fimo-react/primitives/server';
2
+ import { type ElementType } from 'react';
3
+ export declare function Boolean<T extends ElementType = 'span'>({ value, as, children, ...props }: BooleanProps<T>): import("react/jsx-runtime").JSX.Element | null;
4
+ export declare function Date({ value, ...props }: DateProps): import("react/jsx-runtime").JSX.Element | null;
5
+ /** Renders a `datetime` field: the `dateTime` attribute keeps the whole instant. */
6
+ export declare function DateTime({ value, ...props }: DateProps): import("react/jsx-runtime").JSX.Element | null;
7
+ export declare function Image(props: ImageProps): import("react/jsx-runtime").JSX.Element | null;
8
+ export declare function Json<T extends ElementType = 'span'>({ value, as, children, ...props }: JsonProps<T>): import("react/jsx-runtime").JSX.Element | null;
9
+ export declare function RichText({ value, components, ...props }: RichTextProps): import("react/jsx-runtime").JSX.Element | null;
10
+ export declare function Video(props: VideoProps): import("react/jsx-runtime").JSX.Element | null;
11
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { DateRenderer, ImageRenderer, RichTextRenderer, VideoRenderer } from 'fimo-react/primitives/server';
3
+ import { FIMO_SOURCE, getFimoSource } from 'fimo/content';
4
+ export function Boolean({ value, as, children, ...props }) {
5
+ if (!value) {
6
+ return null;
7
+ }
8
+ const Component = as || 'span';
9
+ const booleanValue = value.valueOf();
10
+ return (_jsx(Component, { ...props, "data-fimo-source": value[FIMO_SOURCE], children: children ? children(booleanValue) : String(booleanValue) }));
11
+ }
12
+ export function Date({ value, ...props }) {
13
+ if (!value) {
14
+ return null;
15
+ }
16
+ const source = typeof value === 'object' && FIMO_SOURCE in value ? (value[FIMO_SOURCE] ?? undefined) : undefined;
17
+ return _jsx(DateRenderer, { ...props, value: value, source: source });
18
+ }
19
+ /** Renders a `datetime` field: the `dateTime` attribute keeps the whole instant. */
20
+ export function DateTime({ value, ...props }) {
21
+ if (!value) {
22
+ return null;
23
+ }
24
+ const source = typeof value === 'object' && FIMO_SOURCE in value ? (value[FIMO_SOURCE] ?? undefined) : undefined;
25
+ return _jsx(DateRenderer, { ...props, value: value, source: source, precision: "instant" });
26
+ }
27
+ export function Image(props) {
28
+ if (!props.value) {
29
+ return null;
30
+ }
31
+ return _jsx(ImageRenderer, { ...props });
32
+ }
33
+ export function Json({ value, as, children, ...props }) {
34
+ if (value === null || value === undefined) {
35
+ return null;
36
+ }
37
+ const Component = as || 'span';
38
+ return (_jsx(Component, { ...props, "data-fimo-source": getFimoSource(value), children: children ? children(value) : JSON.stringify(value) }));
39
+ }
40
+ export function RichText({ value, components, ...props }) {
41
+ if (!value) {
42
+ return null;
43
+ }
44
+ return _jsx(RichTextRenderer, { ...props, document: value.content, source: value[FIMO_SOURCE], components: components });
45
+ }
46
+ export function Video(props) {
47
+ if (!props.value) {
48
+ return null;
49
+ }
50
+ return _jsx(VideoRenderer, { ...props });
51
+ }
@@ -0,0 +1,32 @@
1
+ import { QueryClient } from '@tanstack/react-query';
2
+ import { type Labels, type LabelsSnapshot } from 'fimo/labels';
3
+ import { type ReactNode } from 'react';
4
+ import { type FimoConnection } from './connection/contract.js';
5
+ export interface FimoProviderProps {
6
+ children: ReactNode;
7
+ labels?: LabelsSnapshot;
8
+ locale?: string;
9
+ /** Reuse an application-owned React Query client instead of the nearest one in context. */
10
+ queryClient?: QueryClient;
11
+ }
12
+ /**
13
+ * The client half of every Fimo Next integration: content-client connection,
14
+ * React Query, labels, and the Studio preview script. `FimoProvider` from
15
+ * `fimo-next` (App Router) and from `fimo-next/client` (Pages Router) both
16
+ * render it with the connection they resolved.
17
+ */
18
+ export declare function FimoClientProvider({ children, connection, labels, locale, queryClient, }: FimoProviderProps & {
19
+ connection: FimoConnection;
20
+ }): import("react/jsx-runtime").JSX.Element;
21
+ /**
22
+ * Pages Router root provider for `pages/_app`. It resolves the connection
23
+ * while the server renders `_app` and writes it into the document as inline
24
+ * JSON, which is what the browser bundle reads back: Pages Router has no
25
+ * server boundary above `_app`, so the render itself is the transport.
26
+ *
27
+ * The App Router root layout uses `FimoProvider` from `fimo-next` instead.
28
+ */
29
+ export declare function FimoProvider({ children, ...props }: FimoProviderProps): import("react/jsx-runtime").JSX.Element;
30
+ export declare function useNextContentLocale(requestedLocale: string | undefined, defaultLocale: string): string;
31
+ export declare function useLabels(): Labels;
32
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1,72 @@
1
+ 'use client';
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { QueryClient, QueryClientContext, QueryClientProvider } from '@tanstack/react-query';
4
+ import { createLabels } from 'fimo/labels';
5
+ import { ensurePreviewScript } from 'fimo/preview';
6
+ import { useRouter as usePagesRouter } from 'next/compat/router.js';
7
+ import { usePathname } from 'next/navigation.js';
8
+ import { createContext, Suspense, useCallback, useContext, useEffect, useMemo, useState } from 'react';
9
+ import { resolveConnection } from '#connection';
10
+ import { connectClientRuntime } from './client-runtime.js';
11
+ import { FIMO_CONNECTION_SCRIPT_ID, serializeConnection } from './connection/contract.js';
12
+ import { connectNextPreview } from './preview.js';
13
+ const FimoNextContext = createContext({});
14
+ /**
15
+ * The client half of every Fimo Next integration: content-client connection,
16
+ * React Query, labels, and the Studio preview script. `FimoProvider` from
17
+ * `fimo-next` (App Router) and from `fimo-next/client` (Pages Router) both
18
+ * render it with the connection they resolved.
19
+ */
20
+ export function FimoClientProvider({ children, connection, labels, locale, queryClient, }) {
21
+ // Render-phase on purpose: descendants' effects, where React Query starts
22
+ // fetching, run before this component's own effects.
23
+ connectClientRuntime(connection);
24
+ const inheritedQueryClient = useContext(QueryClientContext);
25
+ const [client] = useState(() => queryClient ?? inheritedQueryClient ?? new QueryClient());
26
+ const activeLocale = locale?.trim() || labels?.locale;
27
+ const value = useMemo(() => ({ connection, labels, locale: activeLocale }), [activeLocale, connection, labels]);
28
+ const previewScriptUrl = connection.previewScriptUrl;
29
+ return (_jsx(FimoNextContext.Provider, { value: value, children: _jsxs(QueryClientProvider, { client: client, children: [previewScriptUrl ? (_jsx(Suspense, { fallback: null, children: _jsx(FimoPreviewRuntime, { previewScriptUrl: previewScriptUrl }) })) : null, children] }) }));
30
+ }
31
+ /**
32
+ * Pages Router root provider for `pages/_app`. It resolves the connection
33
+ * while the server renders `_app` and writes it into the document as inline
34
+ * JSON, which is what the browser bundle reads back: Pages Router has no
35
+ * server boundary above `_app`, so the render itself is the transport.
36
+ *
37
+ * The App Router root layout uses `FimoProvider` from `fimo-next` instead.
38
+ */
39
+ export function FimoProvider({ children, ...props }) {
40
+ const [connection] = useState(resolveConnection);
41
+ return (_jsxs(_Fragment, { children: [_jsx("script", { id: FIMO_CONNECTION_SCRIPT_ID, type: "application/json", dangerouslySetInnerHTML: { __html: serializeConnection(connection) } }), _jsx(FimoClientProvider, { connection: connection, ...props, children: children })] }));
42
+ }
43
+ function FimoPreviewRuntime({ previewScriptUrl }) {
44
+ const context = useContext(FimoNextContext);
45
+ const locale = context.locale ?? context.connection?.defaultLocale ?? 'en';
46
+ const appPathname = usePathname();
47
+ const pagesRouter = usePagesRouter();
48
+ const pathname = appPathname ?? pagesRouter?.asPath.split(/[?#]/, 1)[0] ?? '/';
49
+ const navigate = useCallback((to) => {
50
+ if (pagesRouter) {
51
+ return pagesRouter.push(to);
52
+ }
53
+ window.history.pushState(null, '', to);
54
+ }, [pagesRouter]);
55
+ useEffect(() => {
56
+ ensurePreviewScript(previewScriptUrl);
57
+ return connectNextPreview({ locale, navigate, pathname, previewScriptUrl });
58
+ }, [locale, navigate, pathname, previewScriptUrl]);
59
+ return null;
60
+ }
61
+ export function useNextContentLocale(requestedLocale, defaultLocale) {
62
+ const context = useContext(FimoNextContext);
63
+ return requestedLocale ?? context.locale ?? defaultLocale;
64
+ }
65
+ export function useLabels() {
66
+ const context = useContext(FimoNextContext);
67
+ const locale = context.locale ?? context.connection?.defaultLocale ?? 'en';
68
+ const snapshot = context.labels?.locale === locale
69
+ ? context.labels
70
+ : { locale, labels: {}, updatedAt: null };
71
+ return useMemo(() => createLabels(snapshot), [snapshot]);
72
+ }
@@ -0,0 +1,17 @@
1
+ import type { LabelsSnapshot } from 'fimo/labels';
2
+ import type { ReactNode } from 'react';
3
+ export interface FimoProviderProps {
4
+ children: ReactNode;
5
+ /** Label snapshot from `getLabels()`; it also supplies the locale for client hooks. */
6
+ labels?: LabelsSnapshot;
7
+ /** Application-resolved locale for client hooks when no label snapshot is passed. */
8
+ locale?: string;
9
+ }
10
+ /**
11
+ * App Router root integration: a Server Component that resolves the Fimo
12
+ * connection on the server (explicit env, else the linked project and active
13
+ * git branch) and hands only that public data to the client provider. Render
14
+ * it once in the root layout; no page or component receives a runtime prop.
15
+ */
16
+ export declare function FimoProvider({ children, labels, locale }: FimoProviderProps): import("react/jsx-runtime").JSX.Element;
17
+ //# sourceMappingURL=server-provider.d.ts.map
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { resolveConnection } from '#connection';
3
+ import { FimoClientProvider } from './provider.js';
4
+ /**
5
+ * App Router root integration: a Server Component that resolves the Fimo
6
+ * connection on the server (explicit env, else the linked project and active
7
+ * git branch) and hands only that public data to the client provider. Render
8
+ * it once in the root layout; no page or component receives a runtime prop.
9
+ */
10
+ export function FimoProvider({ children, labels, locale }) {
11
+ return (_jsx(FimoClientProvider, { connection: resolveConnection(), labels: labels, locale: locale, children: children }));
12
+ }
@@ -0,0 +1,3 @@
1
+ import type { ContentRuntime } from 'fimo/content';
2
+ export declare const nextServerContentRuntime: ContentRuntime;
3
+ //# sourceMappingURL=server-runtime.d.ts.map
@@ -0,0 +1,23 @@
1
+ import { resolveConnection } from '#connection';
2
+ export const nextServerContentRuntime = {
3
+ apiBase() {
4
+ return resolveConnection().apiUrl;
5
+ },
6
+ defaultLocale() {
7
+ // `undefined` leaves `locale` off the request so the tenant API answers with
8
+ // the project default declared in `.fimo/config.json#i18n.defaultLocale`.
9
+ // Route-resolved locales reach Fimo through `createFimo`/`getLabels`.
10
+ return undefined;
11
+ },
12
+ headers(extra) {
13
+ const headers = { ...extra };
14
+ const env = resolveConnection().env;
15
+ if (env) {
16
+ headers['X-Fimo-Env'] = env;
17
+ }
18
+ return headers;
19
+ },
20
+ fetch(input, init) {
21
+ return globalThis.fetch(input, init);
22
+ },
23
+ };
@@ -0,0 +1,23 @@
1
+ import { StaticImage } from 'fimo-react/primitives/server';
2
+ import type { CollectionContentCore, ContentClientConfig, SingletonContentCore } from 'fimo/content';
3
+ export { StaticImage };
4
+ export { Text } from './primitives/Text.js';
5
+ export { Boolean, Date, DateTime, Image, Json, RichText, Video } from './primitives/server.js';
6
+ export { createFimo } from './facade.js';
7
+ export type { CreateFimoOptions, NextContentRegistry } from './facade.js';
8
+ export { FimoProvider } from './server-provider.js';
9
+ export type { FimoProviderProps } from './server-provider.js';
10
+ export { getLabels } from './labels.js';
11
+ export type { FormClient, FormSubmissionResult } from 'fimo/forms';
12
+ export type { GetLabelsOptions, Labels, LabelsSnapshot } from 'fimo/labels';
13
+ export type { FimoRichTextComponents, FimoRichTextMarkProps, FimoRichTextNodeProps, } from 'fimo-react/primitives/server';
14
+ export declare function createContentClient<T extends {
15
+ id: string;
16
+ }, TInput extends object>(config: ContentClientConfig): CollectionContentCore<T, TInput>;
17
+ export declare function createSingletonClient<T extends {
18
+ id: string;
19
+ }, TInput extends object>(config: ContentClientConfig): SingletonContentCore<T, TInput>;
20
+ export declare const formClient: import("fimo/forms").FormClient;
21
+ export type { CollectionContentCore as CollectionContentClient, SingletonContentCore as SingletonContentClient, } from 'fimo/content';
22
+ export * from 'fimo/content';
23
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1,18 @@
1
+ import { StaticImage } from 'fimo-react/primitives/server';
2
+ import { createServerContentClient, createServerSingletonClient } from 'fimo/content';
3
+ import { createFormClient } from 'fimo/forms';
4
+ import { nextServerContentRuntime } from './server-runtime.js';
5
+ export { StaticImage };
6
+ export { Text } from './primitives/Text.js';
7
+ export { Boolean, Date, DateTime, Image, Json, RichText, Video } from './primitives/server.js';
8
+ export { createFimo } from './facade.js';
9
+ export { FimoProvider } from './server-provider.js';
10
+ export { getLabels } from './labels.js';
11
+ export function createContentClient(config) {
12
+ return createServerContentClient(config, nextServerContentRuntime);
13
+ }
14
+ export function createSingletonClient(config) {
15
+ return createServerSingletonClient(config, nextServerContentRuntime);
16
+ }
17
+ export const formClient = createFormClient(nextServerContentRuntime);
18
+ export * from 'fimo/content';
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "fimo-next",
3
+ "version": "0.21.0-experimental.1",
4
+ "description": "Fimo runtime for Next.js projects.",
5
+ "files": [
6
+ "dist/",
7
+ "!dist/**/*.d.ts.map",
8
+ "README.md"
9
+ ],
10
+ "type": "module",
11
+ "sideEffects": false,
12
+ "imports": {
13
+ "#connection": {
14
+ "edge-light": "./dist/runtime/next/connection/edge.js",
15
+ "worker": "./dist/runtime/next/connection/edge.js",
16
+ "workerd": "./dist/runtime/next/connection/edge.js",
17
+ "browser": "./dist/runtime/next/connection/browser.js",
18
+ "default": "./dist/runtime/next/connection/node.js"
19
+ }
20
+ },
21
+ "exports": {
22
+ "./package.json": "./package.json",
23
+ ".": {
24
+ "types": "./dist/runtime/next/server.d.ts",
25
+ "import": "./dist/runtime/next/server.js"
26
+ },
27
+ "./client": {
28
+ "types": "./dist/runtime/next/client.d.ts",
29
+ "import": "./dist/runtime/next/client.js"
30
+ },
31
+ "./config": {
32
+ "types": "./dist/config.d.ts",
33
+ "import": "./dist/config.js",
34
+ "require": "./dist/config.js"
35
+ }
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc -b tsconfig.json",
42
+ "check:types": "tsc -b tsconfig.json --noEmit",
43
+ "clean": "rm -rf dist tsconfig.tsbuildinfo",
44
+ "test": "vitest run",
45
+ "test:watch": "vitest"
46
+ },
47
+ "dependencies": {
48
+ "fimo-react": "0.21.0-experimental.1"
49
+ },
50
+ "devDependencies": {
51
+ "@fimo/tsconfig": "0.21.0-experimental.1",
52
+ "@tanstack/react-query": "^5.80.6",
53
+ "@types/node": "^22.15.29",
54
+ "@types/react": "^19.2.14",
55
+ "@types/react-dom": "^19.2.3",
56
+ "fimo": "0.21.0-experimental.1",
57
+ "next": "16.3.0",
58
+ "react": "^19.2.4",
59
+ "react-dom": "^19.2.4",
60
+ "typescript": "7.0.2",
61
+ "vitest": "^4.1.6"
62
+ },
63
+ "peerDependencies": {
64
+ "@tanstack/react-query": "^5.80.6",
65
+ "fimo": ">=0.14.0",
66
+ "next": "^15.0.0 || ^16.0.0",
67
+ "react": "^18.2.0 || ^19.0.0",
68
+ "react-dom": "^18.2.0 || ^19.0.0"
69
+ },
70
+ "engines": {
71
+ "node": ">=20.12.0"
72
+ }
73
+ }