@better-i18n/schemas 0.2.1 → 0.2.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.
- package/package.json +18 -19
- package/src/ai-models.ts +251 -0
- package/src/content.ts +291 -0
- package/src/github.ts +130 -0
- package/src/index.ts +10 -0
- package/src/languages/index.ts +265 -0
- package/src/organizations/index.ts +4 -0
- package/src/organizations/organization.input.ts +23 -0
- package/src/projects/index.ts +4 -0
- package/src/projects/project.input.ts +169 -0
- package/src/subscription.ts +12 -0
- package/src/sync.ts +80 -0
- package/src/translations.ts +269 -0
- package/dist/ai-models.d.ts +0 -96
- package/dist/ai-models.d.ts.map +0 -1
- package/dist/ai-models.js +0 -189
- package/dist/ai-models.js.map +0 -1
- package/dist/index.d.ts +0 -11
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -11
- package/dist/index.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,33 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-i18n/schemas",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Shared validation schemas for API and frontend",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
7
|
-
"types": "./
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"types": "./src/index.ts",
|
|
8
8
|
"exports": {
|
|
9
|
-
".":
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"./
|
|
15
|
-
"types": "./dist/ai-models.d.ts",
|
|
16
|
-
"bun": "./src/ai-models.ts",
|
|
17
|
-
"default": "./dist/ai-models.js"
|
|
18
|
-
}
|
|
9
|
+
".": "./src/index.ts",
|
|
10
|
+
"./projects": "./src/projects/index.ts",
|
|
11
|
+
"./organizations": "./src/organizations/index.ts",
|
|
12
|
+
"./languages": "./src/languages/index.ts",
|
|
13
|
+
"./github": "./src/github.ts",
|
|
14
|
+
"./translations": "./src/translations.ts"
|
|
19
15
|
},
|
|
20
16
|
"files": [
|
|
21
|
-
"
|
|
17
|
+
"src"
|
|
22
18
|
],
|
|
23
19
|
"scripts": {
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
20
|
+
"type-check": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"zod": "^3.25.76"
|
|
28
24
|
},
|
|
29
25
|
"devDependencies": {
|
|
30
26
|
"typescript": "~5.9.2"
|
|
31
27
|
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
32
31
|
"license": "MIT"
|
|
33
32
|
}
|
package/src/ai-models.ts
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared AI Model Configuration
|
|
3
|
+
* Single source of truth for all AI model definitions used across frontend and backend.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Provider types
|
|
7
|
+
export type ModelProvider =
|
|
8
|
+
| "better-ai"
|
|
9
|
+
| "openai"
|
|
10
|
+
| "gemini"
|
|
11
|
+
| "claude"
|
|
12
|
+
| "openrouter"
|
|
13
|
+
| "azure-openai";
|
|
14
|
+
|
|
15
|
+
// Model categories
|
|
16
|
+
export type ModelCategory =
|
|
17
|
+
| "flagship"
|
|
18
|
+
| "reasoning"
|
|
19
|
+
| "performance"
|
|
20
|
+
| "specialized"
|
|
21
|
+
| "legacy";
|
|
22
|
+
|
|
23
|
+
// Icon types for models
|
|
24
|
+
export type ModelIcon = "OpenAI" | "Gemini" | "Claude" | "BetterAI" | "AzureOpenAI";
|
|
25
|
+
|
|
26
|
+
// Model color configuration
|
|
27
|
+
export interface ModelColors {
|
|
28
|
+
/** Background color for UI elements */
|
|
29
|
+
background: string;
|
|
30
|
+
/** Hover background color */
|
|
31
|
+
hover: string;
|
|
32
|
+
/** Icon/text color */
|
|
33
|
+
icon: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Complete model configuration
|
|
37
|
+
export interface AIModelConfig {
|
|
38
|
+
/** UI display ID (e.g., "gpt-5.2", "gemini-3-flash") */
|
|
39
|
+
id: string;
|
|
40
|
+
/** Display name for UI (e.g., "ChatGPT 5.2", "Gemini 3 Flash") */
|
|
41
|
+
name: string;
|
|
42
|
+
/** Provider identifier */
|
|
43
|
+
provider: ModelProvider;
|
|
44
|
+
/** Icon to display for this model (from lucide-react or custom) */
|
|
45
|
+
icon: ModelIcon;
|
|
46
|
+
/** Colors for UI elements */
|
|
47
|
+
colors: ModelColors;
|
|
48
|
+
/** Actual API model ID to send to provider SDK (e.g., "gpt-5.2", "gemini-2.5-flash") */
|
|
49
|
+
apiModelId: string;
|
|
50
|
+
/** Max context window in tokens */
|
|
51
|
+
contextSize: number;
|
|
52
|
+
/** Max output tokens */
|
|
53
|
+
maxOutput: number;
|
|
54
|
+
/** Model description */
|
|
55
|
+
description: string;
|
|
56
|
+
/** Optional badge (e.g., "New", "Fast", "Default") */
|
|
57
|
+
badge?: string;
|
|
58
|
+
/** Is this the default model? */
|
|
59
|
+
isDefault?: boolean;
|
|
60
|
+
/** Release date info */
|
|
61
|
+
releaseDate?: string;
|
|
62
|
+
/** Training data cutoff */
|
|
63
|
+
trainingData?: string;
|
|
64
|
+
/** Speed score 0-100 */
|
|
65
|
+
speed?: number;
|
|
66
|
+
/** Model category */
|
|
67
|
+
category?: ModelCategory;
|
|
68
|
+
/** Whether this model requires user's own API key (not platform key) */
|
|
69
|
+
requiresUserKey?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Provider configuration (for UI)
|
|
73
|
+
export interface ProviderInfo {
|
|
74
|
+
id: ModelProvider;
|
|
75
|
+
name: string;
|
|
76
|
+
brandColor: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* All available AI models - Single Source of Truth
|
|
81
|
+
*
|
|
82
|
+
* Important: `apiModelId` is the actual model ID sent to the provider's SDK.
|
|
83
|
+
* This may differ from `id` which is used for UI/selection purposes.
|
|
84
|
+
*
|
|
85
|
+
* We keep only the best/flagship models from each provider to avoid clutter.
|
|
86
|
+
*/
|
|
87
|
+
export const AI_MODELS: AIModelConfig[] = [
|
|
88
|
+
// ============================================
|
|
89
|
+
// Better AI - Platform Default (Powered by Gemini 3.1 Pro Preview)
|
|
90
|
+
// ============================================
|
|
91
|
+
{
|
|
92
|
+
id: "better-ai",
|
|
93
|
+
name: "Better AI",
|
|
94
|
+
provider: "better-ai",
|
|
95
|
+
icon: "BetterAI",
|
|
96
|
+
colors: {
|
|
97
|
+
background: "bg-gray-50 dark:bg-neutral-700",
|
|
98
|
+
hover: "hover:bg-gray-100 dark:hover:bg-neutral-600",
|
|
99
|
+
icon: "text-gray-700 dark:text-white",
|
|
100
|
+
},
|
|
101
|
+
apiModelId: "gemini-2.5-flash",
|
|
102
|
+
contextSize: 1_048_576, // 1M tokens
|
|
103
|
+
maxOutput: 65_536,
|
|
104
|
+
description:
|
|
105
|
+
"Powerful AI model powered by Gemini 2.5 Preview. Great for translations.",
|
|
106
|
+
isDefault: true,
|
|
107
|
+
badge: "Default",
|
|
108
|
+
releaseDate: "Jan 2025",
|
|
109
|
+
trainingData: "Jan 2025",
|
|
110
|
+
speed: 95,
|
|
111
|
+
category: "flagship",
|
|
112
|
+
requiresUserKey: false,
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// ============================================
|
|
116
|
+
// Azure OpenAI Models (via Azure credits)
|
|
117
|
+
// ============================================
|
|
118
|
+
{
|
|
119
|
+
id: "gpt-5.3",
|
|
120
|
+
name: "GPT 5.3",
|
|
121
|
+
provider: "azure-openai",
|
|
122
|
+
icon: "AzureOpenAI",
|
|
123
|
+
colors: {
|
|
124
|
+
background: "bg-gray-100 dark:bg-neutral-800",
|
|
125
|
+
hover: "hover:bg-gray-200 dark:hover:bg-neutral-700",
|
|
126
|
+
icon: "text-gray-700 dark:text-neutral-300",
|
|
127
|
+
},
|
|
128
|
+
apiModelId: "gpt-5.3-chat",
|
|
129
|
+
contextSize: 1_047_576,
|
|
130
|
+
maxOutput: 128_000,
|
|
131
|
+
description: "OpenAI's latest and most capable model via Azure.",
|
|
132
|
+
badge: "New",
|
|
133
|
+
releaseDate: "2025",
|
|
134
|
+
trainingData: "2025",
|
|
135
|
+
speed: 90,
|
|
136
|
+
category: "flagship",
|
|
137
|
+
requiresUserKey: false,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: "gpt-5.2",
|
|
141
|
+
name: "GPT 5.2",
|
|
142
|
+
provider: "azure-openai",
|
|
143
|
+
icon: "AzureOpenAI",
|
|
144
|
+
colors: {
|
|
145
|
+
background: "bg-gray-100 dark:bg-neutral-800",
|
|
146
|
+
hover: "hover:bg-gray-200 dark:hover:bg-neutral-700",
|
|
147
|
+
icon: "text-gray-700 dark:text-neutral-300",
|
|
148
|
+
},
|
|
149
|
+
apiModelId: "gpt-5.2-chat",
|
|
150
|
+
contextSize: 1_047_576,
|
|
151
|
+
maxOutput: 128_000,
|
|
152
|
+
description: "OpenAI GPT 5.2 via Azure OpenAI.",
|
|
153
|
+
badge: "Fast",
|
|
154
|
+
releaseDate: "2025",
|
|
155
|
+
trainingData: "2025",
|
|
156
|
+
speed: 95,
|
|
157
|
+
category: "flagship",
|
|
158
|
+
requiresUserKey: false,
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
// ============================================
|
|
162
|
+
// Google Gemini Models
|
|
163
|
+
// ============================================
|
|
164
|
+
{
|
|
165
|
+
id: "gemini-3-pro",
|
|
166
|
+
name: "Gemini 3 Pro",
|
|
167
|
+
provider: "gemini",
|
|
168
|
+
icon: "Gemini",
|
|
169
|
+
colors: {
|
|
170
|
+
background: "bg-blue-50 dark:bg-blue-700",
|
|
171
|
+
hover: "hover:bg-blue-100 dark:hover:bg-blue-600",
|
|
172
|
+
icon: "text-blue-500 dark:text-blue-100",
|
|
173
|
+
},
|
|
174
|
+
apiModelId: "gemini-3-pro-preview",
|
|
175
|
+
contextSize: 1_048_576, // 1M tokens (verified from Google docs)
|
|
176
|
+
maxOutput: 65_536,
|
|
177
|
+
description: "Google's most capable model. Best for complex tasks.",
|
|
178
|
+
badge: "Pro",
|
|
179
|
+
releaseDate: "Jan 2025",
|
|
180
|
+
trainingData: "Jan 2025",
|
|
181
|
+
speed: 90,
|
|
182
|
+
category: "flagship",
|
|
183
|
+
requiresUserKey: false,
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
// ============================================
|
|
188
|
+
// Helper Functions
|
|
189
|
+
// ============================================
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get model config by ID
|
|
193
|
+
*/
|
|
194
|
+
export function getModelById(modelId: string): AIModelConfig | undefined {
|
|
195
|
+
return AI_MODELS.find((m) => m.id === modelId);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Get the actual API model ID for a given UI model ID
|
|
200
|
+
*/
|
|
201
|
+
export function getApiModelId(modelId: string): string {
|
|
202
|
+
const model = getModelById(modelId);
|
|
203
|
+
return model?.apiModelId ?? modelId;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Get provider for a model ID
|
|
208
|
+
*/
|
|
209
|
+
export function getModelProvider(modelId: string): ModelProvider {
|
|
210
|
+
const model = getModelById(modelId);
|
|
211
|
+
return model?.provider ?? "better-ai";
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get default model
|
|
216
|
+
*/
|
|
217
|
+
export function getDefaultModel(): AIModelConfig {
|
|
218
|
+
return AI_MODELS.find((m) => m.isDefault) ?? AI_MODELS[0];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get models by provider
|
|
223
|
+
*/
|
|
224
|
+
export function getModelsByProvider(provider: ModelProvider): AIModelConfig[] {
|
|
225
|
+
return AI_MODELS.filter((m) => m.provider === provider);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Get models that don't require user API key (platform-provided)
|
|
230
|
+
*/
|
|
231
|
+
export function getPlatformModels(): AIModelConfig[] {
|
|
232
|
+
return AI_MODELS.filter((m) => !m.requiresUserKey);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Check if a model requires user's own API key
|
|
237
|
+
*/
|
|
238
|
+
export function modelRequiresUserKey(modelId: string): boolean {
|
|
239
|
+
const model = getModelById(modelId);
|
|
240
|
+
return model?.requiresUserKey ?? false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Format context size for display (e.g., "128K", "2M")
|
|
245
|
+
*/
|
|
246
|
+
export function formatContextSize(tokens: number): string {
|
|
247
|
+
if (tokens >= 1_000_000) {
|
|
248
|
+
return `${(tokens / 1_000_000).toFixed(0)}M`;
|
|
249
|
+
}
|
|
250
|
+
return `${(tokens / 1_000).toFixed(0)}K`;
|
|
251
|
+
}
|
package/src/content.ts
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod validation schemas for Content Model CRUD operations.
|
|
3
|
+
*
|
|
4
|
+
* Content models are user-defined content types (blog, changelog, legal, etc.)
|
|
5
|
+
* with custom field definitions. These schemas validate all API inputs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
// —————————————————————————————————————————————————————————————————————————————
|
|
11
|
+
// Shared enums
|
|
12
|
+
// —————————————————————————————————————————————————————————————————————————————
|
|
13
|
+
|
|
14
|
+
export const contentModelFieldTypeEnum = z.enum([
|
|
15
|
+
"text",
|
|
16
|
+
"textarea",
|
|
17
|
+
"richtext",
|
|
18
|
+
"number",
|
|
19
|
+
"boolean",
|
|
20
|
+
"date",
|
|
21
|
+
"datetime",
|
|
22
|
+
"enum",
|
|
23
|
+
"media",
|
|
24
|
+
"relation",
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
export const contentModelKindEnum = z.enum(["collection", "single"]);
|
|
28
|
+
|
|
29
|
+
// —————————————————————————————————————————————————————————————————————————————
|
|
30
|
+
// Field-level config schemas (must precede model schemas that reference them)
|
|
31
|
+
// —————————————————————————————————————————————————————————————————————————————
|
|
32
|
+
|
|
33
|
+
/** Single enum option (label displayed to users, value stored in DB) */
|
|
34
|
+
export const enumValueItemSchema = z.object({
|
|
35
|
+
label: z.string().min(1).max(200),
|
|
36
|
+
value: z.string().min(1).max(200),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/** Extensible field-level options (Unsplash, AI generation, enum values, etc.) */
|
|
40
|
+
export const fieldOptionsSchema = z.object({
|
|
41
|
+
unsplash: z.object({ enabled: z.boolean() }).optional(),
|
|
42
|
+
aiGeneration: z.object({
|
|
43
|
+
enabled: z.boolean(),
|
|
44
|
+
prompt: z.string().max(2000).optional(),
|
|
45
|
+
model: z.string().max(100).optional(),
|
|
46
|
+
}).optional(),
|
|
47
|
+
enumValues: z.array(enumValueItemSchema).max(200).optional(),
|
|
48
|
+
showInTable: z.boolean().optional(),
|
|
49
|
+
}).optional();
|
|
50
|
+
|
|
51
|
+
/** Type-specific field configuration (e.g., targetModel for relation fields) */
|
|
52
|
+
export const fieldConfigSchema = z.object({
|
|
53
|
+
targetModel: z.string().optional(),
|
|
54
|
+
}).optional();
|
|
55
|
+
|
|
56
|
+
// —————————————————————————————————————————————————————————————————————————————
|
|
57
|
+
// Model schemas
|
|
58
|
+
// —————————————————————————————————————————————————————————————————————————————
|
|
59
|
+
|
|
60
|
+
/** List all content models for a project */
|
|
61
|
+
export const listContentModelsSchema = z.object({
|
|
62
|
+
projectId: z.string().uuid(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/** Get a single content model by slug */
|
|
66
|
+
export const getContentModelBySlugSchema = z.object({
|
|
67
|
+
projectId: z.string().uuid(),
|
|
68
|
+
slug: z.string().min(1),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/** Get a single content model by ID */
|
|
72
|
+
export const getContentModelSchema = z.object({
|
|
73
|
+
modelId: z.string().uuid(),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
/** Create a content model with fields atomically */
|
|
77
|
+
export const createContentModelSchema = z.object({
|
|
78
|
+
projectId: z.string().uuid(),
|
|
79
|
+
organizationId: z.string().uuid(),
|
|
80
|
+
slug: z
|
|
81
|
+
.string()
|
|
82
|
+
.min(1)
|
|
83
|
+
.max(100)
|
|
84
|
+
.regex(/^[a-z0-9-]+$/, "Slug must contain only lowercase letters, numbers, and hyphens"),
|
|
85
|
+
displayName: z.string().min(1).max(200),
|
|
86
|
+
description: z.string().max(1000).optional(),
|
|
87
|
+
kind: contentModelKindEnum.default("collection"),
|
|
88
|
+
icon: z.string().max(50).optional(),
|
|
89
|
+
enableVersionHistory: z.boolean().default(true),
|
|
90
|
+
includeBody: z.boolean().default(true),
|
|
91
|
+
fields: z
|
|
92
|
+
.array(
|
|
93
|
+
z.object({
|
|
94
|
+
name: z
|
|
95
|
+
.string()
|
|
96
|
+
.min(1)
|
|
97
|
+
.max(100)
|
|
98
|
+
.regex(
|
|
99
|
+
/^[a-z_][a-z0-9_]*$/,
|
|
100
|
+
"Field name must be snake_case (lowercase letters, numbers, underscores)",
|
|
101
|
+
),
|
|
102
|
+
displayName: z.string().min(1).max(200),
|
|
103
|
+
type: contentModelFieldTypeEnum.default("text"),
|
|
104
|
+
localized: z.boolean().default(false),
|
|
105
|
+
required: z.boolean().default(false),
|
|
106
|
+
placeholder: z.string().max(500).optional(),
|
|
107
|
+
helpText: z.string().max(500).optional(),
|
|
108
|
+
position: z.number().int().min(0),
|
|
109
|
+
fieldConfig: fieldConfigSchema,
|
|
110
|
+
options: fieldOptionsSchema,
|
|
111
|
+
}),
|
|
112
|
+
)
|
|
113
|
+
.default([]),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/** Table settings schema for base field visibility */
|
|
117
|
+
export const contentModelTableSettingsSchema = z.object({
|
|
118
|
+
baseFields: z.record(z.string(), z.boolean()).optional(),
|
|
119
|
+
}).optional();
|
|
120
|
+
|
|
121
|
+
/** Update a content model's metadata (not fields) */
|
|
122
|
+
export const updateContentModelSchema = z.object({
|
|
123
|
+
modelId: z.string().uuid(),
|
|
124
|
+
displayName: z.string().min(1).max(200).optional(),
|
|
125
|
+
description: z.string().max(1000).optional(),
|
|
126
|
+
kind: contentModelKindEnum.optional(),
|
|
127
|
+
icon: z.string().max(50).optional(),
|
|
128
|
+
enableVersionHistory: z.boolean().optional(),
|
|
129
|
+
includeBody: z.boolean().optional(),
|
|
130
|
+
tableSettings: contentModelTableSettingsSchema,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
/** Delete a content model */
|
|
134
|
+
export const deleteContentModelSchema = z.object({
|
|
135
|
+
modelId: z.string().uuid(),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// —————————————————————————————————————————————————————————————————————————————
|
|
139
|
+
// Field schemas
|
|
140
|
+
// —————————————————————————————————————————————————————————————————————————————
|
|
141
|
+
|
|
142
|
+
/** Add a field to a content model */
|
|
143
|
+
export const addContentModelFieldSchema = z.object({
|
|
144
|
+
modelId: z.string().uuid(),
|
|
145
|
+
name: z
|
|
146
|
+
.string()
|
|
147
|
+
.min(1)
|
|
148
|
+
.max(100)
|
|
149
|
+
.regex(
|
|
150
|
+
/^[a-z_][a-z0-9_]*$/,
|
|
151
|
+
"Field name must be snake_case",
|
|
152
|
+
),
|
|
153
|
+
displayName: z.string().min(1).max(200),
|
|
154
|
+
type: contentModelFieldTypeEnum.default("text"),
|
|
155
|
+
localized: z.boolean().default(false),
|
|
156
|
+
required: z.boolean().default(false),
|
|
157
|
+
placeholder: z.string().max(500).optional(),
|
|
158
|
+
helpText: z.string().max(500).optional(),
|
|
159
|
+
position: z.number().int().min(0).optional(),
|
|
160
|
+
options: fieldOptionsSchema,
|
|
161
|
+
fieldConfig: fieldConfigSchema,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
/** Update an existing field */
|
|
165
|
+
export const updateContentModelFieldSchema = z.object({
|
|
166
|
+
fieldId: z.string().uuid(),
|
|
167
|
+
displayName: z.string().min(1).max(200).optional(),
|
|
168
|
+
type: contentModelFieldTypeEnum.optional(),
|
|
169
|
+
localized: z.boolean().optional(),
|
|
170
|
+
required: z.boolean().optional(),
|
|
171
|
+
placeholder: z.string().max(500).optional(),
|
|
172
|
+
helpText: z.string().max(500).optional(),
|
|
173
|
+
options: fieldOptionsSchema,
|
|
174
|
+
fieldConfig: fieldConfigSchema,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
/** Remove a field from a content model */
|
|
178
|
+
export const removeContentModelFieldSchema = z.object({
|
|
179
|
+
fieldId: z.string().uuid(),
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
/** Reorder fields within a content model */
|
|
183
|
+
export const reorderContentModelFieldsSchema = z.object({
|
|
184
|
+
modelId: z.string().uuid(),
|
|
185
|
+
fieldIds: z.array(z.string().uuid()).min(1),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// —————————————————————————————————————————————————————————————————————————————
|
|
189
|
+
// Entry schemas
|
|
190
|
+
// —————————————————————————————————————————————————————————————————————————————
|
|
191
|
+
|
|
192
|
+
export const contentEntryStatusEnum = z.enum(["draft", "published", "archived"]);
|
|
193
|
+
export const contentEntrySortFieldEnum = z.enum(["publishedAt", "createdAt", "updatedAt", "title"]);
|
|
194
|
+
|
|
195
|
+
/** List content entries with filtering and pagination */
|
|
196
|
+
export const listContentEntriesSchema = z.object({
|
|
197
|
+
projectId: z.string().uuid(),
|
|
198
|
+
modelSlug: z.string().optional(),
|
|
199
|
+
status: contentEntryStatusEnum.optional(),
|
|
200
|
+
search: z.string().optional(),
|
|
201
|
+
languageCodes: z.array(z.string().transform(v => v.toLowerCase())).optional(),
|
|
202
|
+
/** Filter by custom field values. Key = field name, value = exact match. */
|
|
203
|
+
fieldFilters: z.record(z.string(), z.string()).optional(),
|
|
204
|
+
sort: contentEntrySortFieldEnum.optional(),
|
|
205
|
+
order: z.enum(["asc", "desc"]).optional(),
|
|
206
|
+
page: z.number().int().min(1).default(1),
|
|
207
|
+
limit: z.number().int().min(1).max(100).default(25),
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
/** Get a single content entry by ID */
|
|
211
|
+
export const getContentEntrySchema = z.object({
|
|
212
|
+
entryId: z.string().uuid(),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
/** Create a content entry */
|
|
216
|
+
export const createContentEntrySchema = z.object({
|
|
217
|
+
contentModelId: z.string().uuid(),
|
|
218
|
+
organizationId: z.string().uuid(),
|
|
219
|
+
slug: z
|
|
220
|
+
.string()
|
|
221
|
+
.min(1)
|
|
222
|
+
.max(200)
|
|
223
|
+
.regex(/^[a-z0-9-]+$/, "Slug must contain only lowercase letters, numbers, and hyphens"),
|
|
224
|
+
sourceLanguageCode: z.string().default("en").transform(v => v.toLowerCase()),
|
|
225
|
+
title: z.string().min(1).max(500),
|
|
226
|
+
body: z.unknown().default([]),
|
|
227
|
+
bodyMarkdown: z.string().optional(),
|
|
228
|
+
customFields: z.record(z.string(), z.string().nullable()).default({}),
|
|
229
|
+
status: contentEntryStatusEnum.default("draft"),
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
/** Update a content entry */
|
|
233
|
+
export const updateContentEntrySchema = z.object({
|
|
234
|
+
entryId: z.string().uuid(),
|
|
235
|
+
languageCode: z.string().optional().transform(v => v?.toLowerCase()),
|
|
236
|
+
title: z.string().min(1).max(500).optional(),
|
|
237
|
+
body: z.unknown().optional(),
|
|
238
|
+
bodyMarkdown: z.string().optional(),
|
|
239
|
+
customFields: z.record(z.string(), z.string().nullable()).optional(),
|
|
240
|
+
status: contentEntryStatusEnum.optional(),
|
|
241
|
+
slug: z
|
|
242
|
+
.string()
|
|
243
|
+
.min(1)
|
|
244
|
+
.max(200)
|
|
245
|
+
.regex(/^[a-z0-9-]+$/, "Slug must contain only lowercase letters, numbers, and hyphens")
|
|
246
|
+
.optional(),
|
|
247
|
+
publishedAt: z.string().datetime({ offset: true }).nullable().optional(),
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
/** Delete a content entry */
|
|
251
|
+
export const deleteContentEntrySchema = z.object({
|
|
252
|
+
entryId: z.string().uuid(),
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
/** Publish a content entry */
|
|
256
|
+
export const publishContentEntrySchema = z.object({
|
|
257
|
+
entryId: z.string().uuid(),
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
/** Generate AI content for a specific field */
|
|
261
|
+
export const generateFieldContentSchema = z.object({
|
|
262
|
+
entryId: z.string().uuid(),
|
|
263
|
+
fieldName: z.string().min(1),
|
|
264
|
+
languageCode: z.string().min(1).transform(v => v.toLowerCase()),
|
|
265
|
+
modelId: z.string().max(100).optional(),
|
|
266
|
+
prompt: z.string().max(4000).optional(),
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// —————————————————————————————————————————————————————————————————————————————
|
|
270
|
+
// Type exports
|
|
271
|
+
// —————————————————————————————————————————————————————————————————————————————
|
|
272
|
+
|
|
273
|
+
export type EnumValueItem = z.infer<typeof enumValueItemSchema>;
|
|
274
|
+
export type ListContentModelsInput = z.infer<typeof listContentModelsSchema>;
|
|
275
|
+
export type GetContentModelBySlugInput = z.infer<typeof getContentModelBySlugSchema>;
|
|
276
|
+
export type GetContentModelInput = z.infer<typeof getContentModelSchema>;
|
|
277
|
+
export type CreateContentModelInput = z.infer<typeof createContentModelSchema>;
|
|
278
|
+
export type UpdateContentModelInput = z.infer<typeof updateContentModelSchema>;
|
|
279
|
+
export type DeleteContentModelInput = z.infer<typeof deleteContentModelSchema>;
|
|
280
|
+
export type AddContentModelFieldInput = z.infer<typeof addContentModelFieldSchema>;
|
|
281
|
+
export type UpdateContentModelFieldInput = z.infer<typeof updateContentModelFieldSchema>;
|
|
282
|
+
export type RemoveContentModelFieldInput = z.infer<typeof removeContentModelFieldSchema>;
|
|
283
|
+
export type ReorderContentModelFieldsInput = z.infer<typeof reorderContentModelFieldsSchema>;
|
|
284
|
+
|
|
285
|
+
export type ListContentEntriesInput = z.infer<typeof listContentEntriesSchema>;
|
|
286
|
+
export type GetContentEntryInput = z.infer<typeof getContentEntrySchema>;
|
|
287
|
+
export type CreateContentEntryInput = z.infer<typeof createContentEntrySchema>;
|
|
288
|
+
export type UpdateContentEntryInput = z.infer<typeof updateContentEntrySchema>;
|
|
289
|
+
export type DeleteContentEntryInput = z.infer<typeof deleteContentEntrySchema>;
|
|
290
|
+
export type PublishContentEntryInput = z.infer<typeof publishContentEntrySchema>;
|
|
291
|
+
export type GenerateFieldContentInput = z.infer<typeof generateFieldContentSchema>;
|