@cravery/core 0.0.21 → 0.0.23

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 (76) hide show
  1. package/dist/lib/ai/embedding.d.ts +1 -1
  2. package/dist/lib/ai/embedding.d.ts.map +1 -1
  3. package/dist/lib/ai/embedding.js +12 -9
  4. package/dist/lib/ai/embedding.js.map +1 -1
  5. package/dist/lib/ai/genkit.d.ts +3 -3
  6. package/dist/lib/ai/genkit.d.ts.map +1 -1
  7. package/dist/lib/ai/genkit.js +12 -8
  8. package/dist/lib/ai/genkit.js.map +1 -1
  9. package/dist/types/ai/recipe.d.ts.map +1 -1
  10. package/dist/types/ai/recipe.js +17 -10
  11. package/dist/types/ai/recipe.js.map +1 -1
  12. package/dist/types/core/settings.d.ts.map +1 -1
  13. package/dist/types/core/settings.js.map +1 -1
  14. package/dist/types/iam/user.d.ts.map +1 -1
  15. package/dist/types/iam/user.js.map +1 -1
  16. package/dist/types/recipe/equipment.d.ts.map +1 -1
  17. package/dist/types/recipe/equipment.js +4 -5
  18. package/dist/types/recipe/equipment.js.map +1 -1
  19. package/dist/types/recipe/ingredient.d.ts.map +1 -1
  20. package/dist/types/recipe/ingredient.js +13 -14
  21. package/dist/types/recipe/ingredient.js.map +1 -1
  22. package/dist/types/recipe/instruction.js +4 -4
  23. package/dist/types/recipe/instruction.js.map +1 -1
  24. package/dist/types/recipe/nutrition.js +9 -9
  25. package/dist/types/recipe/nutrition.js.map +1 -1
  26. package/package.json +56 -56
  27. package/src/lib/ai/embedding.ts +15 -9
  28. package/src/lib/ai/flow.ts +89 -89
  29. package/src/lib/ai/genkit.ts +18 -12
  30. package/src/lib/api.ts +103 -103
  31. package/src/lib/utils/sanitize.ts +38 -38
  32. package/src/types/ai/recipe.ts +68 -61
  33. package/src/types/ai/translation.ts +27 -27
  34. package/src/types/core/asset.ts +17 -17
  35. package/src/types/core/enums/image_type.ts +5 -5
  36. package/src/types/core/enums/locale.ts +5 -5
  37. package/src/types/core/enums/measurement_system.ts +5 -5
  38. package/src/types/core/enums/theme.ts +5 -5
  39. package/src/types/core/enums/url_type.ts +13 -13
  40. package/src/types/core/settings.ts +68 -61
  41. package/src/types/iam/enums/profile_status.ts +10 -10
  42. package/src/types/iam/enums/subscription_role.ts +5 -5
  43. package/src/types/iam/enums/subscription_status.ts +10 -10
  44. package/src/types/iam/enums/subscription_tier.ts +4 -4
  45. package/src/types/iam/enums/user_role.ts +11 -11
  46. package/src/types/iam/enums/user_status.ts +10 -10
  47. package/src/types/iam/profile.ts +45 -45
  48. package/src/types/iam/subscription.ts +45 -45
  49. package/src/types/iam/user.ts +36 -31
  50. package/src/types/moderation/enums/moderation_status.ts +9 -9
  51. package/src/types/moderation/enums/priority.ts +5 -5
  52. package/src/types/moderation/enums/severity.ts +5 -5
  53. package/src/types/moderation/enums/suggestion_category.ts +11 -11
  54. package/src/types/moderation/moderation.ts +87 -87
  55. package/src/types/recipe/enums/allergen.ts +19 -19
  56. package/src/types/recipe/enums/cuisine.ts +39 -39
  57. package/src/types/recipe/enums/dietary_tag.ts +21 -21
  58. package/src/types/recipe/enums/difficulty.ts +10 -10
  59. package/src/types/recipe/enums/meal_type.ts +14 -14
  60. package/src/types/recipe/enums/recipe_source.ts +5 -5
  61. package/src/types/recipe/enums/recipe_status.ts +12 -12
  62. package/src/types/recipe/enums/spiciness.ts +5 -5
  63. package/src/types/recipe/enums/temperature_unit.ts +5 -5
  64. package/src/types/recipe/enums/unit.ts +51 -51
  65. package/src/types/recipe/equipment.ts +30 -31
  66. package/src/types/recipe/filters.ts +35 -35
  67. package/src/types/recipe/ingredient.ts +69 -70
  68. package/src/types/recipe/instruction.ts +31 -31
  69. package/src/types/recipe/nutrition.ts +29 -29
  70. package/src/types/recipe/recipe.ts +198 -198
  71. package/src/types/recipe/temperature.ts +12 -12
  72. package/src/types/report/enums/report_priority.ts +10 -10
  73. package/src/types/report/enums/report_status.ts +10 -10
  74. package/src/types/report/enums/report_target_type.ts +10 -10
  75. package/src/types/report/enums/report_type.ts +12 -12
  76. package/src/types/report/report.ts +68 -68
@@ -1,89 +1,89 @@
1
- import { z } from "genkit";
2
- import { AI_MODELS } from "../../config/ai";
3
- import { logAIUsage } from "./cost";
4
- import { ai } from "./genkit";
5
- import { uploadImageToStorage } from "../storage";
6
- import { AIModelConfig } from "../../types";
7
-
8
- export const ImagenOutputSchema = z.object({
9
- images: z.array(
10
- z.object({
11
- url: z.string(),
12
- path: z.string(),
13
- }),
14
- ),
15
- });
16
-
17
- export type FlowHandler<T, R> = (input: T, output: R | null) => Promise<R>;
18
- export type ImageFlowHandler<T> = (
19
- input: T,
20
- prompt: string,
21
- ) => Promise<{ base64Data: string; storagePath: string }[]>;
22
-
23
- export const createGeminiFlow = <T, R>(
24
- name: string,
25
- inputSchema: z.ZodSchema<T>,
26
- outputSchema: z.ZodSchema<R>,
27
- handler: FlowHandler<T, R>,
28
- model: AIModelConfig = AI_MODELS.Gemini25Flash,
29
- ) => {
30
- const FlowInputSchema = ai.defineSchema(`${name}InputSchema`, inputSchema);
31
- const FlowOutputSchema = ai.defineSchema(`${name}OutputSchema`, outputSchema);
32
- const prompt = ai.prompt<
33
- typeof FlowInputSchema,
34
- typeof FlowOutputSchema,
35
- z.ZodTypeAny
36
- >(name);
37
-
38
- return ai.defineFlow(
39
- {
40
- name: name,
41
- inputSchema: inputSchema,
42
- outputSchema: outputSchema,
43
- },
44
- async (input: T) => {
45
- const response = await prompt(input);
46
- logAIUsage({
47
- type: "multimodal",
48
- flowName: name,
49
- model,
50
- inputTokens: response.usage?.inputTokens || 0,
51
- outputTokens: response.usage?.outputTokens || 0,
52
- });
53
- return handler(input, response.output);
54
- },
55
- );
56
- };
57
-
58
- export const createImagenFlow = <T>(
59
- name: string,
60
- inputSchema: z.ZodSchema<T>,
61
- handler: ImageFlowHandler<T>,
62
- model: AIModelConfig = AI_MODELS.Imagen4Fast,
63
- ) => {
64
- return ai.defineFlow(
65
- {
66
- name: name,
67
- inputSchema: inputSchema,
68
- outputSchema: ImagenOutputSchema,
69
- },
70
- async (input: T) => {
71
- const imageData = await handler(input, name);
72
- const uploadPromises = imageData.map(({ base64Data, storagePath }) =>
73
- uploadImageToStorage(base64Data, storagePath).catch((err) => {
74
- throw err;
75
- }),
76
- );
77
- const uploadedImages = await Promise.all(uploadPromises);
78
- logAIUsage({
79
- type: "image",
80
- flowName: name,
81
- model,
82
- imageCount: imageData.length,
83
- });
84
- return {
85
- images: uploadedImages,
86
- };
87
- },
88
- );
89
- };
1
+ import { z } from "genkit";
2
+ import { AI_MODELS } from "../../config/ai";
3
+ import { logAIUsage } from "./cost";
4
+ import { ai } from "./genkit";
5
+ import { uploadImageToStorage } from "../storage";
6
+ import { AIModelConfig } from "../../types";
7
+
8
+ export const ImagenOutputSchema = z.object({
9
+ images: z.array(
10
+ z.object({
11
+ url: z.string(),
12
+ path: z.string(),
13
+ }),
14
+ ),
15
+ });
16
+
17
+ export type FlowHandler<T, R> = (input: T, output: R | null) => Promise<R>;
18
+ export type ImageFlowHandler<T> = (
19
+ input: T,
20
+ prompt: string,
21
+ ) => Promise<{ base64Data: string; storagePath: string }[]>;
22
+
23
+ export const createGeminiFlow = <T, R>(
24
+ name: string,
25
+ inputSchema: z.ZodSchema<T>,
26
+ outputSchema: z.ZodSchema<R>,
27
+ handler: FlowHandler<T, R>,
28
+ model: AIModelConfig = AI_MODELS.Gemini25Flash,
29
+ ) => {
30
+ const FlowInputSchema = ai.defineSchema(`${name}InputSchema`, inputSchema);
31
+ const FlowOutputSchema = ai.defineSchema(`${name}OutputSchema`, outputSchema);
32
+ const prompt = ai.prompt<
33
+ typeof FlowInputSchema,
34
+ typeof FlowOutputSchema,
35
+ z.ZodTypeAny
36
+ >(name);
37
+
38
+ return ai.defineFlow(
39
+ {
40
+ name: name,
41
+ inputSchema: inputSchema,
42
+ outputSchema: outputSchema,
43
+ },
44
+ async (input: T) => {
45
+ const response = await prompt(input);
46
+ logAIUsage({
47
+ type: "multimodal",
48
+ flowName: name,
49
+ model,
50
+ inputTokens: response.usage?.inputTokens || 0,
51
+ outputTokens: response.usage?.outputTokens || 0,
52
+ });
53
+ return handler(input, response.output);
54
+ },
55
+ );
56
+ };
57
+
58
+ export const createImagenFlow = <T>(
59
+ name: string,
60
+ inputSchema: z.ZodSchema<T>,
61
+ handler: ImageFlowHandler<T>,
62
+ model: AIModelConfig = AI_MODELS.Imagen4Fast,
63
+ ) => {
64
+ return ai.defineFlow(
65
+ {
66
+ name: name,
67
+ inputSchema: inputSchema,
68
+ outputSchema: ImagenOutputSchema,
69
+ },
70
+ async (input: T) => {
71
+ const imageData = await handler(input, name);
72
+ const uploadPromises = imageData.map(({ base64Data, storagePath }) =>
73
+ uploadImageToStorage(base64Data, storagePath).catch((err) => {
74
+ throw err;
75
+ }),
76
+ );
77
+ const uploadedImages = await Promise.all(uploadPromises);
78
+ logAIUsage({
79
+ type: "image",
80
+ flowName: name,
81
+ model,
82
+ imageCount: imageData.length,
83
+ });
84
+ return {
85
+ images: uploadedImages,
86
+ };
87
+ },
88
+ );
89
+ };
@@ -1,18 +1,24 @@
1
- import { genkit, EmbedderReference } from "genkit";
1
+ import { genkit, EmbedderReference, Genkit } from "genkit";
2
2
  import { vertexAI } from "@genkit-ai/google-genai";
3
+ import { onInit } from "firebase-functions/v2/core";
3
4
 
4
5
  const projectId =
5
6
  process.env.GCLOUD_PROJECT || process.env.GCP_PROJECT || "canary-cravery";
6
7
 
7
- export const ai = genkit({
8
- plugins: [
9
- vertexAI({
10
- location: "us-central1",
11
- projectId,
12
- }),
13
- ],
14
- });
8
+ // Declare but don't initialize yet - will be initialized in onInit
9
+ export let ai: Genkit;
10
+ export let geminiEmbedding001: EmbedderReference;
11
+
12
+ // Initialize during Firebase Functions init phase, not at module load
13
+ onInit(() => {
14
+ ai = genkit({
15
+ plugins: [
16
+ vertexAI({
17
+ location: "us-central1",
18
+ projectId,
19
+ }),
20
+ ],
21
+ });
15
22
 
16
- export const geminiEmbedding001: EmbedderReference = vertexAI.embedder(
17
- "gemini-embedding-001",
18
- );
23
+ geminiEmbedding001 = vertexAI.embedder("gemini-embedding-001");
24
+ });
package/src/lib/api.ts CHANGED
@@ -1,103 +1,103 @@
1
- import {
2
- onCall,
3
- HttpsError,
4
- CallableRequest,
5
- CallableOptions,
6
- } from "firebase-functions/v2/https";
7
- import { z } from "genkit";
8
- import { isAdmin, isModeratorOrAbove, isTranslatorOrAbove } from "./iam";
9
-
10
- const DEFAULT_OPTIONS: CallableOptions = { cors: true, enforceAppCheck: true };
11
-
12
- const handleError = (error: unknown): never => {
13
- if (error instanceof z.ZodError) {
14
- throw new HttpsError(
15
- "invalid-argument",
16
- `Validation failed: ${error.message}`,
17
- );
18
- }
19
- if (error instanceof HttpsError) {
20
- throw error;
21
- }
22
- throw new HttpsError(
23
- "internal",
24
- error instanceof Error ? error.message : "Unknown error",
25
- );
26
- };
27
-
28
- class CallableBuilder<T> {
29
- private requiresAuth = false;
30
- private requiresAdmin = false;
31
- private requiresModerator = false;
32
- private requiresTranslator = false;
33
- private options: CallableOptions = DEFAULT_OPTIONS;
34
-
35
- constructor(private schema: z.ZodSchema<T>) {}
36
-
37
- requireAdmin() {
38
- this.requiresAuth = true;
39
- this.requiresAdmin = true;
40
- return this;
41
- }
42
-
43
- requireUser() {
44
- this.requiresAuth = true;
45
- return this;
46
- }
47
-
48
- requireModerator() {
49
- this.requiresAuth = true;
50
- this.requiresModerator = true;
51
- return this;
52
- }
53
-
54
- requireTranslator() {
55
- this.requiresAuth = true;
56
- this.requiresTranslator = true;
57
- return this;
58
- }
59
-
60
- withOptions(options: CallableOptions) {
61
- this.options = { ...DEFAULT_OPTIONS, ...options };
62
- return this;
63
- }
64
-
65
- handle<R>(handler: (data: T, request: CallableRequest) => Promise<R>) {
66
- return onCall(this.options, async (request) => {
67
- if (this.requiresAuth && !request.auth) {
68
- throw new HttpsError("unauthenticated", "Authentication required");
69
- }
70
-
71
- if (request.auth) {
72
- const uid = request.auth.uid;
73
-
74
- if (this.requiresAdmin && !(await isAdmin(uid))) {
75
- throw new HttpsError("permission-denied", "Admin access required");
76
- } else if (this.requiresModerator && !(await isModeratorOrAbove(uid))) {
77
- throw new HttpsError(
78
- "permission-denied",
79
- "Moderator access required",
80
- );
81
- } else if (
82
- this.requiresTranslator &&
83
- !(await isTranslatorOrAbove(uid))
84
- ) {
85
- throw new HttpsError(
86
- "permission-denied",
87
- "Translator access required",
88
- );
89
- }
90
- }
91
-
92
- try {
93
- const data = this.schema.parse(request.data);
94
- return await handler(data, request);
95
- } catch (error) {
96
- throw handleError(error);
97
- }
98
- });
99
- }
100
- }
101
-
102
- export const callable = <T>(schema: z.ZodSchema<T>) =>
103
- new CallableBuilder(schema);
1
+ import {
2
+ onCall,
3
+ HttpsError,
4
+ CallableRequest,
5
+ CallableOptions,
6
+ } from "firebase-functions/v2/https";
7
+ import { z } from "genkit";
8
+ import { isAdmin, isModeratorOrAbove, isTranslatorOrAbove } from "./iam";
9
+
10
+ const DEFAULT_OPTIONS: CallableOptions = { cors: true, enforceAppCheck: true };
11
+
12
+ const handleError = (error: unknown): never => {
13
+ if (error instanceof z.ZodError) {
14
+ throw new HttpsError(
15
+ "invalid-argument",
16
+ `Validation failed: ${error.message}`,
17
+ );
18
+ }
19
+ if (error instanceof HttpsError) {
20
+ throw error;
21
+ }
22
+ throw new HttpsError(
23
+ "internal",
24
+ error instanceof Error ? error.message : "Unknown error",
25
+ );
26
+ };
27
+
28
+ class CallableBuilder<T> {
29
+ private requiresAuth = false;
30
+ private requiresAdmin = false;
31
+ private requiresModerator = false;
32
+ private requiresTranslator = false;
33
+ private options: CallableOptions = DEFAULT_OPTIONS;
34
+
35
+ constructor(private schema: z.ZodSchema<T>) {}
36
+
37
+ requireAdmin() {
38
+ this.requiresAuth = true;
39
+ this.requiresAdmin = true;
40
+ return this;
41
+ }
42
+
43
+ requireUser() {
44
+ this.requiresAuth = true;
45
+ return this;
46
+ }
47
+
48
+ requireModerator() {
49
+ this.requiresAuth = true;
50
+ this.requiresModerator = true;
51
+ return this;
52
+ }
53
+
54
+ requireTranslator() {
55
+ this.requiresAuth = true;
56
+ this.requiresTranslator = true;
57
+ return this;
58
+ }
59
+
60
+ withOptions(options: CallableOptions) {
61
+ this.options = { ...DEFAULT_OPTIONS, ...options };
62
+ return this;
63
+ }
64
+
65
+ handle<R>(handler: (data: T, request: CallableRequest) => Promise<R>) {
66
+ return onCall(this.options, async (request) => {
67
+ if (this.requiresAuth && !request.auth) {
68
+ throw new HttpsError("unauthenticated", "Authentication required");
69
+ }
70
+
71
+ if (request.auth) {
72
+ const uid = request.auth.uid;
73
+
74
+ if (this.requiresAdmin && !(await isAdmin(uid))) {
75
+ throw new HttpsError("permission-denied", "Admin access required");
76
+ } else if (this.requiresModerator && !(await isModeratorOrAbove(uid))) {
77
+ throw new HttpsError(
78
+ "permission-denied",
79
+ "Moderator access required",
80
+ );
81
+ } else if (
82
+ this.requiresTranslator &&
83
+ !(await isTranslatorOrAbove(uid))
84
+ ) {
85
+ throw new HttpsError(
86
+ "permission-denied",
87
+ "Translator access required",
88
+ );
89
+ }
90
+ }
91
+
92
+ try {
93
+ const data = this.schema.parse(request.data);
94
+ return await handler(data, request);
95
+ } catch (error) {
96
+ throw handleError(error);
97
+ }
98
+ });
99
+ }
100
+ }
101
+
102
+ export const callable = <T>(schema: z.ZodSchema<T>) =>
103
+ new CallableBuilder(schema);
@@ -1,38 +1,38 @@
1
- import { z } from "genkit";
2
-
3
- /**
4
- * Removes HTML tags and script content from a string.
5
- * Use for text fields that should not contain HTML (descriptions, bios, etc.)
6
- */
7
- export function sanitizeText(text: string): string {
8
- return text
9
- .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
10
- .replace(/<[^>]*>/g, "")
11
- .trim();
12
- }
13
-
14
- /**
15
- * Creates a Zod string schema that sanitizes HTML on transformation.
16
- * Use in schemas where text input needs to be cleaned.
17
- *
18
- * @example
19
- * const BioSchema = sanitizedString(z.string().max(280));
20
- */
21
- export function sanitizedString<T extends z.ZodString>(schema: T) {
22
- return schema.transform(sanitizeText);
23
- }
24
-
25
- /**
26
- * Escapes HTML special characters to prevent XSS.
27
- * Use when HTML needs to be displayed as text.
28
- */
29
- export function escapeHtml(text: string): string {
30
- const htmlEscapes: Record<string, string> = {
31
- "&": "&amp;",
32
- "<": "&lt;",
33
- ">": "&gt;",
34
- '"': "&quot;",
35
- "'": "&#39;",
36
- };
37
- return text.replace(/[&<>"']/g, (char) => htmlEscapes[char]);
38
- }
1
+ import { z } from "genkit";
2
+
3
+ /**
4
+ * Removes HTML tags and script content from a string.
5
+ * Use for text fields that should not contain HTML (descriptions, bios, etc.)
6
+ */
7
+ export function sanitizeText(text: string): string {
8
+ return text
9
+ .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
10
+ .replace(/<[^>]*>/g, "")
11
+ .trim();
12
+ }
13
+
14
+ /**
15
+ * Creates a Zod string schema that sanitizes HTML on transformation.
16
+ * Use in schemas where text input needs to be cleaned.
17
+ *
18
+ * @example
19
+ * const BioSchema = sanitizedString(z.string().max(280));
20
+ */
21
+ export function sanitizedString<T extends z.ZodString>(schema: T) {
22
+ return schema.transform(sanitizeText);
23
+ }
24
+
25
+ /**
26
+ * Escapes HTML special characters to prevent XSS.
27
+ * Use when HTML needs to be displayed as text.
28
+ */
29
+ export function escapeHtml(text: string): string {
30
+ const htmlEscapes: Record<string, string> = {
31
+ "&": "&amp;",
32
+ "<": "&lt;",
33
+ ">": "&gt;",
34
+ '"': "&quot;",
35
+ "'": "&#39;",
36
+ };
37
+ return text.replace(/[&<>"']/g, (char) => htmlEscapes[char]);
38
+ }
@@ -1,61 +1,68 @@
1
- import { z } from "genkit";
2
- import {
3
- AllergenSchema,
4
- CuisineSchema,
5
- DietaryTagSchema,
6
- DifficultySchema,
7
- EquipmentSchema,
8
- IngredientSectionSchema,
9
- InstructionSchema,
10
- MealTypeSchema,
11
- NutritionSchema,
12
- SpicinessSchema,
13
- type Allergen,
14
- type Cuisine,
15
- type DietaryTag,
16
- type Difficulty,
17
- type Equipment,
18
- type IngredientSection,
19
- type Instruction,
20
- type MealType,
21
- type Nutrition,
22
- type Spiciness,
23
- } from "../recipe";
24
-
25
- export interface AIRecipe {
26
- allergens: Allergen[];
27
- confidence: number;
28
- cuisine: Cuisine;
29
- description: string;
30
- dietaryTags: DietaryTag[];
31
- difficulty: Difficulty;
32
- equipment?: Equipment[];
33
- ingredientSections: IngredientSection[];
34
- instructions: Instruction[];
35
- mealTypes: MealType[];
36
- nutrition?: Nutrition;
37
- servings: number;
38
- spiciness: Spiciness;
39
- time: number;
40
- tips?: string[];
41
- title: string;
42
- }
43
-
44
- export const AIRecipeSchema = z.object({
45
- allergens: z.array(AllergenSchema),
46
- confidence: z.number().min(0).max(1),
47
- cuisine: CuisineSchema,
48
- description: z.string().min(10).max(2000),
49
- dietaryTags: z.array(DietaryTagSchema),
50
- difficulty: DifficultySchema,
51
- equipment: z.array(EquipmentSchema).max(20).optional(),
52
- ingredientSections: z.array(IngredientSectionSchema).min(1).max(10),
53
- instructions: z.array(InstructionSchema).min(1).max(50),
54
- mealTypes: z.array(MealTypeSchema).min(1),
55
- nutrition: NutritionSchema.optional(),
56
- servings: z.number().int().min(1).max(100),
57
- spiciness: SpicinessSchema,
58
- time: z.number().int().min(1).max(1440),
59
- tips: z.array(z.string().max(500)).max(10).optional(),
60
- title: z.string().min(3).max(200),
61
- }) satisfies z.ZodType<AIRecipe>;
1
+ import { z } from "genkit";
2
+ import {
3
+ AllergenSchema,
4
+ CuisineSchema,
5
+ DietaryTagSchema,
6
+ DifficultySchema,
7
+ EquipmentSchema,
8
+ IngredientSectionSchema,
9
+ InstructionSchema,
10
+ MealTypeSchema,
11
+ NutritionSchema,
12
+ SpicinessSchema,
13
+ type Allergen,
14
+ type Cuisine,
15
+ type DietaryTag,
16
+ type Difficulty,
17
+ type Equipment,
18
+ type IngredientSection,
19
+ type Instruction,
20
+ type MealType,
21
+ type Nutrition,
22
+ type Spiciness,
23
+ } from "../recipe";
24
+
25
+ export interface AIRecipe {
26
+ allergens: Allergen[];
27
+ confidence: number;
28
+ cuisine: Cuisine;
29
+ description: string;
30
+ dietaryTags: DietaryTag[];
31
+ difficulty: Difficulty;
32
+ equipment?: Equipment[];
33
+ ingredientSections: IngredientSection[];
34
+ instructions: Instruction[];
35
+ mealTypes: MealType[];
36
+ nutrition?: Nutrition;
37
+ servings: number;
38
+ spiciness: Spiciness;
39
+ time: number;
40
+ tips?: string[];
41
+ title: string;
42
+ }
43
+
44
+ // Simplified schema for Gemini structured output
45
+ // Removed complex constraints that cause "too many states" errors:
46
+ // - Regex patterns on slugs (removed from nested schemas)
47
+ // - High array max limits (instructions max 50, equipment max 20, etc)
48
+ // - Numeric min/max bounds (confidence 0-1, servings 1-100, time 1-1440, etc)
49
+ // - String length constraints (description 10-2000, title 3-200, etc)
50
+ // These validations should be done in application logic if needed
51
+ export const AIRecipeSchema = z.object({
52
+ allergens: z.array(AllergenSchema),
53
+ confidence: z.number(),
54
+ cuisine: CuisineSchema,
55
+ description: z.string(),
56
+ dietaryTags: z.array(DietaryTagSchema),
57
+ difficulty: DifficultySchema,
58
+ equipment: z.array(EquipmentSchema).optional(),
59
+ ingredientSections: z.array(IngredientSectionSchema),
60
+ instructions: z.array(InstructionSchema),
61
+ mealTypes: z.array(MealTypeSchema),
62
+ nutrition: NutritionSchema.optional(),
63
+ servings: z.number(),
64
+ spiciness: SpicinessSchema,
65
+ time: z.number(),
66
+ tips: z.array(z.string()).optional(),
67
+ title: z.string(),
68
+ }) satisfies z.ZodType<AIRecipe>;