fimo 0.4.0-experimental.2 → 0.4.0-experimental.3

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.
@@ -1,4 +1,4 @@
1
- import type { FimoBoolean, FimoDate, FimoMedia, FimoRichText, FimoString } from './primitives.js';
1
+ import type { FimoBoolean, FimoDate, FimoMedia, FimoReference, FimoRichText, FimoString } from './primitives.js';
2
2
  export type ScalarOps<T> = {
3
3
  $eq?: T;
4
4
  $ne?: T;
@@ -25,20 +25,36 @@ export type BoolOps = ScalarOps<boolean>;
25
25
  export type NullOnlyOps = {
26
26
  $null?: boolean;
27
27
  };
28
- 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 [FimoMedia | FimoMedia[]] ? NullOnlyOps : [T] extends [FimoRichText] ? string | StringOps : NullOnlyOps;
29
- type TopLevelWhere = {
28
+ export type RelationOps = {
29
+ $eq?: string | null;
30
+ $ne?: string | null;
31
+ $in?: string[];
32
+ $nin?: string[];
33
+ $contains?: string;
34
+ $null?: boolean;
35
+ };
36
+ export type ManyRelationOps = {
37
+ $eq?: string;
38
+ $ne?: string;
39
+ $contains?: string;
40
+ $null?: boolean;
41
+ };
42
+ 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 [FimoReference | null] ? string | null | RelationOps : [T] extends [FimoReference[]] ? string | ManyRelationOps : [T] extends [FimoMedia | FimoMedia[]] ? NullOnlyOps : [T] extends [FimoRichText] ? string | StringOps : NullOnlyOps;
43
+ type BaseTopLevelWhere = {
30
44
  id?: string | StringOps;
31
45
  documentId?: string | StringOps;
32
46
  slug?: string | StringOps;
33
- locale?: string | StringOps;
34
47
  createdAt?: string | DateOps;
35
48
  updatedAt?: string | DateOps;
36
49
  __fimo?: {
37
50
  contentType?: string | StringOps;
38
51
  };
39
52
  };
40
- export type Where<T> = TopLevelWhere & {
41
- [K in Exclude<keyof T, keyof TopLevelWhere>]?: FilterFor<T[K]>;
53
+ type TopLevelWhere<T> = BaseTopLevelWhere & ('locale' extends keyof T ? {
54
+ locale?: string | StringOps;
55
+ } : {});
56
+ export type Where<T> = TopLevelWhere<T> & {
57
+ [K in Exclude<keyof T, keyof BaseTopLevelWhere | 'locale'>]?: FilterFor<T[K]>;
42
58
  };
43
59
  type FimoMetadataField = 'contentType';
44
60
  type FimoField = '__fimo' | `__fimo.${FimoMetadataField}`;
@@ -48,12 +64,34 @@ export type Field<T> = Extract<keyof T, string> | 'createdAt' | 'updatedAt' | Fi
48
64
  export type Fields<T> = Field<T> | readonly Field<T>[];
49
65
  type SelectedField<TField> = TField extends `__fimo.${string}` ? '__fimo' : TField;
50
66
  type SelectedFields<TFields> = TFields extends readonly (infer K)[] ? SelectedField<K> : SelectedField<TFields>;
67
+ type ListItem<TValue> = TValue extends readonly (infer K)[] ? K : TValue;
51
68
  export type Projected<T extends {
52
69
  id: string;
53
70
  }, TFields extends Fields<T> | undefined> = [TFields] extends [undefined] ? T : Pick<T, Extract<'id' | SelectedFields<TFields>, keyof T>>;
71
+ export declare const FIMO_RELATIONS: unique symbol;
72
+ export interface WithRelations<TRelations> {
73
+ readonly [FIMO_RELATIONS]?: TRelations;
74
+ }
75
+ export type RelationMap<TEntry> = TEntry extends WithRelations<infer TRelations> ? TRelations : never;
76
+ export type RelationField<TEntry> = Extract<keyof RelationMap<TEntry>, string>;
77
+ export type Populate<TEntry> = RelationField<TEntry> | readonly RelationField<TEntry>[];
78
+ type PopulatedKeys<TEntry, TPopulate> = Extract<ListItem<TPopulate>, keyof RelationMap<TEntry> & keyof TEntry>;
79
+ export type Populated<TEntry extends {
80
+ id: string;
81
+ }, TPopulate extends Populate<TEntry>> = Pick<TEntry, 'id'> & Omit<TEntry, PopulatedKeys<TEntry, TPopulate>> & {
82
+ [K in PopulatedKeys<TEntry, TPopulate>]: RelationMap<TEntry>[K];
83
+ };
84
+ export type PopulatedResult<TEntry extends {
85
+ id: string;
86
+ }, TPopulate extends Populate<TEntry> | undefined> = [
87
+ TPopulate
88
+ ] extends [undefined] ? TEntry : Populated<TEntry, NonNullable<TPopulate>>;
89
+ export type ProjectedWithPopulate<T extends {
90
+ id: string;
91
+ }, 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>>;
54
92
  export type Query<T extends {
55
93
  id: string;
56
- }, TFields extends Fields<T> | undefined = Fields<T> | undefined> = {
94
+ }, TFields extends Fields<T> | undefined = Fields<T> | undefined, TPopulate extends string | readonly string[] | undefined = string | readonly string[] | undefined> = {
57
95
  where?: Where<T>;
58
96
  locale?: string;
59
97
  search?: string;
@@ -61,7 +99,7 @@ export type Query<T extends {
61
99
  limit?: number;
62
100
  offset?: number;
63
101
  fields?: TFields;
64
- populate?: Fields<T>;
102
+ populate?: TPopulate;
65
103
  };
66
104
  export {};
67
105
  //# sourceMappingURL=query.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../../src/runtime/primitives/lib/query.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../../src/runtime/primitives/lib/query.ts"],"names":[],"mappings":"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"}
@@ -6,11 +6,37 @@ describe('fimo/ui public type surface', () => {
6
6
  const query = {
7
7
  where: { name: 'BASIC' },
8
8
  sort: '-price',
9
- populate: 'name',
10
9
  limit: 10,
11
10
  };
12
11
  expectTypeOf(query.fields).toEqualTypeOf();
12
+ expectTypeOf().toEqualTypeOf();
13
+ const localizedWhere = { locale: 'en' };
14
+ expectTypeOf(localizedWhere).toEqualTypeOf();
15
+ // @ts-expect-error locale is not a filter on non-localized generated entry types.
16
+ const sharedWhere = { locale: 'en' };
17
+ expectTypeOf(sharedWhere).toEqualTypeOf();
18
+ });
19
+ it('supports the relation populate pattern emitted into generated schemas', () => {
20
+ const query = {
21
+ where: {
22
+ sponsor: { $eq: '750e8400-e29b-41d4-a716-446655440000' },
23
+ relatedPackages: { $contains: '850e8400-e29b-41d4-a716-446655440000' },
24
+ },
25
+ fields: ['name'],
26
+ populate: ['sponsor'],
27
+ };
28
+ expectTypeOf(query.fields).toEqualTypeOf();
13
29
  expectTypeOf(query.populate).toEqualTypeOf();
14
30
  expectTypeOf().toEqualTypeOf();
31
+ expectTypeOf().toEqualTypeOf();
32
+ expectTypeOf().toEqualTypeOf();
33
+ expectTypeOf().toEqualTypeOf();
34
+ expectTypeOf().toEqualTypeOf();
35
+ // @ts-expect-error generated relation-aware queries only allow relation fields in populate.
36
+ const badQuery = { populate: 'name' };
37
+ expectTypeOf(badQuery).toEqualTypeOf();
38
+ // @ts-expect-error relation filters use target documentId strings, not raw reference objects.
39
+ const badWhere = { where: { sponsor: { documentId: '750e8400-e29b-41d4-a716-446655440000' } } };
40
+ expectTypeOf(badWhere).toEqualTypeOf();
15
41
  });
16
42
  });
@@ -5,8 +5,8 @@ import { dirname, join, resolve } from 'node:path';
5
5
  * hook files generated from the hooks template.
6
6
  *
7
7
  * - Scans `<cwd>/src/schemas/*.json`.
8
- * - Renders `assets/content-templates/hooks-template.eta`.
9
- * - Creates/updates `<cwd>/src/schemas/<Pascal>.ts`; deletes orphans.
8
+ * - Renders `assets/content-templates/types-template.eta` and `hooks-template.eta`.
9
+ * - Creates/updates `<cwd>/src/schemas/<Pascal>.types.ts` and `<Pascal>.ts`; deletes orphans.
10
10
  */
11
11
  import { pascalCase } from 'change-case';
12
12
  import { Eta } from 'eta';
@@ -28,6 +28,50 @@ function readSchema(path) {
28
28
  return null;
29
29
  }
30
30
  }
31
+ function normalizeFields(fields) {
32
+ return Object.fromEntries(Object.entries(fields).map(([key, value]) => {
33
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
34
+ return [key, { type: undefined }];
35
+ }
36
+ const field = value;
37
+ const target = typeof field.target === 'string' ? field.target : undefined;
38
+ return [
39
+ key,
40
+ {
41
+ ...field,
42
+ type: typeof field.type === 'string' ? field.type : undefined,
43
+ target,
44
+ targetPascal: target ? pascalCase(target) : undefined,
45
+ many: typeof field.many === 'boolean' ? field.many : undefined,
46
+ multiple: typeof field.multiple === 'boolean' ? field.multiple : undefined,
47
+ },
48
+ ];
49
+ }));
50
+ }
51
+ function relationImportsFor(schema, fields) {
52
+ const imports = new Map();
53
+ for (const field of Object.values(fields)) {
54
+ if (field.type !== 'relation' || !field.target || field.target === schema.uid) {
55
+ continue;
56
+ }
57
+ imports.set(field.target, { uid: field.target, pascal: pascalCase(field.target) });
58
+ }
59
+ return [...imports.values()].sort((a, b) => a.pascal.localeCompare(b.pascal));
60
+ }
61
+ function schemaToModel(schema, localizedByUid) {
62
+ const fields = normalizeFields(schema.fields ?? {});
63
+ return {
64
+ uid: schema.uid,
65
+ pascal: pascalCase(schema.uid),
66
+ isSingleton: schema.isSingleton ?? false,
67
+ localized: schema.i18n?.enabled !== false,
68
+ localizedRelationFields: Object.entries(fields)
69
+ .filter(([, field]) => field.type === 'relation' && field.target && localizedByUid.get(field.target) !== false)
70
+ .map(([fieldName]) => fieldName),
71
+ fields,
72
+ relationImports: relationImportsFor(schema, fields),
73
+ };
74
+ }
31
75
  export function syncSchemas(options = {}) {
32
76
  const cwd = options.cwd ?? process.cwd();
33
77
  const log = (msg) => {
@@ -36,10 +80,26 @@ export function syncSchemas(options = {}) {
36
80
  }
37
81
  };
38
82
  const schemasDir = resolve(cwd, SCHEMAS_REL);
39
- const templatePath = assetPath('content-templates', 'hooks-template.eta');
40
- const templateSource = readFileSync(templatePath, 'utf-8');
83
+ const templates = [
84
+ {
85
+ suffix: '.types.ts',
86
+ path: assetPath('content-templates', 'types-template.eta'),
87
+ },
88
+ {
89
+ suffix: '.ts',
90
+ path: assetPath('content-templates', 'hooks-template.eta'),
91
+ },
92
+ ].map((template) => ({
93
+ ...template,
94
+ source: readFileSync(template.path, 'utf-8'),
95
+ }));
41
96
  const eta = new Eta({ views: undefined, useWith: true, varName: 'it', autoEscape: false });
42
97
  const schemaFiles = getSchemaFiles(schemasDir);
98
+ const schemasByPath = new Map(schemaFiles.flatMap((schemaPath) => {
99
+ const schema = readSchema(schemaPath);
100
+ return schema ? [[schemaPath, schema]] : [];
101
+ }));
102
+ const localizedByUid = new Map([...schemasByPath.values()].map((schema) => [schema.uid, schema.i18n?.enabled !== false]));
43
103
  const expectedTsFiles = new Set();
44
104
  let created = 0;
45
105
  let updated = 0;
@@ -49,9 +109,8 @@ export function syncSchemas(options = {}) {
49
109
  .split('/')
50
110
  .pop()
51
111
  .replace(/\.json$/, '');
52
- const tsPath = join(schemasDir, `${pascalCase(uid)}.ts`);
53
- expectedTsFiles.add(tsPath);
54
- const schema = readSchema(jsonPath);
112
+ const pascal = pascalCase(uid);
113
+ const schema = schemasByPath.get(jsonPath);
55
114
  if (!schema) {
56
115
  console.error(`Failed to parse schema: ${jsonPath}`);
57
116
  continue;
@@ -59,29 +118,34 @@ export function syncSchemas(options = {}) {
59
118
  if (!schema.uid) {
60
119
  schema.uid = uid;
61
120
  }
62
- try {
63
- const content = eta.renderString(templateSource, {
64
- model: { uid: schema.uid, pascal: pascalCase(schema.uid), fields: schema.fields ?? {} },
65
- });
66
- const dir = dirname(tsPath);
67
- if (!existsSync(dir)) {
68
- mkdirSync(dir, { recursive: true });
69
- }
70
- if (!existsSync(tsPath)) {
71
- writeFileSync(tsPath, content, 'utf-8');
72
- created++;
73
- log(`Created: ${tsPath}`);
74
- continue;
121
+ const model = schemaToModel(schema, localizedByUid);
122
+ for (const template of templates) {
123
+ const tsPath = join(schemasDir, `${pascal}${template.suffix}`);
124
+ expectedTsFiles.add(tsPath);
125
+ try {
126
+ const content = eta.renderString(template.source, {
127
+ model,
128
+ });
129
+ const dir = dirname(tsPath);
130
+ if (!existsSync(dir)) {
131
+ mkdirSync(dir, { recursive: true });
132
+ }
133
+ if (!existsSync(tsPath)) {
134
+ writeFileSync(tsPath, content, 'utf-8');
135
+ created++;
136
+ log(`Created: ${tsPath}`);
137
+ continue;
138
+ }
139
+ if (readFileSync(tsPath, 'utf-8') !== content) {
140
+ writeFileSync(tsPath, content, 'utf-8');
141
+ updated++;
142
+ log(`Updated: ${tsPath}`);
143
+ }
75
144
  }
76
- if (readFileSync(tsPath, 'utf-8') !== content) {
77
- writeFileSync(tsPath, content, 'utf-8');
78
- updated++;
79
- log(`Updated: ${tsPath}`);
145
+ catch (error) {
146
+ console.error(`Failed to generate ${tsPath}:`, error);
80
147
  }
81
148
  }
82
- catch (error) {
83
- console.error(`Failed to generate ${tsPath}:`, error);
84
- }
85
149
  }
86
150
  if (existsSync(schemasDir)) {
87
151
  const allTsFiles = readdirSync(schemasDir)
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,121 @@
1
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
+ import { syncSchemas } from './sync-schemas.js';
6
+ let scratch;
7
+ beforeEach(() => {
8
+ scratch = mkdtempSync(join(tmpdir(), 'fimo-sync-schemas-'));
9
+ mkdirSync(join(scratch, 'src/schemas'), { recursive: true });
10
+ });
11
+ afterEach(() => {
12
+ rmSync(scratch, { recursive: true, force: true });
13
+ });
14
+ function writeSchema(name, schema) {
15
+ writeFileSync(join(scratch, 'src/schemas', `${name}.json`), JSON.stringify(schema, null, 2));
16
+ }
17
+ describe('syncSchemas', () => {
18
+ it('generates raw relation references plus typed populated relation helpers', () => {
19
+ writeSchema('Author', {
20
+ uid: 'Author',
21
+ name: 'Authors',
22
+ fields: {
23
+ name: { type: 'string' },
24
+ },
25
+ });
26
+ writeSchema('Post', {
27
+ uid: 'Post',
28
+ name: 'Posts',
29
+ fields: {
30
+ title: { type: 'string' },
31
+ author: { type: 'relation', target: 'Author' },
32
+ contributors: { type: 'relation', target: 'Author', many: true },
33
+ relatedPosts: { type: 'relation', target: 'Post', many: true },
34
+ },
35
+ });
36
+ expect(syncSchemas({ cwd: scratch, verbose: false })).toEqual({ created: 4, updated: 0, deleted: 0 });
37
+ const post = readFileSync(join(scratch, 'src/schemas/Post.ts'), 'utf-8');
38
+ const postTypes = readFileSync(join(scratch, 'src/schemas/Post.types.ts'), 'utf-8');
39
+ expect(post).toContain('import { wrapWithSource as wrapAuthorWithSource } from "./Author";');
40
+ expect(post).toContain('import type { Post, PostInput, PostPopulate, PostResult, PostFields, PostQuery } from "./Post.types";');
41
+ expect(post).toContain('from "./Post.types";');
42
+ expect(post).not.toContain('import type { Author } from "./Author";');
43
+ expect(post).not.toContain('export interface Post extends WithRelations<PostRelations>');
44
+ expect(post).not.toContain('type FimoReference');
45
+ expect(post).toContain('Promise<PostResult<undefined, TPopulate>>');
46
+ expect(post).toContain('TPopulate extends PostPopulate | undefined');
47
+ expect(post).toContain('wrapAuthorWithSource(value)');
48
+ expect(post).toContain('wrapWithSource(value)');
49
+ expect(post).toContain('export async function create(payload: PostInput): Promise<Post>');
50
+ expect(post).toContain('return wrapWithSource(data.data);');
51
+ expect(postTypes).toContain('import type { Author } from "./Author.types";');
52
+ expect(postTypes).toContain('export interface Post extends WithRelations<PostRelations>');
53
+ expect(postTypes).toContain('type FimoReference');
54
+ expect(postTypes).toContain('author: FimoReference | null;');
55
+ expect(postTypes).toContain('contributors: FimoReference[];');
56
+ expect(postTypes).toContain('relatedPosts: FimoReference[];');
57
+ expect(postTypes).toContain('export interface PostRelations');
58
+ expect(postTypes).toContain('export interface PostInput');
59
+ expect(postTypes).toContain('author?: FimoReference | null;');
60
+ expect(postTypes).toContain('author: Author | null;');
61
+ expect(postTypes).toContain('contributors: Author[];');
62
+ expect(postTypes).toContain('relatedPosts: Post[];');
63
+ expect(postTypes).toContain('export type PostResult<');
64
+ expect(postTypes).not.toContain('PostPopulated');
65
+ });
66
+ it('generates locale-free shared types and locale-sensitive populated target keys', () => {
67
+ writeSchema('Homepage', {
68
+ uid: 'Homepage',
69
+ name: 'Homepages',
70
+ fields: { title: { type: 'string' } },
71
+ });
72
+ writeSchema('Category', {
73
+ uid: 'Category',
74
+ name: 'Categories',
75
+ i18n: { enabled: false },
76
+ fields: { name: { type: 'string' } },
77
+ });
78
+ writeSchema('Settings', {
79
+ uid: 'Settings',
80
+ name: 'Settings',
81
+ i18n: { enabled: false },
82
+ fields: {
83
+ homepage: { type: 'relation', target: 'Homepage' },
84
+ category: { type: 'relation', target: 'Category' },
85
+ },
86
+ });
87
+ syncSchemas({ cwd: scratch, verbose: false });
88
+ const settings = readFileSync(join(scratch, 'src/schemas/Settings.ts'), 'utf-8');
89
+ const settingsTypes = readFileSync(join(scratch, 'src/schemas/Settings.types.ts'), 'utf-8');
90
+ const homepageTypes = readFileSync(join(scratch, 'src/schemas/Homepage.types.ts'), 'utf-8');
91
+ expect(settingsTypes).not.toContain('locale: string;');
92
+ expect(settingsTypes).not.toContain('locale?: string;');
93
+ expect(homepageTypes).toContain('locale: string;');
94
+ expect(settings).toContain('const LOCALIZED_CONTENT = false;');
95
+ expect(settings).toContain('const LOCALIZED_RELATION_FIELDS = new Set<string>(["homepage"]);');
96
+ expect(settings).not.toContain(' locale: entry.locale,');
97
+ expect(settings).toContain('readLocale(params, LOCALIZED_CONTENT, useQueryLocale(params.locale))');
98
+ expect(settings.match(/readLocale\(params, LOCALIZED_CONTENT, useQueryLocale\(params\.locale\)\)/g)?.length).toBe(2);
99
+ expect(settings.match(/readLocale\(options, false, useQueryLocale\(options\.locale\)\)/g)?.length).toBe(2);
100
+ });
101
+ it('generates singleton document APIs without collection mutations', () => {
102
+ writeSchema('Settings', {
103
+ uid: 'Settings',
104
+ name: 'Settings',
105
+ isSingleton: true,
106
+ i18n: { enabled: false },
107
+ fields: { siteName: { type: 'string', required: true } },
108
+ });
109
+ syncSchemas({ cwd: scratch, verbose: false });
110
+ const settings = readFileSync(join(scratch, 'src/schemas/Settings.ts'), 'utf-8');
111
+ const settingsTypes = readFileSync(join(scratch, 'src/schemas/Settings.types.ts'), 'utf-8');
112
+ expect(settings).toContain('fetch(`${base}${withReadOptions(`/documents/Settings`, options)}`');
113
+ expect(settings).toContain('export async function update(payload: Partial<SettingsInput>)');
114
+ expect(settings).toContain('export function useSuspenseGet');
115
+ expect(settings).not.toContain('export async function create(');
116
+ expect(settings).not.toContain('export async function remove(');
117
+ expect(settings).not.toContain('export function useCreate(');
118
+ expect(settingsTypes).toContain('siteName: string;');
119
+ expect(settingsTypes).not.toContain('documentId?: string;');
120
+ });
121
+ });
@@ -63,7 +63,7 @@ export function validateSchemaFile(absolutePath, file, schemaUids = new Set()) {
63
63
  return [{ rule: 'schemas/shape', message: 'Schema must be a JSON object.', file }];
64
64
  }
65
65
  const errors = [];
66
- const { uid, name, fields } = schema;
66
+ const { uid, name, fields, i18n, isSingleton } = schema;
67
67
  const expectedUid = basename(file, '.json');
68
68
  if (typeof uid !== 'string' || uid.length === 0) {
69
69
  errors.push({
@@ -93,6 +93,32 @@ export function validateSchemaFile(absolutePath, file, schemaUids = new Set()) {
93
93
  file,
94
94
  });
95
95
  }
96
+ if (isSingleton !== undefined && typeof isSingleton !== 'boolean') {
97
+ errors.push({
98
+ rule: 'schemas/singleton',
99
+ message: '"isSingleton" must be a boolean when provided.',
100
+ file,
101
+ });
102
+ }
103
+ if (i18n !== undefined) {
104
+ if (typeof i18n !== 'object' || i18n === null || Array.isArray(i18n)) {
105
+ errors.push({
106
+ rule: 'schemas/i18n',
107
+ message: '"i18n" must be an object like { "enabled": false }.',
108
+ file,
109
+ });
110
+ }
111
+ else {
112
+ const keys = Object.keys(i18n);
113
+ if (keys.length !== 1 || keys[0] !== 'enabled' || typeof i18n.enabled !== 'boolean') {
114
+ errors.push({
115
+ rule: 'schemas/i18n',
116
+ message: '"i18n" only supports one boolean option: { "enabled": true | false }.',
117
+ file,
118
+ });
119
+ }
120
+ }
121
+ }
96
122
  if (typeof fields !== 'object' || fields === null || Array.isArray(fields)) {
97
123
  errors.push({
98
124
  rule: 'schemas/fields',
@@ -135,10 +161,11 @@ export function validateSchemaFile(absolutePath, file, schemaUids = new Set()) {
135
161
  file,
136
162
  });
137
163
  }
138
- if ('i18n' in definition) {
164
+ if ('i18n' in definition || 'localized' in definition) {
165
+ const key = 'i18n' in definition ? 'i18n' : 'localized';
139
166
  errors.push({
140
167
  rule: 'schemas/field-i18n',
141
- message: `Field "${fieldName}": field-level locale settings ("i18n") are not supported.`,
168
+ message: `Field "${fieldName}": field-level locale settings ("${key}") are not supported.`,
142
169
  file,
143
170
  });
144
171
  }
@@ -167,7 +194,7 @@ export function validateSchemaFile(absolutePath, file, schemaUids = new Set()) {
167
194
  }
168
195
  }
169
196
  else {
170
- for (const relationOnlyKey of ['target', 'many', 'localized']) {
197
+ for (const relationOnlyKey of ['target', 'many']) {
171
198
  if (relationOnlyKey in definition) {
172
199
  errors.push({
173
200
  rule: 'schemas/relation-shape',
@@ -55,6 +55,42 @@ describe('validateSchemas', () => {
55
55
  });
56
56
  expect(validateSchemas({ cwd })).toEqual({ errors: [], schemasChecked: 1 });
57
57
  });
58
+ it('accepts a non-localized schema policy', () => {
59
+ const cwd = writeProject({
60
+ 'Author.json': {
61
+ uid: 'Author',
62
+ name: 'Authors',
63
+ i18n: { enabled: false },
64
+ fields: { name: { type: 'string' } },
65
+ },
66
+ });
67
+ expect(validateSchemas({ cwd })).toEqual({ errors: [], schemasChecked: 1 });
68
+ });
69
+ it('rejects a non-boolean singleton policy', () => {
70
+ const cwd = writeProject({
71
+ 'Settings.json': {
72
+ uid: 'Settings',
73
+ name: 'Settings',
74
+ isSingleton: 'yes',
75
+ fields: {},
76
+ },
77
+ });
78
+ const { errors } = validateSchemas({ cwd });
79
+ expect(errors).toEqual([expect.objectContaining({ rule: 'schemas/singleton' })]);
80
+ });
81
+ it.each([true, { enabled: 'no' }, { enabled: false, fallback: 'en' }, { fallback: 'en' }])('rejects an invalid schema-level i18n policy %#', (i18n) => {
82
+ const cwd = writeProject({
83
+ 'Author.json': {
84
+ uid: 'Author',
85
+ name: 'Authors',
86
+ i18n,
87
+ fields: { name: { type: 'string' } },
88
+ },
89
+ });
90
+ const { errors } = validateSchemas({ cwd });
91
+ expect(errors).toHaveLength(1);
92
+ expect(errors[0]).toMatchObject({ rule: 'schemas/i18n' });
93
+ });
58
94
  it('returns no errors when the schemas directory does not exist', () => {
59
95
  scratch = mkdtempSync(join(tmpdir(), 'validate-schemas-'));
60
96
  expect(validateSchemas({ cwd: scratch })).toEqual({ errors: [], schemasChecked: 0 });
@@ -178,6 +214,19 @@ describe('validateSchemas', () => {
178
214
  expect(errors).toHaveLength(1);
179
215
  expect(errors[0]).toMatchObject({ rule: 'schemas/field-i18n' });
180
216
  });
217
+ it('rejects relation-level locale settings', () => {
218
+ const cwd = writeProject({
219
+ 'Page.json': {
220
+ uid: 'Page',
221
+ name: 'Pages',
222
+ fields: { parent: { type: 'relation', target: 'Page', localized: true } },
223
+ },
224
+ });
225
+ const { errors } = validateSchemas({ cwd });
226
+ expect(errors).toHaveLength(1);
227
+ expect(errors[0]).toMatchObject({ rule: 'schemas/field-i18n' });
228
+ expect(errors[0].message).toContain('"localized"');
229
+ });
181
230
  it('rejects a uid that does not match the file name', () => {
182
231
  const cwd = writeProject({
183
232
  'TeamMember.json': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fimo",
3
- "version": "0.4.0-experimental.2",
3
+ "version": "0.4.0-experimental.3",
4
4
  "description": "Fimo CLI - create, deploy, and manage Fimo projects",
5
5
  "bin": {
6
6
  "fimo": "dist/cli/index.js"
@@ -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.4.0-experimental.2",
24
+ "fimo": "0.4.0-experimental.3",
25
25
  "input-otp": "^1.4.2",
26
26
  "isbot": "^5",
27
27
  "lucide-react": "^0.577.0",