fimo 0.6.0 → 0.7.0-experimental.2

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 (145) hide show
  1. package/README.md +1 -1
  2. package/assets/content-templates/hooks-template.eta +73 -27
  3. package/assets/content-templates/types-template.eta +4 -1
  4. package/assets/skills/fimo/references/content.md +46 -42
  5. package/assets/skills/fimo-cli/references/content.md +14 -14
  6. package/assets/skills/fimo-cli/references/domains.md +1 -1
  7. package/assets/skills/fimo-studio/references/custom-domains.md +1 -1
  8. package/dist/cli/bundle.json +3 -2
  9. package/dist/cli/index.js +1203 -110450
  10. package/dist/runtime/content/client/createContentClient.d.ts +5 -0
  11. package/dist/runtime/content/client/createContentClient.js +300 -0
  12. package/dist/runtime/content/client/createSingletonClient.d.ts +5 -0
  13. package/dist/runtime/content/client/createSingletonClient.js +108 -0
  14. package/dist/runtime/content/client/internal.d.ts +44 -0
  15. package/dist/runtime/content/client/internal.js +236 -0
  16. package/dist/runtime/content/client/types.d.ts +136 -0
  17. package/dist/runtime/content/client/useQueryLocale.d.ts +2 -0
  18. package/dist/runtime/content/client/useQueryLocale.js +6 -0
  19. package/dist/runtime/content/utils/retrieve-data-sources.d.ts +1 -0
  20. package/dist/runtime/content/utils/retrieve-data-sources.js +27 -4
  21. package/dist/runtime/iframe/runtime/overlay-hmr.js +3 -0
  22. package/dist/runtime/index.d.ts +3 -0
  23. package/dist/runtime/index.js +2 -0
  24. package/dist/runtime/primitives/lib/primitives.d.ts +2 -2
  25. package/dist/runtime/primitives/lib/query.d.ts +5 -3
  26. package/package.json +31 -13
  27. package/release.json +2 -2
  28. package/templates/react-router/package.json +1 -1
  29. package/templates/react-router/src/pages/Index.tsx +1 -1
  30. package/dist/build/vite/index.d.ts.map +0 -1
  31. package/dist/build/vite/plugins/content-sync.d.ts.map +0 -1
  32. package/dist/build/vite/plugins/content-sync.test.d.ts +0 -2
  33. package/dist/build/vite/plugins/content-sync.test.d.ts.map +0 -1
  34. package/dist/build/vite/plugins/content-sync.test.js +0 -64
  35. package/dist/build/vite/plugins/data-id.d.ts.map +0 -1
  36. package/dist/build/vite/plugins/fimo-config.d.ts.map +0 -1
  37. package/dist/build/vite/plugins/fimo-config.test.d.ts +0 -2
  38. package/dist/build/vite/plugins/fimo-config.test.d.ts.map +0 -1
  39. package/dist/build/vite/plugins/fimo-config.test.js +0 -46
  40. package/dist/build/vite/plugins/overlay.d.ts.map +0 -1
  41. package/dist/build/vite/plugins/robots.d.ts.map +0 -1
  42. package/dist/build/vite/plugins/seo.d.ts.map +0 -1
  43. package/dist/build/vite/plugins/server-config.d.ts.map +0 -1
  44. package/dist/build/vite/plugins/sitemap.d.ts.map +0 -1
  45. package/dist/build/vite/plugins/translations.d.ts.map +0 -1
  46. package/dist/build/vite/plugins/translations.test.d.ts +0 -2
  47. package/dist/build/vite/plugins/translations.test.d.ts.map +0 -1
  48. package/dist/build/vite/plugins/translations.test.js +0 -266
  49. package/dist/runtime/app/FimoProviders.d.ts.map +0 -1
  50. package/dist/runtime/app/FimoScripts.d.ts.map +0 -1
  51. package/dist/runtime/app/index.d.ts.map +0 -1
  52. package/dist/runtime/app/prefetch.d.ts.map +0 -1
  53. package/dist/runtime/content/DataSourcesProvider.d.ts.map +0 -1
  54. package/dist/runtime/content/RefreshContentProvider.d.ts.map +0 -1
  55. package/dist/runtime/content/RefreshContentProvider.test.d.ts +0 -2
  56. package/dist/runtime/content/RefreshContentProvider.test.d.ts.map +0 -1
  57. package/dist/runtime/content/RefreshContentProvider.test.js +0 -33
  58. package/dist/runtime/content/parseCollectionResponse.d.ts.map +0 -1
  59. package/dist/runtime/content/parseCollectionResponse.test.d.ts +0 -2
  60. package/dist/runtime/content/parseCollectionResponse.test.d.ts.map +0 -1
  61. package/dist/runtime/content/parseCollectionResponse.test.js +0 -20
  62. package/dist/runtime/content/utils/retrieve-data-sources.d.ts.map +0 -1
  63. package/dist/runtime/core/AppErrorBoundary.d.ts.map +0 -1
  64. package/dist/runtime/core/ViteLifecycles.d.ts.map +0 -1
  65. package/dist/runtime/i18n/FimoLocaleContext.d.ts.map +0 -1
  66. package/dist/runtime/i18n/locale.d.ts.map +0 -1
  67. package/dist/runtime/i18n/locale.test.d.ts +0 -2
  68. package/dist/runtime/i18n/locale.test.d.ts.map +0 -1
  69. package/dist/runtime/i18n/locale.test.js +0 -58
  70. package/dist/runtime/iframe/ConsoleErrorProvider.d.ts.map +0 -1
  71. package/dist/runtime/iframe/ScreenshotProvider.d.ts.map +0 -1
  72. package/dist/runtime/iframe/runtime/overlay-hmr.d.ts.map +0 -1
  73. package/dist/runtime/iframe/runtime/overlay-runtime.d.ts.map +0 -1
  74. package/dist/runtime/index.d.ts.map +0 -1
  75. package/dist/runtime/paths/get-fimo-paths.d.ts.map +0 -1
  76. package/dist/runtime/paths/get-fimo-paths.test.d.ts +0 -2
  77. package/dist/runtime/paths/get-fimo-paths.test.d.ts.map +0 -1
  78. package/dist/runtime/paths/get-fimo-paths.test.js +0 -69
  79. package/dist/runtime/paths/index.d.ts.map +0 -1
  80. package/dist/runtime/paths/types.d.ts.map +0 -1
  81. package/dist/runtime/primitives/components/Boolean.d.ts.map +0 -1
  82. package/dist/runtime/primitives/components/Date.d.ts.map +0 -1
  83. package/dist/runtime/primitives/components/Image.d.ts.map +0 -1
  84. package/dist/runtime/primitives/components/RichText.d.ts.map +0 -1
  85. package/dist/runtime/primitives/components/StaticImage.d.ts.map +0 -1
  86. package/dist/runtime/primitives/components/Text.d.ts.map +0 -1
  87. package/dist/runtime/primitives/components/Video.d.ts.map +0 -1
  88. package/dist/runtime/primitives/components/index.d.ts.map +0 -1
  89. package/dist/runtime/primitives/index.d.ts.map +0 -1
  90. package/dist/runtime/primitives/lib/image-optimization.d.ts.map +0 -1
  91. package/dist/runtime/primitives/lib/index.d.ts.map +0 -1
  92. package/dist/runtime/primitives/lib/primitives.d.ts.map +0 -1
  93. package/dist/runtime/primitives/lib/query.d.ts.map +0 -1
  94. package/dist/runtime/primitives/lib/template.d.ts.map +0 -1
  95. package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts.map +0 -1
  96. package/dist/runtime/primitives/public-surface.test.d.ts +0 -2
  97. package/dist/runtime/primitives/public-surface.test.d.ts.map +0 -1
  98. package/dist/runtime/primitives/public-surface.test.js +0 -45
  99. package/dist/runtime/primitives/translations.d.ts.map +0 -1
  100. package/dist/runtime/react-router/index.d.ts.map +0 -1
  101. package/dist/runtime/react-router/navigation-utils.d.ts.map +0 -1
  102. package/dist/runtime/react-router/navigation.d.ts.map +0 -1
  103. package/dist/runtime/react-router/navigation.test.d.ts +0 -2
  104. package/dist/runtime/react-router/navigation.test.d.ts.map +0 -1
  105. package/dist/runtime/react-router/navigation.test.js +0 -35
  106. package/dist/runtime/react-router/routes.d.ts.map +0 -1
  107. package/dist/runtime/react-router/routes.test.d.ts +0 -2
  108. package/dist/runtime/react-router/routes.test.d.ts.map +0 -1
  109. package/dist/runtime/react-router/routes.test.js +0 -67
  110. package/dist/runtime/routing/AppResetScrollOnNavigate.d.ts.map +0 -1
  111. package/dist/runtime/routing/AppRoutesProvider.d.ts.map +0 -1
  112. package/dist/runtime/seo/buildSeoForPath.d.ts.map +0 -1
  113. package/dist/runtime/seo/htmlProps.d.ts.map +0 -1
  114. package/dist/runtime/seo/index.d.ts.map +0 -1
  115. package/dist/runtime/seo/resolver.d.ts.map +0 -1
  116. package/dist/runtime/shared/fimo-config.server.d.ts.map +0 -1
  117. package/dist/runtime/shared/fimo-config.types.d.ts.map +0 -1
  118. package/dist/runtime/t/OptionalTracking.d.ts.map +0 -1
  119. package/dist/runtime/t/OptionalTracking.test.d.ts +0 -2
  120. package/dist/runtime/t/OptionalTracking.test.d.ts.map +0 -1
  121. package/dist/runtime/t/OptionalTracking.test.js +0 -11
  122. package/dist/runtime/t/Pageview.d.ts.map +0 -1
  123. package/dist/runtime/templates.d.ts.map +0 -1
  124. package/dist/scripts/generated-types.test.js +0 -212
  125. package/dist/scripts/lint-translation-keys.test.d.ts +0 -1
  126. package/dist/scripts/lint-translation-keys.test.js +0 -16
  127. package/dist/scripts/sync-schemas.test.d.ts +0 -1
  128. package/dist/scripts/sync-schemas.test.js +0 -124
  129. package/dist/scripts/validate-route-metadata.test.d.ts +0 -1
  130. package/dist/scripts/validate-route-metadata.test.js +0 -116
  131. package/dist/scripts/validate-schemas.test.d.ts +0 -1
  132. package/dist/scripts/validate-schemas.test.js +0 -263
  133. package/scripts/add-ext.mjs +0 -47
  134. package/scripts/bump-version.mjs +0 -58
  135. package/scripts/bundle-cli.mjs +0 -123
  136. package/scripts/install-cli.mjs +0 -36
  137. package/scripts/lib/channels.mjs +0 -70
  138. package/scripts/lib/dev-release-state.mjs +0 -44
  139. package/scripts/lib/dev-release-state.test.ts +0 -36
  140. package/scripts/lib/postinstall-onboarding.test.ts +0 -82
  141. package/scripts/lib/release-core.mjs +0 -225
  142. package/scripts/lib/release-core.test.ts +0 -147
  143. package/scripts/migration-qa-gate.test.ts +0 -155
  144. package/scripts/publish-npm.mjs +0 -180
  145. /package/dist/{scripts/generated-types.test.d.ts → runtime/content/client/types.js} +0 -0
@@ -0,0 +1,236 @@
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
+ import { FimoBoolean, FimoMedia, FimoRichText, FimoString } from '../../primitives/lib/primitives.js';
5
+ export function interpretSchema(schema) {
6
+ if (!schema || typeof schema.uid !== 'string' || schema.uid.length === 0) {
7
+ throw new Error('Content schema is missing a "uid".');
8
+ }
9
+ return {
10
+ uid: schema.uid,
11
+ pascal: pascalCase(schema.uid),
12
+ localized: schema.i18n?.enabled !== false,
13
+ isSingleton: schema.isSingleton ?? false,
14
+ // Codegen tolerates malformed field values (null, arrays, primitives) by
15
+ // treating them as untyped passthrough fields — mirror that.
16
+ fields: Object.entries(schema.fields ?? {}).map(([key, value]) => [key, value && typeof value === 'object' && !Array.isArray(value) ? value : {}]),
17
+ };
18
+ }
19
+ // Codegen treats an empty or non-string-bearing `targets` array as absent and
20
+ // falls back to the monomorphic `target` — mirror that normalization so the
21
+ // polymorphic/fixed decision, validation, and the singleton locale rule agree.
22
+ function polymorphicTargetUids(field) {
23
+ if (!Array.isArray(field.targets)) {
24
+ return undefined;
25
+ }
26
+ const targets = field.targets.filter((uid) => typeof uid === 'string');
27
+ return targets.length > 0 ? targets : undefined;
28
+ }
29
+ export function referenceTargetUids(field) {
30
+ return polymorphicTargetUids(field) ?? (field.target ? [field.target] : []);
31
+ }
32
+ export function validateReferences(info, references) {
33
+ for (const [key, field] of info.fields) {
34
+ if (field.type !== 'reference') {
35
+ continue;
36
+ }
37
+ for (const uid of referenceTargetUids(field)) {
38
+ if (uid !== info.uid && !(uid in references)) {
39
+ throw new Error(`Schema "${info.uid}" field "${key}" references "${uid}", but "${uid}" is missing from the client's references map.`);
40
+ }
41
+ }
42
+ }
43
+ }
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 branch = import.meta.env.VITE_FIMO_BRANCH;
55
+ if (branch) {
56
+ h['X-Fimo-Branch'] = branch;
57
+ }
58
+ return h;
59
+ }
60
+ function populateFields(populate) {
61
+ if (populate == null) {
62
+ return [];
63
+ }
64
+ return typeof populate === 'string' ? [populate] : [...populate];
65
+ }
66
+ export function readCollectionLocale(options, fallbackLocale = defaultLocale()) {
67
+ return options.locale ?? fallbackLocale;
68
+ }
69
+ export function readSingletonLocale(options, ownerUsesLocale, localizedReferenceFields, fallbackLocale = defaultLocale()) {
70
+ const populatesLocalizedTarget = populateFields(options.populate).some((field) => localizedReferenceFields.has(field));
71
+ return ownerUsesLocale || populatesLocalizedTarget ? (options.locale ?? fallbackLocale) : undefined;
72
+ }
73
+ export function buildReadPath(url, locale, populate, localeFallbacks) {
74
+ const u = new URL(url, 'http://fimo.local');
75
+ if (locale) {
76
+ u.searchParams.set('locale', locale);
77
+ }
78
+ if (populate != null) {
79
+ u.searchParams.set('populate', typeof populate === 'string' ? populate : populate.join(','));
80
+ }
81
+ if (localeFallbacks) {
82
+ u.searchParams.set('localeFallbacks', 'true');
83
+ }
84
+ return `${u.pathname}${u.search}`;
85
+ }
86
+ export function splitWritePayload(payload) {
87
+ const { id, entryId, documentId, locale, createdAt, updatedAt, ...data } = payload;
88
+ delete data.__fimo;
89
+ void id;
90
+ void entryId;
91
+ void createdAt;
92
+ void updatedAt;
93
+ return {
94
+ data,
95
+ ...(typeof locale === 'string' && locale.length > 0 ? { locale } : {}),
96
+ ...(typeof documentId === 'string' && documentId.length > 0 ? { documentId } : {}),
97
+ };
98
+ }
99
+ export function withMutationLocale(payload, activeLocale) {
100
+ const locale = payload.locale;
101
+ return { ...payload, locale: locale ?? activeLocale };
102
+ }
103
+ function isPopulatedEntry(value) {
104
+ if (value == null || typeof value !== 'object') {
105
+ return false;
106
+ }
107
+ const entry = value;
108
+ const fimo = entry.__fimo;
109
+ return (typeof entry.id === 'string' &&
110
+ fimo != null &&
111
+ typeof fimo === 'object' &&
112
+ typeof fimo.contentType === 'string');
113
+ }
114
+ function asClientHandle(candidate) {
115
+ if (candidate == null || typeof candidate !== 'object') {
116
+ return undefined;
117
+ }
118
+ const handle = candidate;
119
+ return typeof handle.wrapWithSource === 'function' ? handle : undefined;
120
+ }
121
+ /**
122
+ * A reference is either a client or a module namespace whose default export
123
+ * is the client. Only ever called at request time — unwrapping a namespace's
124
+ * `default` mid-cycle would read an uninitialized binding. A namespace can
125
+ * plausibly carry BOTH a re-exported wrapWithSource and a default client, so
126
+ * prefer whichever shape carries meta — the locale rules depend on it.
127
+ */
128
+ export function resolveClientHandle(ref) {
129
+ if (ref == null) {
130
+ return undefined;
131
+ }
132
+ const direct = asClientHandle(ref);
133
+ const viaDefault = asClientHandle(ref.default);
134
+ if (viaDefault?.meta) {
135
+ return viaDefault;
136
+ }
137
+ if (direct?.meta) {
138
+ return direct;
139
+ }
140
+ return direct ?? viaDefault;
141
+ }
142
+ function unresolvableReference(uid) {
143
+ // The template's equivalent was a compile-time import error; degrading to
144
+ // silently unwrapped values would kill inline editing invisibly instead.
145
+ return new Error(`Cannot wrap populated reference to "${uid}": its module did not resolve to a content client.`);
146
+ }
147
+ /**
148
+ * Populated polymorphic entries carry their type marker; the wrapper picks
149
+ * the right per-type wrapping and stamps a top-level $type so the generated
150
+ * union narrows in a switch. Raw (unpopulated) values and types outside the
151
+ * field's declared targets pass through untouched.
152
+ */
153
+ function wrapPolymorphic(value, targetUids, resolve) {
154
+ if (!isPopulatedEntry(value)) {
155
+ return value;
156
+ }
157
+ const contentType = typeof value.__fimo.contentType === 'string' ? value.__fimo.contentType : null;
158
+ if (!contentType || !targetUids.includes(contentType)) {
159
+ return value;
160
+ }
161
+ const target = resolve(contentType);
162
+ if (!target) {
163
+ throw unresolvableReference(contentType);
164
+ }
165
+ const wrapped = target.wrapWithSource(value);
166
+ wrapped.$type = contentType;
167
+ return wrapped;
168
+ }
169
+ function wrapReference(value, uid, resolve) {
170
+ if (!isPopulatedEntry(value)) {
171
+ return value;
172
+ }
173
+ const target = resolve(uid);
174
+ if (!target) {
175
+ throw unresolvableReference(uid);
176
+ }
177
+ return target.wrapWithSource(value);
178
+ }
179
+ function wrapField(field, value, source, resolve) {
180
+ switch (field.type) {
181
+ case 'string':
182
+ return value != null ? new FimoString(value, source) : value;
183
+ case 'richtext':
184
+ return value != null ? new FimoRichText(value, source) : value;
185
+ case 'boolean':
186
+ return value != null ? new FimoBoolean(value, source) : value;
187
+ case 'media':
188
+ if (field.multiple) {
189
+ return (value?.map((m, i) => new FimoMedia(m, `${source}[${i}]`)) ?? value);
190
+ }
191
+ return value != null ? new FimoMedia(value, source) : value;
192
+ case 'reference': {
193
+ const polymorphic = polymorphicTargetUids(field);
194
+ if (polymorphic) {
195
+ if (field.many) {
196
+ return Array.isArray(value) ? value.map((item) => wrapPolymorphic(item, polymorphic, resolve)) : value;
197
+ }
198
+ return wrapPolymorphic(value, polymorphic, resolve);
199
+ }
200
+ const uid = field.target;
201
+ if (!uid) {
202
+ return value;
203
+ }
204
+ if (field.many) {
205
+ return Array.isArray(value) ? value.map((item) => wrapReference(item, uid, resolve)) : value;
206
+ }
207
+ return wrapReference(value, uid, resolve);
208
+ }
209
+ default:
210
+ return value;
211
+ }
212
+ }
213
+ export function wrapEntry(info, entry, options) {
214
+ const id = entry.id;
215
+ const entryId = entry.entryId;
216
+ const documentId = entry.documentId ?? id;
217
+ const locale = entry.locale ?? defaultLocale();
218
+ const sourcePrefix = `${info.pascal}.id:${id}:${encodeURIComponent(locale)}`;
219
+ const entryFimo = entry.__fimo && typeof entry.__fimo === 'object' ? entry.__fimo : {};
220
+ const wrapped = {
221
+ id,
222
+ entryId,
223
+ documentId,
224
+ slug: entry.slug != null ? new FimoString(entry.slug, `${sourcePrefix}.slug`) : entry.slug,
225
+ ...(options.alwaysIncludeLocale || entry.locale ? { locale } : {}),
226
+ createdAt: entry.createdAt,
227
+ updatedAt: entry.updatedAt,
228
+ __fimo: {
229
+ contentType: typeof entryFimo.contentType === 'string' ? entryFimo.contentType : info.uid,
230
+ },
231
+ };
232
+ for (const [key, field] of info.fields) {
233
+ wrapped[key] = wrapField(field, entry[key], `${sourcePrefix}.${key}`, options.resolve);
234
+ }
235
+ return wrapped;
236
+ }
@@ -0,0 +1,136 @@
1
+ import type { InfiniteData, UseInfiniteQueryResult, UseMutationResult, UseQueryResult, UseSuspenseQueryResult } from '@tanstack/react-query';
2
+ import type { Fields, Populate, PopulatedResult, ProjectedWithPopulate, Query } from '../../primitives/lib/query.js';
3
+ /**
4
+ * Result shape of a read: the entity with populated references applied and
5
+ * field projection narrowed — the runtime equivalent of the generated
6
+ * `{Uid}Result` alias.
7
+ */
8
+ export type ContentResult<T extends {
9
+ id: string;
10
+ }, TFields extends Fields<T> | undefined = undefined, TPopulate extends Populate<T> | undefined = undefined> = ProjectedWithPopulate<PopulatedResult<T, TPopulate> & {
11
+ id: string;
12
+ }, TFields, TPopulate>;
13
+ /**
14
+ * The schema definition as written in `src/schemas/<uid>.json`. The factory
15
+ * interprets it directly — the schema is the descriptor, so generated code
16
+ * never transcribes field shapes into a second format that could drift.
17
+ */
18
+ export interface ContentSchemaField {
19
+ type?: string;
20
+ required?: boolean;
21
+ /** Media fields: multiple assets. */
22
+ multiple?: boolean;
23
+ /** Reference fields: to-many. */
24
+ many?: boolean;
25
+ /** Reference fields: target schema uid (may be this schema's own uid). */
26
+ target?: string;
27
+ /** Polymorphic reference fields: allowed target schema uids. */
28
+ targets?: readonly string[];
29
+ }
30
+ export interface ContentSchema {
31
+ uid: string;
32
+ name?: string;
33
+ isSingleton?: boolean;
34
+ i18n?: {
35
+ enabled?: boolean;
36
+ };
37
+ fields?: Readonly<Record<string, ContentSchemaField>>;
38
+ }
39
+ export interface ContentModuleMeta {
40
+ readonly uid: string;
41
+ readonly pascal: string;
42
+ readonly localized: boolean;
43
+ }
44
+ export interface ContentClientHandle {
45
+ wrapWithSource: (entry: Record<string, any>) => unknown;
46
+ meta: ContentModuleMeta;
47
+ }
48
+ /**
49
+ * A cross-referenced generated module — either a client directly, or a module
50
+ * namespace whose default export is the client. Generated modules for schemas
51
+ * that reference each other form ESM cycles, so the factory only reads
52
+ * properties off a ref lazily at request time, never while the client is
53
+ * being constructed — a namespace import is safe to pass here even mid-cycle.
54
+ */
55
+ export type ContentModuleRef = ContentClientHandle | {
56
+ default: ContentClientHandle;
57
+ };
58
+ export interface ContentClientConfig {
59
+ schema: ContentSchema;
60
+ /**
61
+ * Every OTHER schema module this schema references, keyed by uid —
62
+ * `references: { Author, Video }` with namespace imports. Self-references
63
+ * resolve internally and never appear here.
64
+ */
65
+ references?: Readonly<Record<string, ContentModuleRef>>;
66
+ }
67
+ export interface ContentReadOptions<TPopulate> {
68
+ locale?: string;
69
+ populate?: TPopulate;
70
+ localeFallbacks?: boolean;
71
+ }
72
+ export interface CollectionPageInfo {
73
+ readonly limit: number;
74
+ readonly offset: number;
75
+ readonly total: number;
76
+ readonly hasMore: boolean;
77
+ readonly nextOffset: number | null;
78
+ }
79
+ export interface CollectionPage<TItem> {
80
+ readonly items: TItem[];
81
+ readonly pageInfo: CollectionPageInfo;
82
+ }
83
+ export type InfiniteListResult<TItem> = UseInfiniteQueryResult<InfiniteData<CollectionPage<TItem>>, Error> & {
84
+ readonly items: TItem[];
85
+ readonly total: number;
86
+ readonly hasMore: boolean;
87
+ readonly loadMore: () => ReturnType<UseInfiniteQueryResult<InfiniteData<CollectionPage<TItem>>, Error>['fetchNextPage']>;
88
+ readonly isLoadingMore: boolean;
89
+ };
90
+ export interface CollectionContentClient<T extends {
91
+ id: string;
92
+ }, TInput> {
93
+ readonly meta: ContentModuleMeta;
94
+ wrapWithSource(entry: Record<string, any>): T;
95
+ getById<TPopulate extends Populate<T> | undefined = undefined>(id: string, options?: ContentReadOptions<TPopulate>): Promise<ContentResult<T, undefined, TPopulate>>;
96
+ getBySlug<TPopulate extends Populate<T> | undefined = undefined>(slug: string, options?: ContentReadOptions<TPopulate>): Promise<ContentResult<T, undefined, TPopulate> | null>;
97
+ getByField<TPopulate extends Populate<T> | undefined = undefined>(fieldName: string, value: string, options?: ContentReadOptions<TPopulate>): Promise<ContentResult<T, undefined, TPopulate> | null>;
98
+ list<TFields extends Fields<T> | undefined = undefined, TPopulate extends Populate<T> | undefined = undefined>(params?: Query<T, TFields, TPopulate>): Promise<CollectionPage<ContentResult<T, TFields, TPopulate>>>;
99
+ get<TFields extends Fields<T> | undefined = undefined, TPopulate extends Populate<T> | undefined = undefined>(params?: Query<T, TFields, TPopulate>): Promise<ContentResult<T, TFields, TPopulate>[]>;
100
+ create(payload: TInput): Promise<T>;
101
+ update(id: string, payload: Partial<TInput>): Promise<T>;
102
+ remove(id: string, options?: {
103
+ locale?: string;
104
+ }): Promise<void>;
105
+ useGetById<TPopulate extends Populate<T> | undefined = undefined>(id: string, options?: ContentReadOptions<TPopulate>): UseQueryResult<ContentResult<T, undefined, TPopulate>, Error>;
106
+ useGetBySlug<TPopulate extends Populate<T> | undefined = undefined>(slug: string, options?: ContentReadOptions<TPopulate>): UseQueryResult<ContentResult<T, undefined, TPopulate> | null, Error>;
107
+ useGetByField<TPopulate extends Populate<T> | undefined = undefined>(fieldName: string, value: string, options?: ContentReadOptions<TPopulate>): UseQueryResult<ContentResult<T, undefined, TPopulate> | null, Error>;
108
+ useGet<TFields extends Fields<T> | undefined = undefined, TPopulate extends Populate<T> | undefined = undefined>(params?: Query<T, TFields, TPopulate>): UseQueryResult<ContentResult<T, TFields, TPopulate>[], Error>;
109
+ useList<TFields extends Fields<T> | undefined = undefined, TPopulate extends Populate<T> | undefined = undefined>(params?: Query<T, TFields, TPopulate>): UseQueryResult<CollectionPage<ContentResult<T, TFields, TPopulate>>, Error>;
110
+ useInfiniteList<TFields extends Fields<T> | undefined = undefined, TPopulate extends Populate<T> | undefined = undefined>(params?: Omit<Query<T, TFields, TPopulate>, 'offset'>): InfiniteListResult<ContentResult<T, TFields, TPopulate>>;
111
+ useSuspenseGetById<TPopulate extends Populate<T> | undefined = undefined>(id: string, options?: ContentReadOptions<TPopulate>): UseSuspenseQueryResult<ContentResult<T, undefined, TPopulate>, Error>;
112
+ useSuspenseGetBySlug<TPopulate extends Populate<T> | undefined = undefined>(slug: string, options?: ContentReadOptions<TPopulate>): UseSuspenseQueryResult<ContentResult<T, undefined, TPopulate> | null, Error>;
113
+ useSuspenseGetByField<TPopulate extends Populate<T> | undefined = undefined>(fieldName: string, value: string, options?: ContentReadOptions<TPopulate>): UseSuspenseQueryResult<ContentResult<T, undefined, TPopulate> | null, Error>;
114
+ useSuspenseGet<TFields extends Fields<T> | undefined = undefined, TPopulate extends Populate<T> | undefined = undefined>(params?: Query<T, TFields, TPopulate>): UseSuspenseQueryResult<ContentResult<T, TFields, TPopulate>[], Error>;
115
+ useCreate(): UseMutationResult<T, Error, TInput>;
116
+ useUpdate(): UseMutationResult<T, Error, {
117
+ id: string;
118
+ locale?: string;
119
+ } & Partial<TInput>>;
120
+ useRemove(): UseMutationResult<void, Error, string | {
121
+ id: string;
122
+ locale?: string;
123
+ }>;
124
+ }
125
+ export interface SingletonContentClient<T extends {
126
+ id: string;
127
+ }, TInput> {
128
+ readonly meta: ContentModuleMeta;
129
+ wrapWithSource(entry: Record<string, any>): T;
130
+ get<TPopulate extends Populate<T> | undefined = undefined>(options?: ContentReadOptions<TPopulate>): Promise<ContentResult<T, undefined, TPopulate>>;
131
+ update(payload: Partial<TInput>): Promise<T>;
132
+ useGet<TPopulate extends Populate<T> | undefined = undefined>(options?: ContentReadOptions<TPopulate>): UseQueryResult<ContentResult<T, undefined, TPopulate>, Error>;
133
+ useSuspenseGet<TPopulate extends Populate<T> | undefined = undefined>(options?: ContentReadOptions<TPopulate>): UseSuspenseQueryResult<ContentResult<T, undefined, TPopulate>, Error>;
134
+ useUpdate(): UseMutationResult<T, Error, Partial<TInput>>;
135
+ }
136
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export declare function useQueryLocale(locale?: string): string;
2
+ //# sourceMappingURL=useQueryLocale.d.ts.map
@@ -0,0 +1,6 @@
1
+ import { useLocale } from '../../i18n/FimoLocaleContext.js';
2
+ import { defaultLocale } from './internal.js';
3
+ export function useQueryLocale(locale) {
4
+ const activeLocale = useLocale().locale;
5
+ return locale ?? activeLocale ?? defaultLocale();
6
+ }
@@ -1,6 +1,7 @@
1
1
  type FieldData = Record<string, string>;
2
2
  interface RecordData {
3
3
  id: string;
4
+ locale?: string;
4
5
  data: FieldData;
5
6
  visible: boolean;
6
7
  }
@@ -1,3 +1,19 @@
1
+ function parseRecordIdentity(token) {
2
+ if (!token.startsWith('id:')) {
3
+ return token.includes(':') ? null : { id: token };
4
+ }
5
+ const [format, id, encodedLocale, ...rest] = token.split(':');
6
+ if (format !== 'id' || !id || !encodedLocale || rest.length > 0) {
7
+ return null;
8
+ }
9
+ try {
10
+ const locale = decodeURIComponent(encodedLocale);
11
+ return locale ? { id, locale } : null;
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
1
17
  /**
2
18
  * Checks if an element is genuinely visible to the user within the viewport.
3
19
  * @param el The element to check.
@@ -38,7 +54,13 @@ export function retrieveVisibleDataSources() {
38
54
  const parts = source.split('.');
39
55
  // Expects the format "SchemaName.id.fieldName"
40
56
  if (parts.length === 3) {
41
- const [schemaName, id, fieldName] = parts;
57
+ const [schemaName, identityToken, fieldName] = parts;
58
+ const identity = parseRecordIdentity(identityToken);
59
+ if (!identity) {
60
+ return;
61
+ }
62
+ const { id, locale } = identity;
63
+ const recordKey = JSON.stringify([id, locale ?? null]);
42
64
  // Skip __translations
43
65
  if (schemaName === '__translations') {
44
66
  return;
@@ -48,16 +70,17 @@ export function retrieveVisibleDataSources() {
48
70
  tempGroupedData[schemaName] = {};
49
71
  }
50
72
  // Ensure the record (by ID) exists for this schema
51
- if (!tempGroupedData[schemaName][id]) {
52
- tempGroupedData[schemaName][id] = {
73
+ if (!tempGroupedData[schemaName][recordKey]) {
74
+ tempGroupedData[schemaName][recordKey] = {
53
75
  id: id,
76
+ ...(locale ? { locale } : {}),
54
77
  data: {},
55
78
  visible: isElementVisible(el),
56
79
  };
57
80
  }
58
81
  // Add the field and its text content to the record
59
82
  if (el.textContent) {
60
- tempGroupedData[schemaName][id].data[fieldName] = el.textContent.trim();
83
+ tempGroupedData[schemaName][recordKey].data[fieldName] = el.textContent.trim();
61
84
  }
62
85
  }
63
86
  });
@@ -6,6 +6,9 @@
6
6
  // stays `undefined`. The inline overlay runtime (overlay-runtime.ts) exposes
7
7
  // its helpers on `window.__fimoOverlay`, which we call here.
8
8
  function api() {
9
+ if (typeof window === 'undefined') {
10
+ return undefined;
11
+ }
9
12
  return window.__fimoOverlay;
10
13
  }
11
14
  if (import.meta && import.meta.hot) {
@@ -8,6 +8,9 @@ export { DataSourcesProvider } from './content/DataSourcesProvider.js';
8
8
  export { RefreshContentProvider } from './content/RefreshContentProvider.js';
9
9
  export { parseCollectionResponse } from './content/parseCollectionResponse.js';
10
10
  export type { CollectionResponse } from './content/parseCollectionResponse.js';
11
+ export { createContentClient } from './content/client/createContentClient.js';
12
+ export { createSingletonClient } from './content/client/createSingletonClient.js';
13
+ export type { CollectionContentClient, CollectionPage, CollectionPageInfo, ContentClientConfig, ContentClientHandle, ContentModuleMeta, ContentModuleRef, ContentReadOptions, ContentResult, ContentSchema, ContentSchemaField, InfiniteListResult, SingletonContentClient, } from './content/client/types.js';
11
14
  export { AppErrorBoundary } from './core/AppErrorBoundary.js';
12
15
  export { ViteLifecycles } from './core/ViteLifecycles.js';
13
16
  export { Pageview } from './t/Pageview.js';
@@ -6,6 +6,8 @@ export { localizeFimoPath, resolveFimoLocale } from './i18n/locale.js';
6
6
  export { DataSourcesProvider } from './content/DataSourcesProvider.js';
7
7
  export { RefreshContentProvider } from './content/RefreshContentProvider.js';
8
8
  export { parseCollectionResponse } from './content/parseCollectionResponse.js';
9
+ export { createContentClient } from './content/client/createContentClient.js';
10
+ export { createSingletonClient } from './content/client/createSingletonClient.js';
9
11
  // Core
10
12
  export { AppErrorBoundary } from './core/AppErrorBoundary.js';
11
13
  export { ViteLifecycles } from './core/ViteLifecycles.js';
@@ -9,12 +9,12 @@ export interface FimoSource {
9
9
  source: string;
10
10
  }
11
11
  export interface FimoReference {
12
- documentId: string;
12
+ id: string;
13
13
  }
14
14
  /** Polymorphic reference value: the schema names a target set, so every value carries its own type. */
15
15
  export interface FimoPolymorphicReference {
16
16
  contentType: string;
17
- documentId: string;
17
+ id: string;
18
18
  }
19
19
  export declare const FIMO_SOURCE: unique symbol;
20
20
  export declare const FIMO_PARTS: unique symbol;
@@ -43,6 +43,7 @@ export type ManyReferenceOps = {
43
43
  export type FilterFor<T> = [T] extends [FimoString | string] ? string | StringOps : [T] extends [FimoBoolean | boolean] ? boolean | BoolOps : [T] extends [FimoDate | Date] ? string | DateOps : [T] extends [number] ? number | NumberOps : [T] extends [FimoPolymorphicReference | null] ? never : [T] extends [FimoPolymorphicReference[]] ? never : [T] extends [FimoReference | null] ? string | null | ReferenceOps : [T] extends [FimoReference[]] ? string | ManyReferenceOps : [T] extends [FimoMedia | FimoMedia[]] ? NullOnlyOps : [T] extends [FimoRichText] ? string | StringOps : NullOnlyOps;
44
44
  type BaseTopLevelWhere = {
45
45
  id?: string | StringOps;
46
+ entryId?: string | StringOps;
46
47
  documentId?: string | StringOps;
47
48
  slug?: string | StringOps;
48
49
  createdAt?: string | DateOps;
@@ -66,9 +67,10 @@ export type Fields<T> = Field<T> | readonly Field<T>[];
66
67
  type SelectedField<TField> = TField extends `__fimo.${string}` ? '__fimo' : TField;
67
68
  type SelectedFields<TFields> = TFields extends readonly (infer K)[] ? SelectedField<K> : SelectedField<TFields>;
68
69
  type ListItem<TValue> = TValue extends readonly (infer K)[] ? K : TValue;
70
+ type EntryIdentityField<T> = Extract<'id' | 'entryId' | 'documentId' | 'locale', keyof T>;
69
71
  export type Projected<T extends {
70
72
  id: string;
71
- }, TFields extends Fields<T> | undefined> = [TFields] extends [undefined] ? T : Pick<T, Extract<'id' | SelectedFields<TFields>, keyof T>>;
73
+ }, TFields extends Fields<T> | undefined> = [TFields] extends [undefined] ? T : Pick<T, EntryIdentityField<T> | Extract<SelectedFields<TFields>, keyof T>>;
72
74
  export declare const FIMO_REFERENCES: unique symbol;
73
75
  export interface WithReferences<TReferences> {
74
76
  readonly [FIMO_REFERENCES]?: TReferences;
@@ -79,7 +81,7 @@ export type Populate<TEntry> = ReferenceField<TEntry> | readonly ReferenceField<
79
81
  type PopulatedKeys<TEntry, TPopulate> = Extract<ListItem<TPopulate>, keyof ReferenceMap<TEntry> & keyof TEntry>;
80
82
  export type Populated<TEntry extends {
81
83
  id: string;
82
- }, TPopulate extends Populate<TEntry>> = Pick<TEntry, 'id'> & Omit<TEntry, PopulatedKeys<TEntry, TPopulate>> & {
84
+ }, TPopulate extends Populate<TEntry>> = Pick<TEntry, EntryIdentityField<TEntry>> & Omit<TEntry, PopulatedKeys<TEntry, TPopulate>> & {
83
85
  [K in PopulatedKeys<TEntry, TPopulate>]: ReferenceMap<TEntry>[K];
84
86
  };
85
87
  export type PopulatedResult<TEntry extends {
@@ -89,7 +91,7 @@ export type PopulatedResult<TEntry extends {
89
91
  ] extends [undefined] ? TEntry : Populated<TEntry, NonNullable<TPopulate>>;
90
92
  export type ProjectedWithPopulate<T extends {
91
93
  id: string;
92
- }, TFields extends string | readonly string[] | undefined, TPopulate extends string | readonly string[] | undefined> = [TFields] extends [undefined] ? T : Pick<T, Extract<'id' | SelectedFields<TFields> | ListItem<NonNullable<TPopulate>>, keyof T>>;
94
+ }, TFields extends string | readonly string[] | undefined, TPopulate extends string | readonly string[] | undefined> = [TFields] extends [undefined] ? T : Pick<T, EntryIdentityField<T> | Extract<SelectedFields<TFields> | ListItem<NonNullable<TPopulate>>, keyof T>>;
93
95
  export type Query<T extends {
94
96
  id: string;
95
97
  }, TFields extends Fields<T> | undefined = Fields<T> | undefined, TPopulate extends string | readonly string[] | undefined = string | readonly string[] | undefined> = {
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "fimo",
3
- "version": "0.6.0",
3
+ "version": "0.7.0-experimental.2",
4
4
  "description": "Fimo CLI - create, deploy, and manage Fimo projects",
5
5
  "bin": {
6
6
  "fimo": "dist/cli/index.js"
7
7
  },
8
8
  "files": [
9
9
  "dist/",
10
+ "!dist/**/*.d.ts.map",
10
11
  "assets/",
11
- "scripts/",
12
+ "scripts/postinstall.mjs",
13
+ "scripts/lib/postinstall-onboarding.mjs",
12
14
  "templates/",
13
15
  "README.md",
14
16
  "release.json"
@@ -47,7 +49,7 @@
47
49
  "scripts": {
48
50
  "build": "pnpm run clean && tsc -b tsconfig.json && chmod +x dist/cli/index.js",
49
51
  "build:bundle": "node scripts/bundle-cli.mjs",
50
- "check:types": "tsc -b tsconfig.json",
52
+ "check:types": "tsc -b tsconfig.json && tsc -p tsconfig.test.json",
51
53
  "start": "tsx src/cli/index.ts",
52
54
  "install-cli": "pnpm build && node scripts/install-cli.mjs",
53
55
  "publish:npm": "node scripts/publish-npm.mjs",
@@ -60,23 +62,16 @@
60
62
  "clean": "rm -rf dist *.tsbuildinfo"
61
63
  },
62
64
  "dependencies": {
63
- "@clack/core": "^1.2.0",
64
- "@clack/prompts": "^1.2.0",
65
65
  "@rollup/pluginutils": "^5.1.0",
66
- "agent-install": "^0.0.5",
67
- "better-auth": "1.4.18",
68
66
  "change-case": "^5.4.4",
69
- "cheerio": "^1.0.0",
70
- "commander": "^13.1.0",
71
67
  "eta": "^3.5.0",
72
- "lucide-react": "^0.577.0",
73
- "open": "^10.1.2",
74
68
  "oxc-parser": "^0.118.0",
75
69
  "react-error-boundary": "^6.0.0",
76
- "yaml": "2.8.2",
77
70
  "zod": "4.1.8"
78
71
  },
79
72
  "devDependencies": {
73
+ "@clack/core": "^1.2.0",
74
+ "@clack/prompts": "^1.2.0",
80
75
  "@fimo/agents-bundle": "workspace:*",
81
76
  "@fimo/api-contracts": "workspace:*",
82
77
  "@fimo/cli-kit": "workspace:*",
@@ -85,7 +80,12 @@
85
80
  "@types/node": "^22.15.29",
86
81
  "@types/react": "^19.2.14",
87
82
  "@types/react-dom": "^19.2.3",
83
+ "agent-install": "^0.0.5",
84
+ "better-auth": "1.4.18",
85
+ "cheerio": "^1.0.0",
86
+ "commander": "^13.1.0",
88
87
  "esbuild": "^0.25.10",
88
+ "open": "^10.1.2",
89
89
  "react": "^19.2.4",
90
90
  "react-dom": "^19.2.4",
91
91
  "react-router": "^7.15.1",
@@ -93,7 +93,8 @@
93
93
  "typescript": "5.9.2",
94
94
  "vite": "^8.0.13",
95
95
  "vitest": "^4.1.6",
96
- "wrangler": "^4.83.0"
96
+ "wrangler": "^4.83.0",
97
+ "yaml": "2.8.2"
97
98
  },
98
99
  "peerDependencies": {
99
100
  "@tanstack/react-query": "^5.80.6",
@@ -102,6 +103,23 @@
102
103
  "react-router": "^7.0.0 || ^8.0.0",
103
104
  "vite": "^7.0.0 || ^8.0.0"
104
105
  },
106
+ "peerDependenciesMeta": {
107
+ "@tanstack/react-query": {
108
+ "optional": true
109
+ },
110
+ "react": {
111
+ "optional": true
112
+ },
113
+ "react-dom": {
114
+ "optional": true
115
+ },
116
+ "react-router": {
117
+ "optional": true
118
+ },
119
+ "vite": {
120
+ "optional": true
121
+ }
122
+ },
105
123
  "engines": {
106
124
  "node": ">=20.12.0"
107
125
  }
package/release.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "source": "npm",
3
- "apiUrl": "https://api.fimo.ai",
4
- "webUrl": "https://fimo.ai"
3
+ "apiUrl": "https://api.staging.fimo.team",
4
+ "webUrl": "https://staging.fimo.team"
5
5
  }
@@ -21,7 +21,7 @@
21
21
  "cmdk": "^1.1.1",
22
22
  "date-fns": "^4.1.0",
23
23
  "embla-carousel-react": "^8.6.0",
24
- "fimo": "0.6.0",
24
+ "fimo": "0.7.0-experimental.2",
25
25
  "input-otp": "^1.4.2",
26
26
  "isbot": "^5",
27
27
  "lucide-react": "^0.577.0",
@@ -29,7 +29,7 @@ const Index = () => {
29
29
  <a href="https://fimo.ai" target="_blank" rel="noreferrer">
30
30
  Fimo.ai
31
31
  </a>
32
- <a href="https://docs.fimo.ai" target="_blank" rel="noreferrer">
32
+ <a href="https://fimo.ai/docs" target="_blank" rel="noreferrer">
33
33
  Documentation
34
34
  </a>
35
35
  <a href="https://blog.fimo.ai" target="_blank" rel="noreferrer">