fimo 0.11.0 → 0.12.0-staging.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 (57) hide show
  1. package/README.md +7 -1
  2. package/assets/content-templates/hooks-template.eta +8 -8
  3. package/assets/content-templates/next-client-template.eta +102 -0
  4. package/assets/content-templates/next-server-template.eta +75 -0
  5. package/assets/content-templates/types-template.eta +2 -2
  6. package/assets/skills/fimo-cli/SKILL.md +2 -0
  7. package/assets/skills/fimo-cli/references/deploy.md +22 -1
  8. package/assets/skills/fimo-cli/references/preview.md +4 -3
  9. package/assets/skills/fimo-code/references/setup-plain-vite.md +4 -2
  10. package/assets/skills/fimo-code/references/setup-react-router.md +4 -2
  11. package/dist/cli/bundle.json +1 -1
  12. package/dist/cli/index.js +49 -47
  13. package/dist/runtime/content/client/collection-core.d.ts +37 -0
  14. package/dist/runtime/content/client/collection-core.js +149 -0
  15. package/dist/runtime/content/client/createContentClient.d.ts +3 -16
  16. package/dist/runtime/content/client/createContentClient.js +4 -321
  17. package/dist/runtime/content/client/createSingletonClient.js +3 -108
  18. package/dist/runtime/content/client/internal.d.ts +1 -3
  19. package/dist/runtime/content/client/internal.js +3 -21
  20. package/dist/runtime/content/client/react-content-client.d.ts +10 -0
  21. package/dist/runtime/content/client/react-content-client.js +165 -0
  22. package/dist/runtime/content/client/react-singleton-client.d.ts +7 -0
  23. package/dist/runtime/content/client/react-singleton-client.js +38 -0
  24. package/dist/runtime/content/client/runtime.d.ts +8 -0
  25. package/dist/runtime/content/client/runtime.js +7 -0
  26. package/dist/runtime/content/client/server-content-client.d.ts +9 -0
  27. package/dist/runtime/content/client/server-content-client.js +35 -0
  28. package/dist/runtime/content/client/singleton-core.d.ts +26 -0
  29. package/dist/runtime/content/client/singleton-core.js +73 -0
  30. package/dist/runtime/content/client/useQueryLocale.d.ts +1 -1
  31. package/dist/runtime/content/client/useQueryLocale.js +2 -3
  32. package/dist/runtime/content/client/vite-runtime.d.ts +3 -0
  33. package/dist/runtime/content/client/vite-runtime.js +23 -0
  34. package/dist/runtime/content/index.d.ts +16 -0
  35. package/dist/runtime/content/index.js +10 -0
  36. package/dist/runtime/content/serialization.d.ts +22 -0
  37. package/dist/runtime/content/serialization.js +42 -0
  38. package/dist/runtime/next/Text.d.ts +10 -0
  39. package/dist/runtime/next/Text.js +18 -0
  40. package/dist/runtime/next/client-runtime.d.ts +3 -0
  41. package/dist/runtime/next/client-runtime.js +20 -0
  42. package/dist/runtime/next/client.d.ts +16 -0
  43. package/dist/runtime/next/client.js +15 -0
  44. package/dist/runtime/next/provider.d.ts +10 -0
  45. package/dist/runtime/next/provider.js +12 -0
  46. package/dist/runtime/next/server-runtime.d.ts +3 -0
  47. package/dist/runtime/next/server-runtime.js +20 -0
  48. package/dist/runtime/next/server.d.ts +11 -0
  49. package/dist/runtime/next/server.js +10 -0
  50. package/dist/runtime/primitives/components/Text.d.ts +1 -2
  51. package/dist/runtime/primitives/lib/query.d.ts +1 -1
  52. package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts +4 -6
  53. package/dist/runtime/primitives/lib/useFimoLiveValue.js +4 -10
  54. package/dist/scripts/sync-schemas.js +67 -16
  55. package/package.json +14 -1
  56. package/release.json +2 -2
  57. package/templates/react-router/package.json +1 -1
@@ -1,6 +1,4 @@
1
1
  import { pascalCase } from 'change-case';
2
- // @ts-ignore - virtual module provided by fimoConfigPlugin in fimo/vite
3
- import fimoConfig from 'virtual:fimo-config';
4
2
  import { FIMO_SOURCE, FimoBoolean, FimoMedia, FimoRichText, FimoString } from '../../primitives/lib/primitives.js';
5
3
  export function interpretSchema(schema) {
6
4
  if (!schema || typeof schema.uid !== 'string' || schema.uid.length === 0) {
@@ -41,32 +39,16 @@ export function validateReferences(info, references) {
41
39
  }
42
40
  }
43
41
  }
44
- export function apiBase() {
45
- // @ts-ignore - import.meta.env is a Vite feature, replaced at build time for both client and SSR
46
- return import.meta.env.VITE_API_URL;
47
- }
48
- export function defaultLocale() {
49
- return fimoConfig.i18n?.defaultLocale ?? 'en';
50
- }
51
- export function fimoHeaders(extra) {
52
- const h = { ...extra };
53
- // @ts-ignore - import.meta.env is a Vite feature
54
- const env = import.meta.env.FIMO_ENV;
55
- if (env) {
56
- h['X-Fimo-Env'] = env;
57
- }
58
- return h;
59
- }
60
42
  function populateFields(populate) {
61
43
  if (populate == null) {
62
44
  return [];
63
45
  }
64
46
  return typeof populate === 'string' ? [populate] : [...populate];
65
47
  }
66
- export function readCollectionLocale(options, fallbackLocale = defaultLocale()) {
48
+ export function readCollectionLocale(options, fallbackLocale = 'en') {
67
49
  return options.locale ?? fallbackLocale;
68
50
  }
69
- export function readSingletonLocale(options, ownerUsesLocale, localizedReferenceFields, fallbackLocale = defaultLocale()) {
51
+ export function readSingletonLocale(options, ownerUsesLocale, localizedReferenceFields, fallbackLocale = 'en') {
70
52
  const populatesLocalizedTarget = populateFields(options.populate).some((field) => localizedReferenceFields.has(field));
71
53
  return ownerUsesLocale || populatesLocalizedTarget ? (options.locale ?? fallbackLocale) : undefined;
72
54
  }
@@ -276,7 +258,7 @@ export function wrapEntry(info, entry, options) {
276
258
  const id = entry.id;
277
259
  const entryId = entry.entryId;
278
260
  const documentId = entry.documentId ?? id;
279
- const locale = entry.locale ?? defaultLocale();
261
+ const locale = entry.locale ?? options.defaultLocale;
280
262
  const sourcePrefix = `${info.pascal}.id:${id}:${encodeURIComponent(locale)}`;
281
263
  const entryFimo = entry.__fimo && typeof entry.__fimo === 'object' ? entry.__fimo : {};
282
264
  const wrapped = {
@@ -0,0 +1,10 @@
1
+ import type { ContentRuntime } from './runtime.js';
2
+ import type { CollectionContentClient, ContentClientConfig } from './types.js';
3
+ export declare const SOURCE_TRACKED_QUERY_OPTIONS: {
4
+ readonly structuralSharing: false;
5
+ };
6
+ export type UseContentLocale = (requestedLocale: string | undefined, defaultLocale: string) => string;
7
+ export declare function createReactContentClient<T extends {
8
+ id: string;
9
+ }, TInput extends object>(config: ContentClientConfig, runtime: ContentRuntime, useContentLocale: UseContentLocale): CollectionContentClient<T, TInput>;
10
+ //# sourceMappingURL=react-content-client.d.ts.map
@@ -0,0 +1,165 @@
1
+ import { useInfiniteQuery, useMutation, useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
2
+ import { createCollectionContentCore } from './collection-core.js';
3
+ import { readCollectionLocale, withMutationLocale } from './internal.js';
4
+ export const SOURCE_TRACKED_QUERY_OPTIONS = { structuralSharing: false };
5
+ export function createReactContentClient(config, runtime, useContentLocale) {
6
+ const { client: core, defaultLocale, queryKeys } = createCollectionContentCore(config, runtime);
7
+ const { getById, getBySlug, getByField, list, create, update, remove } = core;
8
+ function activeLocale(requested) {
9
+ return useContentLocale(requested, defaultLocale);
10
+ }
11
+ function useGetById(id, options = {}) {
12
+ const localizedOptions = { ...options, locale: activeLocale(options.locale) };
13
+ return useQuery({
14
+ queryKey: queryKeys.byId(id, localizedOptions),
15
+ queryFn: () => getById(id, localizedOptions),
16
+ ...SOURCE_TRACKED_QUERY_OPTIONS,
17
+ });
18
+ }
19
+ function useGetBySlug(slug, options = {}) {
20
+ const locale = readCollectionLocale(options, activeLocale(options.locale));
21
+ const localizedOptions = { ...options, locale };
22
+ return useQuery({
23
+ queryKey: queryKeys.bySlug(slug, localizedOptions),
24
+ queryFn: () => getBySlug(slug, localizedOptions),
25
+ ...SOURCE_TRACKED_QUERY_OPTIONS,
26
+ enabled: !!slug,
27
+ });
28
+ }
29
+ function useGetByField(fieldName, value, options = {}) {
30
+ const locale = readCollectionLocale(options, activeLocale(options.locale));
31
+ const localizedOptions = { ...options, locale };
32
+ return useQuery({
33
+ queryKey: queryKeys.byField(fieldName, value, localizedOptions),
34
+ queryFn: () => getByField(fieldName, value, localizedOptions),
35
+ ...SOURCE_TRACKED_QUERY_OPTIONS,
36
+ enabled: !!value,
37
+ });
38
+ }
39
+ function useGet(params = {}) {
40
+ const locale = readCollectionLocale(params, activeLocale(params.locale));
41
+ const localizedParams = { ...params, locale };
42
+ return useQuery({
43
+ queryKey: queryKeys.list(localizedParams),
44
+ queryFn: () => list(localizedParams),
45
+ ...SOURCE_TRACKED_QUERY_OPTIONS,
46
+ select: (page) => page.items,
47
+ });
48
+ }
49
+ function useList(params = {}) {
50
+ const locale = readCollectionLocale(params, activeLocale(params.locale));
51
+ const localizedParams = { ...params, locale };
52
+ return useQuery({
53
+ queryKey: queryKeys.list(localizedParams),
54
+ queryFn: () => list(localizedParams),
55
+ ...SOURCE_TRACKED_QUERY_OPTIONS,
56
+ });
57
+ }
58
+ function useInfiniteList(params = {}) {
59
+ const locale = readCollectionLocale(params, activeLocale(params.locale));
60
+ const localizedParams = { ...params, locale };
61
+ const query = useInfiniteQuery({
62
+ queryKey: queryKeys.infiniteList(localizedParams),
63
+ queryFn: ({ pageParam }) => list({ ...localizedParams, offset: pageParam }),
64
+ ...SOURCE_TRACKED_QUERY_OPTIONS,
65
+ initialPageParam: 0,
66
+ getNextPageParam: (lastPage) => lastPage.pageInfo.nextOffset ?? undefined,
67
+ });
68
+ return {
69
+ ...query,
70
+ items: query.data?.pages.flatMap((page) => page.items) ?? [],
71
+ total: query.data?.pages[0]?.pageInfo.total ?? 0,
72
+ hasMore: query.hasNextPage,
73
+ loadMore: () => query.fetchNextPage(),
74
+ isLoadingMore: query.isFetchingNextPage,
75
+ };
76
+ }
77
+ function useSuspenseGetById(id, options = {}) {
78
+ const localizedOptions = { ...options, locale: activeLocale(options.locale) };
79
+ return useSuspenseQuery({
80
+ queryKey: queryKeys.byId(id, localizedOptions),
81
+ queryFn: () => getById(id, localizedOptions),
82
+ ...SOURCE_TRACKED_QUERY_OPTIONS,
83
+ });
84
+ }
85
+ function useSuspenseGetBySlug(slug, options = {}) {
86
+ const locale = readCollectionLocale(options, activeLocale(options.locale));
87
+ const localizedOptions = { ...options, locale };
88
+ return useSuspenseQuery({
89
+ queryKey: queryKeys.bySlug(slug, localizedOptions),
90
+ queryFn: () => getBySlug(slug, localizedOptions),
91
+ ...SOURCE_TRACKED_QUERY_OPTIONS,
92
+ });
93
+ }
94
+ function useSuspenseGetByField(fieldName, value, options = {}) {
95
+ const locale = readCollectionLocale(options, activeLocale(options.locale));
96
+ const localizedOptions = { ...options, locale };
97
+ return useSuspenseQuery({
98
+ queryKey: queryKeys.byField(fieldName, value, localizedOptions),
99
+ queryFn: () => getByField(fieldName, value, localizedOptions),
100
+ ...SOURCE_TRACKED_QUERY_OPTIONS,
101
+ });
102
+ }
103
+ function useSuspenseGet(params = {}) {
104
+ const locale = readCollectionLocale(params, activeLocale(params.locale));
105
+ const localizedParams = { ...params, locale };
106
+ return useSuspenseQuery({
107
+ queryKey: queryKeys.list(localizedParams),
108
+ queryFn: () => list(localizedParams),
109
+ ...SOURCE_TRACKED_QUERY_OPTIONS,
110
+ select: (page) => page.items,
111
+ });
112
+ }
113
+ function useCreate() {
114
+ const queryClient = useQueryClient();
115
+ const locale = activeLocale();
116
+ return useMutation({
117
+ mutationFn: (payload) => create(withMutationLocale(payload, locale)),
118
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: queryKeys.root }),
119
+ });
120
+ }
121
+ function useUpdate() {
122
+ const queryClient = useQueryClient();
123
+ const locale = activeLocale();
124
+ return useMutation({
125
+ mutationFn: ({ id, ...patch }) => update(id, withMutationLocale(patch, locale)),
126
+ onSuccess: (_data, variables) => {
127
+ queryClient.invalidateQueries({ queryKey: queryKeys.root });
128
+ queryClient.invalidateQueries({
129
+ queryKey: queryKeys.byId(variables.id, { locale: variables.locale ?? locale }),
130
+ });
131
+ },
132
+ });
133
+ }
134
+ function useRemove() {
135
+ const queryClient = useQueryClient();
136
+ const active = activeLocale();
137
+ return useMutation({
138
+ mutationFn: (value) => {
139
+ const { id, locale } = typeof value === 'string' ? { id: value, locale: active } : { ...value, locale: value.locale ?? active };
140
+ return remove(id, { locale });
141
+ },
142
+ onSuccess: (_data, value) => {
143
+ const { id, locale } = typeof value === 'string' ? { id: value, locale: active } : { ...value, locale: value.locale ?? active };
144
+ queryClient.invalidateQueries({ queryKey: queryKeys.root });
145
+ queryClient.removeQueries({ queryKey: queryKeys.byId(id, { locale }), exact: false });
146
+ },
147
+ });
148
+ }
149
+ return {
150
+ ...core,
151
+ useGetById,
152
+ useGetBySlug,
153
+ useGetByField,
154
+ useGet,
155
+ useList,
156
+ useInfiniteList,
157
+ useSuspenseGetById,
158
+ useSuspenseGetBySlug,
159
+ useSuspenseGetByField,
160
+ useSuspenseGet,
161
+ useCreate,
162
+ useUpdate,
163
+ useRemove,
164
+ };
165
+ }
@@ -0,0 +1,7 @@
1
+ import { type UseContentLocale } from './react-content-client.js';
2
+ import type { ContentRuntime } from './runtime.js';
3
+ import type { ContentClientConfig, SingletonContentClient } from './types.js';
4
+ export declare function createReactSingletonClient<T extends {
5
+ id: string;
6
+ }, TInput extends object>(config: ContentClientConfig, runtime: ContentRuntime, useContentLocale: UseContentLocale): SingletonContentClient<T, TInput>;
7
+ //# sourceMappingURL=react-singleton-client.d.ts.map
@@ -0,0 +1,38 @@
1
+ import { useMutation, useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
2
+ import { withMutationLocale } from './internal.js';
3
+ import { SOURCE_TRACKED_QUERY_OPTIONS } from './react-content-client.js';
4
+ import { createSingletonContentCore } from './singleton-core.js';
5
+ export function createReactSingletonClient(config, runtime, useContentLocale) {
6
+ const { client: core, defaultLocale, localize, queryKeys } = createSingletonContentCore(config, runtime);
7
+ const { get, update } = core;
8
+ function activeLocale(requested) {
9
+ return useContentLocale(requested, defaultLocale);
10
+ }
11
+ function useGet(options = {}) {
12
+ const locale = localize(options, activeLocale(options.locale));
13
+ const localizedOptions = { ...options, locale };
14
+ return useQuery({
15
+ queryKey: queryKeys.document(localizedOptions),
16
+ queryFn: () => get(localizedOptions),
17
+ ...SOURCE_TRACKED_QUERY_OPTIONS,
18
+ });
19
+ }
20
+ function useSuspenseGet(options = {}) {
21
+ const locale = localize(options, activeLocale(options.locale));
22
+ const localizedOptions = { ...options, locale };
23
+ return useSuspenseQuery({
24
+ queryKey: queryKeys.document(localizedOptions),
25
+ queryFn: () => get(localizedOptions),
26
+ ...SOURCE_TRACKED_QUERY_OPTIONS,
27
+ });
28
+ }
29
+ function useUpdate() {
30
+ const queryClient = useQueryClient();
31
+ const locale = activeLocale();
32
+ return useMutation({
33
+ mutationFn: (payload) => update(withMutationLocale(payload, locale)),
34
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: queryKeys.root }),
35
+ });
36
+ }
37
+ return { ...core, useGet, useSuspenseGet, useUpdate };
38
+ }
@@ -0,0 +1,8 @@
1
+ export interface ContentRuntime {
2
+ apiBase(): string;
3
+ defaultLocale(): string;
4
+ headers(extra?: Record<string, string>): Record<string, string>;
5
+ fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
6
+ }
7
+ export declare function requireContentApiUrl(value: string | undefined, source: string): string;
8
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1,7 @@
1
+ export function requireContentApiUrl(value, source) {
2
+ const apiUrl = value?.trim().replace(/\/$/, '');
3
+ if (!apiUrl) {
4
+ throw new Error(`Missing ${source}. Configure the Fimo content API URL before reading content.`);
5
+ }
6
+ return apiUrl;
7
+ }
@@ -0,0 +1,9 @@
1
+ import type { ContentRuntime } from './runtime.js';
2
+ import type { CollectionContentClient, ContentClientConfig, SingletonContentClient } from './types.js';
3
+ export declare function createServerContentClient<T extends {
4
+ id: string;
5
+ }, TInput extends object>(config: ContentClientConfig, runtime: ContentRuntime): CollectionContentClient<T, TInput>;
6
+ export declare function createServerSingletonClient<T extends {
7
+ id: string;
8
+ }, TInput extends object>(config: ContentClientConfig, runtime: ContentRuntime): SingletonContentClient<T, TInput>;
9
+ //# sourceMappingURL=server-content-client.d.ts.map
@@ -0,0 +1,35 @@
1
+ import { createCollectionContentCore } from './collection-core.js';
2
+ import { createSingletonContentCore } from './singleton-core.js';
3
+ function clientHook(name) {
4
+ return () => {
5
+ throw new Error(`${name} is a Client Component hook. Call it only below a "use client" boundary.`);
6
+ };
7
+ }
8
+ export function createServerContentClient(config, runtime) {
9
+ const { client } = createCollectionContentCore(config, runtime);
10
+ return {
11
+ ...client,
12
+ useGetById: clientHook('useGetById'),
13
+ useGetBySlug: clientHook('useGetBySlug'),
14
+ useGetByField: clientHook('useGetByField'),
15
+ useGet: clientHook('useGet'),
16
+ useList: clientHook('useList'),
17
+ useInfiniteList: clientHook('useInfiniteList'),
18
+ useSuspenseGetById: clientHook('useSuspenseGetById'),
19
+ useSuspenseGetBySlug: clientHook('useSuspenseGetBySlug'),
20
+ useSuspenseGetByField: clientHook('useSuspenseGetByField'),
21
+ useSuspenseGet: clientHook('useSuspenseGet'),
22
+ useCreate: clientHook('useCreate'),
23
+ useUpdate: clientHook('useUpdate'),
24
+ useRemove: clientHook('useRemove'),
25
+ };
26
+ }
27
+ export function createServerSingletonClient(config, runtime) {
28
+ const { client } = createSingletonContentCore(config, runtime);
29
+ return {
30
+ ...client,
31
+ useGet: clientHook('useGet'),
32
+ useSuspenseGet: clientHook('useSuspenseGet'),
33
+ useUpdate: clientHook('useUpdate'),
34
+ };
35
+ }
@@ -0,0 +1,26 @@
1
+ import type { Populate } from '../../primitives/lib/query.js';
2
+ import type { ContentRuntime } from './runtime.js';
3
+ import type { ContentClientConfig, ContentModuleMeta, ContentReadOptions, ContentResult } from './types.js';
4
+ export interface SingletonContentCore<T extends {
5
+ id: string;
6
+ }, TInput extends object> {
7
+ readonly meta: ContentModuleMeta;
8
+ wrapWithSource(entry: Record<string, any>): T;
9
+ get<TPopulate extends Populate<T> | undefined = undefined>(options?: ContentReadOptions<TPopulate>): Promise<ContentResult<T, undefined, TPopulate>>;
10
+ update(payload: Partial<TInput>): Promise<T>;
11
+ }
12
+ export interface SingletonCoreState<T extends {
13
+ id: string;
14
+ }, TInput extends object> {
15
+ client: SingletonContentCore<T, TInput>;
16
+ defaultLocale: string;
17
+ localize<TPopulate extends Populate<T> | undefined>(options: ContentReadOptions<TPopulate>, fallbackLocale?: string): string | undefined;
18
+ queryKeys: {
19
+ root: readonly [string];
20
+ document(options?: ContentReadOptions<Populate<T> | undefined>): readonly unknown[];
21
+ };
22
+ }
23
+ export declare function createSingletonContentCore<T extends {
24
+ id: string;
25
+ }, TInput extends object>(config: ContentClientConfig, runtime: ContentRuntime): SingletonCoreState<T, TInput>;
26
+ //# sourceMappingURL=singleton-core.d.ts.map
@@ -0,0 +1,73 @@
1
+ import { buildReadPath, interpretSchema, readSingletonLocale, referenceTargetUids, resolveClientHandle, splitWritePayload, validateReferences, wrapEntry, } from './internal.js';
2
+ export function createSingletonContentCore(config, runtime) {
3
+ const references = config.references ?? {};
4
+ const info = interpretSchema(config.schema);
5
+ if (!info.isSingleton) {
6
+ throw new Error(`Schema "${info.uid}" is a collection — use createContentClient instead.`);
7
+ }
8
+ validateReferences(info, references);
9
+ const defaultLocale = runtime.defaultLocale();
10
+ const meta = { uid: info.uid, pascal: info.pascal, localized: info.localized };
11
+ const self = { wrapWithSource, meta };
12
+ const resolve = (uid) => uid === info.uid ? self : resolveClientHandle(references[uid]);
13
+ let localizedReferenceFields = null;
14
+ function getLocalizedReferenceFields() {
15
+ if (localizedReferenceFields) {
16
+ return localizedReferenceFields;
17
+ }
18
+ const fields = new Set();
19
+ for (const [key, field] of info.fields) {
20
+ if (field.type !== 'reference') {
21
+ continue;
22
+ }
23
+ const localizedTarget = referenceTargetUids(field).some((uid) => resolve(uid)?.meta?.localized !== false);
24
+ if (localizedTarget) {
25
+ fields.add(key);
26
+ }
27
+ }
28
+ localizedReferenceFields = fields;
29
+ return fields;
30
+ }
31
+ function wrapWithSource(entry) {
32
+ return wrapEntry(info, entry, {
33
+ alwaysIncludeLocale: info.localized,
34
+ defaultLocale,
35
+ resolve,
36
+ });
37
+ }
38
+ function localize(options, fallbackLocale = defaultLocale) {
39
+ return readSingletonLocale(options, info.localized, getLocalizedReferenceFields(), fallbackLocale);
40
+ }
41
+ function readPath(url, options) {
42
+ return buildReadPath(url, localize(options), options.populate, options.localeFallbacks);
43
+ }
44
+ async function get(options = {}) {
45
+ const response = await runtime.fetch(`${runtime.apiBase()}${readPath(`/documents/${info.uid}`, options)}`, {
46
+ headers: runtime.headers(),
47
+ });
48
+ if (!response.ok) {
49
+ throw new Error(await response.text());
50
+ }
51
+ const body = await response.json();
52
+ return wrapWithSource(body.data);
53
+ }
54
+ async function update(payload) {
55
+ const write = splitWritePayload(payload);
56
+ const url = buildReadPath(`/documents/${info.uid}`, localize({ locale: write.locale }), undefined);
57
+ const response = await runtime.fetch(`${runtime.apiBase()}${url}`, {
58
+ method: 'PUT',
59
+ headers: runtime.headers({ 'Content-Type': 'application/json' }),
60
+ body: JSON.stringify({ data: write.data }),
61
+ });
62
+ if (!response.ok) {
63
+ throw new Error(await response.text());
64
+ }
65
+ const body = await response.json();
66
+ return wrapWithSource(body.data);
67
+ }
68
+ const queryKeys = {
69
+ root: [info.pascal],
70
+ document: (options = {}) => [info.pascal, 'document', options.locale, options.populate],
71
+ };
72
+ return { client: { meta, wrapWithSource, get, update }, defaultLocale, localize, queryKeys };
73
+ }
@@ -1,2 +1,2 @@
1
- export declare function useQueryLocale(locale?: string): string;
1
+ export declare function useQueryLocale(locale?: string, fallbackLocale?: string): string;
2
2
  //# sourceMappingURL=useQueryLocale.d.ts.map
@@ -1,6 +1,5 @@
1
1
  import { useLocale } from '../../i18n/FimoLocaleContext.js';
2
- import { defaultLocale } from './internal.js';
3
- export function useQueryLocale(locale) {
2
+ export function useQueryLocale(locale, fallbackLocale = 'en') {
4
3
  const activeLocale = useLocale().locale;
5
- return locale ?? activeLocale ?? defaultLocale();
4
+ return locale ?? activeLocale ?? fallbackLocale;
6
5
  }
@@ -0,0 +1,3 @@
1
+ import type { ContentRuntime } from './runtime.js';
2
+ export declare const viteContentRuntime: ContentRuntime;
3
+ //# sourceMappingURL=vite-runtime.d.ts.map
@@ -0,0 +1,23 @@
1
+ // @ts-ignore - virtual module provided by fimoConfigPlugin in fimo/vite
2
+ import fimoConfig from 'virtual:fimo-config';
3
+ export const viteContentRuntime = {
4
+ apiBase() {
5
+ // @ts-ignore - import.meta.env is a Vite feature
6
+ return import.meta.env.VITE_API_URL;
7
+ },
8
+ defaultLocale() {
9
+ return fimoConfig.i18n?.defaultLocale ?? 'en';
10
+ },
11
+ headers(extra) {
12
+ const headers = { ...extra };
13
+ // @ts-ignore - import.meta.env is a Vite feature
14
+ const env = import.meta.env.FIMO_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,16 @@
1
+ import type { ContentRuntime } from './client/runtime.js';
2
+ import type { ContentClientConfig } from './client/types.js';
3
+ export declare function createContentCore<T extends {
4
+ id: string;
5
+ }, TInput extends object>(config: ContentClientConfig, runtime: ContentRuntime): import("./index.js").CollectionContentCore<T, TInput>;
6
+ export declare function createSingletonCore<T extends {
7
+ id: string;
8
+ }, TInput extends object>(config: ContentClientConfig, runtime: ContentRuntime): import("./index.js").SingletonContentCore<T, TInput>;
9
+ export type { CollectionContentCore } from './client/collection-core.js';
10
+ export type { ContentRuntime } from './client/runtime.js';
11
+ export type { SingletonContentCore } from './client/singleton-core.js';
12
+ export { serializeContent, type SerializedContent, type SerializedMedia } from './serialization.js';
13
+ export type { CollectionPage, CollectionPageInfo, ContentClientConfig, ContentClientHandle, ContentModuleMeta, ContentModuleRef, ContentReadOptions, ContentResult, ContentSchema, ContentSchemaField, } from './client/types.js';
14
+ export { FIMO_PARTS, FIMO_SOURCE, FimoBoolean, FimoDate, FimoMedia, FimoRichText, FimoString, getFimoParts, getFimoSource, hasFimoSource, isFimoDate, isFimoString, type FimoPart, type FimoPolymorphicReference, type FimoReference, type FimoRichTextComponents, type FimoRichTextContent, type FimoRichTextMarkProps, type FimoRichTextNodeProps, type FimoSource, } from '../primitives/lib/primitives.js';
15
+ export type { BoolOps, DateOps, Field, Fields, FilterFor, NullOnlyOps, NumberOps, Populate, Populated, PopulatedResult, Projected, ProjectedWithPopulate, Query, ReferenceField, ReferenceMap, ScalarOps, Sort, SortField, StringOps, Where, WithReferences, } from '../primitives/lib/query.js';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,10 @@
1
+ import { createCollectionContentCore } from './client/collection-core.js';
2
+ import { createSingletonContentCore } from './client/singleton-core.js';
3
+ export function createContentCore(config, runtime) {
4
+ return createCollectionContentCore(config, runtime).client;
5
+ }
6
+ export function createSingletonCore(config, runtime) {
7
+ return createSingletonContentCore(config, runtime).client;
8
+ }
9
+ export { serializeContent } from './serialization.js';
10
+ export { FIMO_PARTS, FIMO_SOURCE, FimoBoolean, FimoDate, FimoMedia, FimoRichText, FimoString, getFimoParts, getFimoSource, hasFimoSource, isFimoDate, isFimoString, } from '../primitives/lib/primitives.js';
@@ -0,0 +1,22 @@
1
+ import { FimoBoolean, FimoDate, FimoMedia, FimoRichText, FimoString, type FimoRichTextContent } from '../primitives/lib/primitives.js';
2
+ export interface SerializedMedia {
3
+ url: string;
4
+ alt: string;
5
+ name: string;
6
+ mime: string;
7
+ width?: number;
8
+ height?: number;
9
+ }
10
+ /** The plain-data counterpart of source-tracked Fimo content. */
11
+ export type SerializedContent<T> = T extends FimoString ? string : T extends FimoDate ? string : T extends FimoBoolean ? boolean : T extends FimoMedia ? SerializedMedia : T extends FimoRichText ? FimoRichTextContent : T extends readonly (infer TItem)[] ? SerializedContent<TItem>[] : T extends object ? {
12
+ [TKey in keyof T]: SerializedContent<T[TKey]>;
13
+ } : T;
14
+ /**
15
+ * Convert source-tracked content to the API-shaped plain data required by
16
+ * framework boundaries such as Next.js Pages Router props.
17
+ *
18
+ * Use the generated schema module's `hydrate` function on the receiving side
19
+ * to restore source tracking without shipping private metadata in the payload.
20
+ */
21
+ export declare function serializeContent<T>(value: T): SerializedContent<T>;
22
+ //# sourceMappingURL=serialization.d.ts.map
@@ -0,0 +1,42 @@
1
+ import { FimoBoolean, FimoDate, FimoMedia, FimoRichText, FimoString, } from '../primitives/lib/primitives.js';
2
+ /**
3
+ * Convert source-tracked content to the API-shaped plain data required by
4
+ * framework boundaries such as Next.js Pages Router props.
5
+ *
6
+ * Use the generated schema module's `hydrate` function on the receiving side
7
+ * to restore source tracking without shipping private metadata in the payload.
8
+ */
9
+ export function serializeContent(value) {
10
+ if (value instanceof FimoString) {
11
+ return value.toString();
12
+ }
13
+ if (value instanceof FimoDate) {
14
+ return value.toISOString();
15
+ }
16
+ if (value instanceof FimoBoolean) {
17
+ return value.valueOf();
18
+ }
19
+ if (value instanceof FimoMedia) {
20
+ const media = {
21
+ url: value.url,
22
+ alt: value.alt,
23
+ name: value.name,
24
+ mime: value.mime,
25
+ ...(value.width === undefined ? {} : { width: value.width }),
26
+ ...(value.height === undefined ? {} : { height: value.height }),
27
+ };
28
+ return media;
29
+ }
30
+ if (value instanceof FimoRichText) {
31
+ return serializeContent(value.content);
32
+ }
33
+ if (Array.isArray(value)) {
34
+ return value.map((item) => serializeContent(item));
35
+ }
36
+ if (value !== null && typeof value === 'object') {
37
+ return Object.fromEntries(Object.entries(value)
38
+ .filter(([, item]) => item !== undefined)
39
+ .map(([key, item]) => [key, serializeContent(item)]));
40
+ }
41
+ return value;
42
+ }
@@ -0,0 +1,10 @@
1
+ import { type ElementType } from 'react';
2
+ import type { TextProps } from '../primitives/components/Text.js';
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 { Fragment } from 'react';
3
+ import { getFimoParts, getFimoSource } from '../content/index.js';
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,3 @@
1
+ import { type ContentRuntime } from '../content/client/runtime.js';
2
+ export declare const nextClientContentRuntime: ContentRuntime;
3
+ //# sourceMappingURL=client-runtime.d.ts.map
@@ -0,0 +1,20 @@
1
+ import { requireContentApiUrl } from '../content/client/runtime.js';
2
+ export const nextClientContentRuntime = {
3
+ apiBase() {
4
+ return requireContentApiUrl(process.env.NEXT_PUBLIC_FIMO_API_URL, 'NEXT_PUBLIC_FIMO_API_URL');
5
+ },
6
+ defaultLocale() {
7
+ return process.env.NEXT_PUBLIC_FIMO_DEFAULT_LOCALE?.trim() || 'en';
8
+ },
9
+ headers(extra) {
10
+ const headers = { ...extra };
11
+ const env = process.env.NEXT_PUBLIC_FIMO_ENV?.trim();
12
+ if (env) {
13
+ headers['X-Fimo-Env'] = env;
14
+ }
15
+ return headers;
16
+ },
17
+ fetch(input, init) {
18
+ return globalThis.fetch(input, init);
19
+ },
20
+ };