@cravery/core 0.0.25 → 0.0.26

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 (286) hide show
  1. package/dist/index.d.ts +0 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +0 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/types/ai/recipe.d.ts +1 -244
  6. package/dist/types/ai/recipe.d.ts.map +1 -1
  7. package/dist/types/ai/recipe.js +0 -28
  8. package/dist/types/ai/recipe.js.map +1 -1
  9. package/dist/types/ai/translation.d.ts +1 -107
  10. package/dist/types/ai/translation.d.ts.map +1 -1
  11. package/dist/types/ai/translation.js +0 -11
  12. package/dist/types/ai/translation.js.map +1 -1
  13. package/dist/types/core/api.d.ts +4 -4
  14. package/dist/types/core/api.d.ts.map +1 -1
  15. package/dist/types/core/asset.d.ts +2 -22
  16. package/dist/types/core/asset.d.ts.map +1 -1
  17. package/dist/types/core/asset.js +0 -9
  18. package/dist/types/core/asset.js.map +1 -1
  19. package/dist/types/core/entity.d.ts +7 -0
  20. package/dist/types/core/entity.d.ts.map +1 -0
  21. package/dist/types/core/entity.js +3 -0
  22. package/dist/types/core/entity.js.map +1 -0
  23. package/dist/types/core/index.d.ts +5 -1
  24. package/dist/types/core/index.d.ts.map +1 -1
  25. package/dist/types/core/index.js +5 -1
  26. package/dist/types/core/index.js.map +1 -1
  27. package/dist/types/core/locale.d.ts +3 -0
  28. package/dist/types/core/locale.d.ts.map +1 -0
  29. package/dist/types/core/locale.js +5 -0
  30. package/dist/types/core/locale.js.map +1 -0
  31. package/dist/types/core/measurement_system.d.ts +3 -0
  32. package/dist/types/core/measurement_system.d.ts.map +1 -0
  33. package/dist/types/core/measurement_system.js +5 -0
  34. package/dist/types/core/measurement_system.js.map +1 -0
  35. package/dist/types/core/settings.d.ts +5 -88
  36. package/dist/types/core/settings.d.ts.map +1 -1
  37. package/dist/types/core/settings.js +0 -25
  38. package/dist/types/core/settings.js.map +1 -1
  39. package/dist/types/core/theme.d.ts +3 -0
  40. package/dist/types/core/theme.d.ts.map +1 -0
  41. package/dist/types/core/theme.js +5 -0
  42. package/dist/types/core/theme.js.map +1 -0
  43. package/dist/types/core/timestamp.d.ts +5 -0
  44. package/dist/types/core/timestamp.d.ts.map +1 -0
  45. package/dist/types/core/timestamp.js +3 -0
  46. package/dist/types/core/timestamp.js.map +1 -0
  47. package/dist/types/iam/index.d.ts +6 -1
  48. package/dist/types/iam/index.d.ts.map +1 -1
  49. package/dist/types/iam/index.js +6 -1
  50. package/dist/types/iam/index.js.map +1 -1
  51. package/dist/types/iam/profile.d.ts +2 -56
  52. package/dist/types/iam/profile.d.ts.map +1 -1
  53. package/dist/types/iam/profile.js +0 -22
  54. package/dist/types/iam/profile.js.map +1 -1
  55. package/dist/types/iam/profile_status.d.ts +3 -0
  56. package/dist/types/iam/profile_status.d.ts.map +1 -0
  57. package/dist/types/iam/profile_status.js +10 -0
  58. package/dist/types/iam/profile_status.js.map +1 -0
  59. package/dist/types/iam/subscription.d.ts +4 -80
  60. package/dist/types/iam/subscription.d.ts.map +1 -1
  61. package/dist/types/iam/subscription.js +0 -19
  62. package/dist/types/iam/subscription.js.map +1 -1
  63. package/dist/types/iam/subscription_role.d.ts +3 -0
  64. package/dist/types/iam/subscription_role.d.ts.map +1 -0
  65. package/dist/types/iam/subscription_role.js +5 -0
  66. package/dist/types/iam/subscription_role.js.map +1 -0
  67. package/dist/types/iam/subscription_status.d.ts +3 -0
  68. package/dist/types/iam/subscription_status.d.ts.map +1 -0
  69. package/dist/types/iam/subscription_status.js +10 -0
  70. package/dist/types/iam/subscription_status.js.map +1 -0
  71. package/dist/types/iam/subscription_tier.d.ts +3 -0
  72. package/dist/types/iam/subscription_tier.d.ts.map +1 -0
  73. package/dist/types/iam/subscription_tier.js +5 -0
  74. package/dist/types/iam/subscription_tier.js.map +1 -0
  75. package/dist/types/iam/user.d.ts +3 -42
  76. package/dist/types/iam/user.d.ts.map +1 -1
  77. package/dist/types/iam/user.js +0 -16
  78. package/dist/types/iam/user.js.map +1 -1
  79. package/dist/types/iam/user_role.d.ts +5 -0
  80. package/dist/types/iam/user_role.d.ts.map +1 -0
  81. package/dist/types/iam/user_role.js +16 -0
  82. package/dist/types/iam/user_role.js.map +1 -0
  83. package/dist/types/iam/user_status.d.ts +3 -0
  84. package/dist/types/iam/user_status.d.ts.map +1 -0
  85. package/dist/types/iam/user_status.js +10 -0
  86. package/dist/types/iam/user_status.js.map +1 -0
  87. package/dist/types/moderation/index.d.ts +4 -1
  88. package/dist/types/moderation/index.d.ts.map +1 -1
  89. package/dist/types/moderation/index.js +4 -1
  90. package/dist/types/moderation/index.js.map +1 -1
  91. package/dist/types/moderation/moderation.d.ts +5 -137
  92. package/dist/types/moderation/moderation.d.ts.map +1 -1
  93. package/dist/types/moderation/moderation.js +0 -37
  94. package/dist/types/moderation/moderation.js.map +1 -1
  95. package/dist/types/moderation/moderation_status.d.ts +3 -0
  96. package/dist/types/moderation/moderation_status.d.ts.map +1 -0
  97. package/dist/types/moderation/moderation_status.js +9 -0
  98. package/dist/types/moderation/moderation_status.js.map +1 -0
  99. package/dist/types/moderation/priority.d.ts +3 -0
  100. package/dist/types/moderation/priority.d.ts.map +1 -0
  101. package/dist/types/moderation/priority.js +5 -0
  102. package/dist/types/moderation/priority.js.map +1 -0
  103. package/dist/types/moderation/severity.d.ts +3 -0
  104. package/dist/types/moderation/severity.d.ts.map +1 -0
  105. package/dist/types/moderation/severity.js +5 -0
  106. package/dist/types/moderation/severity.js.map +1 -0
  107. package/dist/types/moderation/suggestion_category.d.ts +3 -0
  108. package/dist/types/moderation/suggestion_category.d.ts.map +1 -0
  109. package/dist/types/moderation/suggestion_category.js +11 -0
  110. package/dist/types/moderation/suggestion_category.js.map +1 -0
  111. package/dist/types/recipe/allergen.d.ts +3 -0
  112. package/dist/types/recipe/allergen.d.ts.map +1 -0
  113. package/dist/types/recipe/allergen.js +19 -0
  114. package/dist/types/recipe/allergen.js.map +1 -0
  115. package/dist/types/recipe/cuisine.d.ts +3 -0
  116. package/dist/types/recipe/cuisine.d.ts.map +1 -0
  117. package/dist/types/recipe/cuisine.js +39 -0
  118. package/dist/types/recipe/cuisine.js.map +1 -0
  119. package/dist/types/recipe/dietary_tag.d.ts +3 -0
  120. package/dist/types/recipe/dietary_tag.d.ts.map +1 -0
  121. package/dist/types/recipe/dietary_tag.js +21 -0
  122. package/dist/types/recipe/dietary_tag.js.map +1 -0
  123. package/dist/types/recipe/difficulty.d.ts +3 -0
  124. package/dist/types/recipe/difficulty.d.ts.map +1 -0
  125. package/dist/types/recipe/difficulty.js +10 -0
  126. package/dist/types/recipe/difficulty.js.map +1 -0
  127. package/dist/types/recipe/equipment.d.ts +0 -40
  128. package/dist/types/recipe/equipment.d.ts.map +1 -1
  129. package/dist/types/recipe/equipment.js +0 -12
  130. package/dist/types/recipe/equipment.js.map +1 -1
  131. package/dist/types/recipe/filters.d.ts +5 -30
  132. package/dist/types/recipe/filters.d.ts.map +1 -1
  133. package/dist/types/recipe/filters.js +0 -13
  134. package/dist/types/recipe/filters.js.map +1 -1
  135. package/dist/types/recipe/index.d.ts +10 -1
  136. package/dist/types/recipe/index.d.ts.map +1 -1
  137. package/dist/types/recipe/index.js +10 -1
  138. package/dist/types/recipe/index.js.map +1 -1
  139. package/dist/types/recipe/ingredient.d.ts +1 -169
  140. package/dist/types/recipe/ingredient.d.ts.map +1 -1
  141. package/dist/types/recipe/ingredient.js +0 -29
  142. package/dist/types/recipe/ingredient.js.map +1 -1
  143. package/dist/types/recipe/instruction.d.ts +2 -72
  144. package/dist/types/recipe/instruction.d.ts.map +1 -1
  145. package/dist/types/recipe/instruction.js +0 -13
  146. package/dist/types/recipe/instruction.js.map +1 -1
  147. package/dist/types/recipe/meal_type.d.ts +3 -0
  148. package/dist/types/recipe/meal_type.d.ts.map +1 -0
  149. package/dist/types/recipe/meal_type.js +14 -0
  150. package/dist/types/recipe/meal_type.js.map +1 -0
  151. package/dist/types/recipe/nutrition.d.ts +0 -36
  152. package/dist/types/recipe/nutrition.d.ts.map +1 -1
  153. package/dist/types/recipe/nutrition.js +0 -13
  154. package/dist/types/recipe/nutrition.js.map +1 -1
  155. package/dist/types/recipe/recipe.d.ts +14 -525
  156. package/dist/types/recipe/recipe.d.ts.map +1 -1
  157. package/dist/types/recipe/recipe.js +0 -80
  158. package/dist/types/recipe/recipe.js.map +1 -1
  159. package/dist/types/recipe/recipe_source.d.ts +3 -0
  160. package/dist/types/recipe/recipe_source.d.ts.map +1 -0
  161. package/dist/types/recipe/recipe_source.js +5 -0
  162. package/dist/types/recipe/recipe_source.js.map +1 -0
  163. package/dist/types/recipe/recipe_status.d.ts +3 -0
  164. package/dist/types/recipe/recipe_status.d.ts.map +1 -0
  165. package/dist/types/recipe/recipe_status.js +12 -0
  166. package/dist/types/recipe/recipe_status.js.map +1 -0
  167. package/dist/types/recipe/spiciness.d.ts +3 -0
  168. package/dist/types/recipe/spiciness.d.ts.map +1 -0
  169. package/dist/types/recipe/spiciness.js +5 -0
  170. package/dist/types/recipe/spiciness.js.map +1 -0
  171. package/dist/types/recipe/temperature.d.ts +1 -12
  172. package/dist/types/recipe/temperature.d.ts.map +1 -1
  173. package/dist/types/recipe/temperature.js +0 -7
  174. package/dist/types/recipe/temperature.js.map +1 -1
  175. package/dist/types/recipe/temperature_unit.d.ts +3 -0
  176. package/dist/types/recipe/temperature_unit.d.ts.map +1 -0
  177. package/dist/types/recipe/temperature_unit.js +5 -0
  178. package/dist/types/recipe/temperature_unit.js.map +1 -0
  179. package/dist/types/recipe/unit.d.ts +3 -0
  180. package/dist/types/recipe/unit.d.ts.map +1 -0
  181. package/dist/types/recipe/unit.js +51 -0
  182. package/dist/types/recipe/unit.js.map +1 -0
  183. package/dist/types/report/index.d.ts +4 -1
  184. package/dist/types/report/index.d.ts.map +1 -1
  185. package/dist/types/report/index.js +4 -1
  186. package/dist/types/report/index.js.map +1 -1
  187. package/dist/types/report/report.d.ts +5 -94
  188. package/dist/types/report/report.d.ts.map +1 -1
  189. package/dist/types/report/report.js +0 -29
  190. package/dist/types/report/report.js.map +1 -1
  191. package/dist/types/report/report_priority.d.ts +3 -0
  192. package/dist/types/report/report_priority.d.ts.map +1 -0
  193. package/dist/types/report/report_priority.js +10 -0
  194. package/dist/types/report/report_priority.js.map +1 -0
  195. package/dist/types/report/report_status.d.ts +3 -0
  196. package/dist/types/report/report_status.d.ts.map +1 -0
  197. package/dist/types/report/report_status.js +10 -0
  198. package/dist/types/report/report_status.js.map +1 -0
  199. package/dist/types/report/report_target_type.d.ts +3 -0
  200. package/dist/types/report/report_target_type.d.ts.map +1 -0
  201. package/dist/types/report/report_target_type.js +10 -0
  202. package/dist/types/report/report_target_type.js.map +1 -0
  203. package/dist/types/report/report_type.d.ts +3 -0
  204. package/dist/types/report/report_type.d.ts.map +1 -0
  205. package/dist/types/report/report_type.js +12 -0
  206. package/dist/types/report/report_type.js.map +1 -0
  207. package/package.json +3 -14
  208. package/src/index.ts +0 -1
  209. package/src/types/ai/recipe.ts +10 -47
  210. package/src/types/ai/translation.ts +1 -18
  211. package/src/types/core/api.ts +4 -4
  212. package/src/types/core/asset.ts +2 -14
  213. package/src/types/core/entity.ts +7 -0
  214. package/src/types/core/index.ts +5 -1
  215. package/src/types/core/{enums/locale.ts → locale.ts} +0 -3
  216. package/src/types/core/{enums/measurement_system.ts → measurement_system.ts} +0 -3
  217. package/src/types/core/settings.ts +9 -43
  218. package/src/types/core/{enums/theme.ts → theme.ts} +0 -3
  219. package/src/types/core/timestamp.ts +4 -0
  220. package/src/types/iam/index.ts +6 -1
  221. package/src/types/iam/profile.ts +2 -24
  222. package/src/types/iam/{enums/profile_status.ts → profile_status.ts} +0 -3
  223. package/src/types/iam/subscription.ts +4 -28
  224. package/src/types/iam/{enums/subscription_role.ts → subscription_role.ts} +0 -3
  225. package/src/types/iam/{enums/subscription_status.ts → subscription_status.ts} +0 -3
  226. package/src/types/iam/subscription_tier.ts +2 -0
  227. package/src/types/iam/user.ts +3 -22
  228. package/src/types/iam/user_role.ts +15 -0
  229. package/src/types/iam/{enums/user_status.ts → user_status.ts} +0 -3
  230. package/src/types/moderation/index.ts +4 -1
  231. package/src/types/moderation/moderation.ts +5 -50
  232. package/src/types/moderation/{enums/moderation_status.ts → moderation_status.ts} +0 -3
  233. package/src/types/moderation/{enums/priority.ts → priority.ts} +0 -3
  234. package/src/types/moderation/{enums/severity.ts → severity.ts} +0 -3
  235. package/src/types/moderation/suggestion_category.ts +8 -0
  236. package/src/types/recipe/{enums/allergen.ts → allergen.ts} +0 -3
  237. package/src/types/recipe/{enums/cuisine.ts → cuisine.ts} +0 -3
  238. package/src/types/recipe/{enums/dietary_tag.ts → dietary_tag.ts} +0 -3
  239. package/src/types/recipe/{enums/difficulty.ts → difficulty.ts} +0 -3
  240. package/src/types/recipe/equipment.ts +0 -18
  241. package/src/types/recipe/filters.ts +5 -24
  242. package/src/types/recipe/index.ts +10 -1
  243. package/src/types/recipe/ingredient.ts +1 -37
  244. package/src/types/recipe/instruction.ts +2 -19
  245. package/src/types/recipe/{enums/meal_type.ts → meal_type.ts} +0 -3
  246. package/src/types/recipe/nutrition.ts +0 -18
  247. package/src/types/recipe/recipe.ts +18 -118
  248. package/src/types/recipe/{enums/recipe_source.ts → recipe_source.ts} +0 -3
  249. package/src/types/recipe/{enums/recipe_status.ts → recipe_status.ts} +0 -3
  250. package/src/types/recipe/{enums/spiciness.ts → spiciness.ts} +0 -3
  251. package/src/types/recipe/temperature.ts +1 -7
  252. package/src/types/recipe/{enums/temperature_unit.ts → temperature_unit.ts} +0 -3
  253. package/src/types/recipe/{enums/unit.ts → unit.ts} +0 -3
  254. package/src/types/report/index.ts +4 -1
  255. package/src/types/report/report.ts +5 -40
  256. package/src/types/report/{enums/report_priority.ts → report_priority.ts} +0 -3
  257. package/src/types/report/{enums/report_status.ts → report_status.ts} +0 -3
  258. package/src/types/report/{enums/report_target_type.ts → report_target_type.ts} +0 -3
  259. package/src/types/report/{enums/report_type.ts → report_type.ts} +0 -3
  260. package/src/lib/ai/cost.ts +0 -82
  261. package/src/lib/ai/embedding.ts +0 -49
  262. package/src/lib/ai/errors.ts +0 -43
  263. package/src/lib/ai/flow.ts +0 -134
  264. package/src/lib/ai/genkit.ts +0 -39
  265. package/src/lib/ai/image.ts +0 -27
  266. package/src/lib/ai/index.ts +0 -6
  267. package/src/lib/api.ts +0 -103
  268. package/src/lib/firebase.ts +0 -5
  269. package/src/lib/iam.ts +0 -37
  270. package/src/lib/index.ts +0 -7
  271. package/src/lib/storage.ts +0 -60
  272. package/src/lib/usage/index.ts +0 -1
  273. package/src/lib/usage/usage.service.ts +0 -192
  274. package/src/lib/utils/index.ts +0 -2
  275. package/src/lib/utils/sanitize.ts +0 -38
  276. package/src/lib/utils/slug.ts +0 -33
  277. package/src/types/core/enums/image_type.ts +0 -5
  278. package/src/types/core/enums/index.ts +0 -5
  279. package/src/types/core/enums/url_type.ts +0 -13
  280. package/src/types/iam/enums/index.ts +0 -6
  281. package/src/types/iam/enums/subscription_tier.ts +0 -4
  282. package/src/types/iam/enums/user_role.ts +0 -11
  283. package/src/types/moderation/enums/index.ts +0 -4
  284. package/src/types/moderation/enums/suggestion_category.ts +0 -11
  285. package/src/types/recipe/enums/index.ts +0 -10
  286. package/src/types/report/enums/index.ts +0 -4
@@ -1,43 +0,0 @@
1
- export const AI_ERROR_CODE_VALUES = [
2
- "hallucination_detected",
3
- "image_unclear",
4
- "impossible_constraint",
5
- "invalid_input",
6
- "invalid_url",
7
- "low_confidence",
8
- "model_error",
9
- "no_recipe_found",
10
- "nonsense_input",
11
- "rate_limit",
12
- "timeout",
13
- "translation_failed",
14
- "unsafe_input",
15
- "url_not_accessible",
16
- ] as const;
17
-
18
- export type AIErrorCode = (typeof AI_ERROR_CODE_VALUES)[number];
19
-
20
- export class AIFlowError extends Error {
21
- public readonly code: AIErrorCode;
22
- public readonly suggestions?: string[];
23
-
24
- constructor(code: AIErrorCode, message: string, suggestions?: string[]) {
25
- super(message);
26
- this.name = "AIFlowError";
27
- this.code = code;
28
- this.suggestions = suggestions;
29
-
30
- if (Error.captureStackTrace) {
31
- Error.captureStackTrace(this, AIFlowError);
32
- }
33
- }
34
-
35
- toJSON() {
36
- return {
37
- name: this.name,
38
- code: this.code,
39
- message: this.message,
40
- suggestions: this.suggestions,
41
- };
42
- }
43
- }
@@ -1,134 +0,0 @@
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
- // Lazy initialization - don't access ai until the flow is actually called
31
- let initialized = false;
32
- let FlowInputSchema: any;
33
- let FlowOutputSchema: any;
34
- let prompt: any;
35
- let flow: any;
36
-
37
- const initializeFlow = () => {
38
- if (!initialized) {
39
- if (!ai) {
40
- throw new Error(
41
- "Genkit AI not initialized. Make sure Firebase Functions onInit has been called.",
42
- );
43
- }
44
- FlowInputSchema = ai.defineSchema(`${name}InputSchema`, inputSchema);
45
- FlowOutputSchema = ai.defineSchema(`${name}OutputSchema`, outputSchema);
46
- prompt = ai.prompt<
47
- typeof FlowInputSchema,
48
- typeof FlowOutputSchema,
49
- z.ZodTypeAny
50
- >(name);
51
-
52
- flow = ai.defineFlow(
53
- {
54
- name: name,
55
- inputSchema: inputSchema,
56
- outputSchema: outputSchema,
57
- },
58
- async (input: T) => {
59
- const response = await prompt(input);
60
- logAIUsage({
61
- type: "multimodal",
62
- flowName: name,
63
- model,
64
- inputTokens: response.usage?.inputTokens || 0,
65
- outputTokens: response.usage?.outputTokens || 0,
66
- });
67
- return handler(input, response.output);
68
- },
69
- );
70
- initialized = true;
71
- }
72
- return flow;
73
- };
74
-
75
- // Return a proxy function that initializes on first call
76
- return (async (input: T) => {
77
- const actualFlow = initializeFlow();
78
- return actualFlow(input);
79
- }) as ReturnType<typeof ai.defineFlow>;
80
- };
81
-
82
- export const createImagenFlow = <T>(
83
- name: string,
84
- inputSchema: z.ZodSchema<T>,
85
- handler: ImageFlowHandler<T>,
86
- model: AIModelConfig = AI_MODELS.Imagen4Fast,
87
- ) => {
88
- // Lazy initialization - don't access ai until the flow is actually called
89
- let initialized = false;
90
- let flow: any;
91
-
92
- const initializeFlow = () => {
93
- if (!initialized) {
94
- if (!ai) {
95
- throw new Error(
96
- "Genkit AI not initialized. Make sure Firebase Functions onInit has been called.",
97
- );
98
- }
99
- flow = ai.defineFlow(
100
- {
101
- name: name,
102
- inputSchema: inputSchema,
103
- outputSchema: ImagenOutputSchema,
104
- },
105
- async (input: T) => {
106
- const imageData = await handler(input, name);
107
- const uploadPromises = imageData.map(({ base64Data, storagePath }) =>
108
- uploadImageToStorage(base64Data, storagePath).catch((err) => {
109
- throw err;
110
- }),
111
- );
112
- const uploadedImages = await Promise.all(uploadPromises);
113
- logAIUsage({
114
- type: "image",
115
- flowName: name,
116
- model,
117
- imageCount: imageData.length,
118
- });
119
- return {
120
- images: uploadedImages,
121
- };
122
- },
123
- );
124
- initialized = true;
125
- }
126
- return flow;
127
- };
128
-
129
- // Return a proxy function that initializes on first call
130
- return (async (input: T) => {
131
- const actualFlow = initializeFlow();
132
- return actualFlow(input);
133
- }) as ReturnType<typeof ai.defineFlow>;
134
- };
@@ -1,39 +0,0 @@
1
- import { genkit, EmbedderReference, Genkit } from "genkit";
2
- import { vertexAI } from "@genkit-ai/google-genai";
3
- import { defineFirestoreRetriever } from "@genkit-ai/firebase";
4
- import { onInit } from "firebase-functions/v2/core";
5
- import { firestore } from "../firebase";
6
-
7
- const projectId =
8
- process.env.GCLOUD_PROJECT || process.env.GCP_PROJECT || "canary-cravery";
9
-
10
- // Declare but don't initialize yet - will be initialized in onInit
11
- export let ai: Genkit;
12
- export let geminiEmbedding001: EmbedderReference;
13
- export let recipesRetriever: any;
14
-
15
- // Initialize during Firebase Functions init phase, not at module load
16
- onInit(() => {
17
- ai = genkit({
18
- plugins: [
19
- vertexAI({
20
- location: "us-central1",
21
- projectId,
22
- }),
23
- ],
24
- });
25
-
26
- geminiEmbedding001 = vertexAI.embedder("gemini-embedding-001");
27
-
28
- // Initialize retriever after ai is set up
29
- recipesRetriever = defineFirestoreRetriever(ai, {
30
- name: "recipesRetriever",
31
- firestore,
32
- collection: "recipes",
33
- contentField: "searchTerms",
34
- vectorField: "titleEmbedding",
35
- embedder: geminiEmbedding001,
36
- distanceMeasure: "COSINE",
37
- distanceResultField: "distance",
38
- });
39
- });
@@ -1,27 +0,0 @@
1
- import type { GenerateResponse } from "genkit";
2
- import { AIFlowError } from "./errors";
3
-
4
- export interface ImageData {
5
- base64: string;
6
- contentType: string;
7
- }
8
-
9
- export const getImageData = (response: GenerateResponse): ImageData => {
10
- const media = response.media;
11
-
12
- if (!media?.url) {
13
- throw new AIFlowError("model_error", "No image data in response", [
14
- "Try with a different recipe description",
15
- ]);
16
- }
17
-
18
- const url = media.url;
19
- const contentType = media.contentType ?? "image/png";
20
-
21
- if (url.startsWith("data:")) {
22
- const base64Part = url.split(",")[1];
23
- if (base64Part) return { base64: base64Part, contentType };
24
- }
25
-
26
- return { base64: url, contentType };
27
- };
@@ -1,6 +0,0 @@
1
- export * from "./cost";
2
- export * from "./embedding";
3
- export * from "./errors";
4
- export * from "./flow";
5
- export * from "./genkit";
6
- export * from "./image";
package/src/lib/api.ts DELETED
@@ -1,103 +0,0 @@
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,5 +0,0 @@
1
- import { getFirestore } from "firebase-admin/firestore";
2
- import { getDatabase } from "firebase-admin/database";
3
-
4
- export const firestore = getFirestore();
5
- export const rtdb = getDatabase();
package/src/lib/iam.ts DELETED
@@ -1,37 +0,0 @@
1
- import * as admin from "firebase-admin";
2
- import { UserRole } from "../types";
3
-
4
- const getUserRole = async (uid: string): Promise<UserRole> => {
5
- let role: UserRole = "guest";
6
- const userDoc = await admin.firestore().collection("users").doc(uid).get();
7
- if (userDoc.exists) {
8
- const userData = userDoc.data();
9
- role = userData?.role || "user";
10
- }
11
- return role;
12
- };
13
-
14
- export const isAdmin = async (uid: string): Promise<boolean> => {
15
- const role = await getUserRole(uid);
16
- return role === "admin";
17
- };
18
-
19
- export const isModerator = async (uid: string): Promise<boolean> => {
20
- const role = await getUserRole(uid);
21
- return role === "moderator";
22
- };
23
-
24
- export const isModeratorOrAbove = async (uid: string): Promise<boolean> => {
25
- const role = await getUserRole(uid);
26
- return role === "admin" || role === "moderator";
27
- };
28
-
29
- export const isTranslator = async (uid: string): Promise<boolean> => {
30
- const role = await getUserRole(uid);
31
- return role === "translator";
32
- };
33
-
34
- export const isTranslatorOrAbove = async (uid: string): Promise<boolean> => {
35
- const role = await getUserRole(uid);
36
- return role === "admin" || role === "moderator" || role === "translator";
37
- };
package/src/lib/index.ts DELETED
@@ -1,7 +0,0 @@
1
- export * from "./ai";
2
- export * from "./api";
3
- export * from "./firebase";
4
- export * from "./iam";
5
- export * from "./storage";
6
- export * from "./usage";
7
- export * from "./utils";
@@ -1,60 +0,0 @@
1
- import { getStorage } from "firebase-admin/storage";
2
- import { ValidationError } from "../types";
3
-
4
- export interface UploadImageResult {
5
- url: string;
6
- path: string;
7
- }
8
-
9
- function validateStoragePath(path: string): void {
10
- if (!path || path.length === 0) {
11
- throw new ValidationError("Storage path cannot be empty");
12
- }
13
- if (path.includes("..")) {
14
- throw new ValidationError("Storage path cannot contain '..'", { path });
15
- }
16
- if (path.startsWith("/")) {
17
- throw new ValidationError("Storage path cannot start with '/'", { path });
18
- }
19
- if (path.includes("//")) {
20
- throw new ValidationError("Storage path cannot contain '//'", { path });
21
- }
22
- }
23
-
24
- export const uploadImageToStorage = async (
25
- base64Data: string,
26
- path: string,
27
- contentType: string = "image/png",
28
- ): Promise<UploadImageResult> => {
29
- validateStoragePath(path);
30
-
31
- try {
32
- const bucket = getStorage().bucket();
33
- const buffer = Buffer.from(base64Data, "base64");
34
- const file = bucket.file(path);
35
-
36
- const fileExists = await file.exists();
37
- if (fileExists[0]) {
38
- await file.delete();
39
- }
40
-
41
- await file.save(buffer, {
42
- metadata: {
43
- contentType,
44
- cacheControl: "public",
45
- },
46
- resumable: false,
47
- });
48
-
49
- const publicUrl = `https://storage.googleapis.com/${bucket.name}/${path}`;
50
-
51
- return {
52
- url: publicUrl,
53
- path,
54
- };
55
- } catch {
56
- const uploadError = new Error("Image upload failed");
57
- uploadError.name = "UPLOAD_FAILED";
58
- throw uploadError;
59
- }
60
- };
@@ -1 +0,0 @@
1
- export * from "./usage.service";
@@ -1,192 +0,0 @@
1
- import { Database } from "firebase-admin/database";
2
- import {
3
- DailyLimitError,
4
- MonthlyLimitError,
5
- SubscriptionTier,
6
- } from "../../types";
7
- import { LIMITS, type UsageType } from "../../config/limits";
8
-
9
- interface UsageCounter {
10
- count: number;
11
- date: string;
12
- }
13
-
14
- interface MonthlyCounter {
15
- count: number;
16
- month: string;
17
- }
18
-
19
- export interface UsageCheckResult {
20
- allowed: boolean;
21
- dailyRemaining: number;
22
- monthlyRemaining: number;
23
- error?: DailyLimitError | MonthlyLimitError;
24
- }
25
-
26
- export class UsageService {
27
- constructor(private readonly database: Database) {}
28
-
29
- private getDateString(): string {
30
- return new Date().toISOString().split("T")[0];
31
- }
32
-
33
- private getMonthString(): string {
34
- return new Date().toISOString().slice(0, 7);
35
- }
36
-
37
- private getDailyRef(userId: string, type: UsageType) {
38
- return this.database.ref(`usage/${userId}/daily/${type}`);
39
- }
40
-
41
- private getMonthlyRef(userId: string, type: UsageType) {
42
- return this.database.ref(`usage/${userId}/monthly/${type}`);
43
- }
44
-
45
- async checkLimit(
46
- userId: string,
47
- type: UsageType,
48
- tier: SubscriptionTier,
49
- ): Promise<UsageCheckResult> {
50
- const limits = LIMITS[tier];
51
- const dailyLimit = limits.daily[type];
52
- const monthlyLimit = limits.monthly[type];
53
-
54
- const today = this.getDateString();
55
- const month = this.getMonthString();
56
-
57
- const [dailySnapshot, monthlySnapshot] = await Promise.all([
58
- this.getDailyRef(userId, type).get(),
59
- this.getMonthlyRef(userId, type).get(),
60
- ]);
61
-
62
- let dailyCount = 0;
63
- let monthlyCount = 0;
64
-
65
- if (dailySnapshot.exists()) {
66
- const data = dailySnapshot.val() as UsageCounter;
67
- if (data.date === today) {
68
- dailyCount = data.count;
69
- }
70
- }
71
-
72
- if (monthlySnapshot.exists()) {
73
- const data = monthlySnapshot.val() as MonthlyCounter;
74
- if (data.month === month) {
75
- monthlyCount = data.count;
76
- }
77
- }
78
-
79
- const dailyRemaining =
80
- dailyLimit === -1 ? Infinity : dailyLimit - dailyCount;
81
- const monthlyRemaining =
82
- monthlyLimit === -1 ? Infinity : monthlyLimit - monthlyCount;
83
-
84
- if (dailyLimit !== -1 && dailyCount >= dailyLimit) {
85
- return {
86
- allowed: false,
87
- dailyRemaining: 0,
88
- monthlyRemaining,
89
- error: new DailyLimitError(
90
- `Daily ${type} limit reached (${dailyLimit})`,
91
- { type, limit: dailyLimit, count: dailyCount },
92
- ),
93
- };
94
- }
95
-
96
- if (monthlyLimit !== -1 && monthlyCount >= monthlyLimit) {
97
- return {
98
- allowed: false,
99
- dailyRemaining,
100
- monthlyRemaining: 0,
101
- error: new MonthlyLimitError(
102
- `Monthly ${type} limit reached (${monthlyLimit})`,
103
- { type, limit: monthlyLimit, count: monthlyCount },
104
- ),
105
- };
106
- }
107
-
108
- return {
109
- allowed: true,
110
- dailyRemaining,
111
- monthlyRemaining,
112
- };
113
- }
114
-
115
- async increment(userId: string, type: UsageType): Promise<void> {
116
- const today = this.getDateString();
117
- const month = this.getMonthString();
118
-
119
- const dailyRef = this.getDailyRef(userId, type);
120
- const monthlyRef = this.getMonthlyRef(userId, type);
121
- const lifetimeRef = this.database.ref(
122
- `usage/${userId}/lifetime/total${type.charAt(0).toUpperCase() + type.slice(1)}s`,
123
- );
124
-
125
- await Promise.all([
126
- dailyRef.transaction((current: UsageCounter | null) => {
127
- if (!current || current.date !== today) {
128
- return { count: 1, date: today };
129
- }
130
- return { count: current.count + 1, date: today };
131
- }),
132
- monthlyRef.transaction((current: MonthlyCounter | null) => {
133
- if (!current || current.month !== month) {
134
- return { count: 1, month };
135
- }
136
- return { count: current.count + 1, month };
137
- }),
138
- lifetimeRef.transaction((current: number | null) => {
139
- return (current || 0) + 1;
140
- }),
141
- ]);
142
- }
143
-
144
- async getUsage(
145
- userId: string,
146
- type: UsageType,
147
- ): Promise<{ daily: number; monthly: number; lifetime: number }> {
148
- const today = this.getDateString();
149
- const month = this.getMonthString();
150
-
151
- const [dailySnapshot, monthlySnapshot, lifetimeSnapshot] =
152
- await Promise.all([
153
- this.getDailyRef(userId, type).get(),
154
- this.getMonthlyRef(userId, type).get(),
155
- this.database
156
- .ref(
157
- `usage/${userId}/lifetime/total${type.charAt(0).toUpperCase() + type.slice(1)}s`,
158
- )
159
- .get(),
160
- ]);
161
-
162
- let daily = 0;
163
- let monthly = 0;
164
- const lifetime = lifetimeSnapshot.exists() ? lifetimeSnapshot.val() : 0;
165
-
166
- if (dailySnapshot.exists()) {
167
- const data = dailySnapshot.val() as UsageCounter;
168
- if (data.date === today) {
169
- daily = data.count;
170
- }
171
- }
172
-
173
- if (monthlySnapshot.exists()) {
174
- const data = monthlySnapshot.val() as MonthlyCounter;
175
- if (data.month === month) {
176
- monthly = data.count;
177
- }
178
- }
179
-
180
- return { daily, monthly, lifetime };
181
- }
182
-
183
- async resetDaily(userId: string, type: UsageType): Promise<void> {
184
- const today = this.getDateString();
185
- await this.getDailyRef(userId, type).set({ count: 0, date: today });
186
- }
187
-
188
- async resetMonthly(userId: string, type: UsageType): Promise<void> {
189
- const month = this.getMonthString();
190
- await this.getMonthlyRef(userId, type).set({ count: 0, month });
191
- }
192
- }
@@ -1,2 +0,0 @@
1
- export * from "./sanitize";
2
- export * from "./slug";