@m5kdev/backend 0.8.5 → 0.8.7

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.
@@ -35,6 +35,8 @@ type AIServiceGenerateTextParams = Omit<GenerateTextParams, "model" | "prompt" |
35
35
  model: string;
36
36
  removeMDash?: boolean;
37
37
  ctx?: AIServiceActorContext;
38
+ retryAttempts?: number;
39
+ retryModels?: string[];
38
40
  };
39
41
  type AIServiceGenerateObjectParams<T extends ZodType> = Omit<GenerateTextParams, "model" | "prompt" | "messages" | "output"> & GenerateTextInput & {
40
42
  model: string;
@@ -42,6 +44,15 @@ type AIServiceGenerateObjectParams<T extends ZodType> = Omit<GenerateTextParams,
42
44
  repairAttempts?: number;
43
45
  repairModel?: string;
44
46
  ctx?: AIServiceActorContext;
47
+ retryAttempts?: number;
48
+ retryModels?: string[];
49
+ };
50
+ type AIServiceOptions = {
51
+ retryAttempts?: number;
52
+ retryModels?: string[];
53
+ repairAttempts?: number;
54
+ repairModel?: string;
55
+ removeMDash?: boolean;
45
56
  };
46
57
  declare class AIService<MastraInstance extends Mastra> extends BaseService<{
47
58
  aiUsage?: AiUsageRepository;
@@ -54,6 +65,7 @@ declare class AIService<MastraInstance extends Mastra> extends BaseService<{
54
65
  mastra?: MastraInstance;
55
66
  openrouter?: OpenRouterProvider;
56
67
  replicate?: Replicate;
68
+ options?: AIServiceOptions;
57
69
  constructor(repositories: {
58
70
  aiUsage?: AiUsageRepository;
59
71
  }, services: {
@@ -62,7 +74,7 @@ declare class AIService<MastraInstance extends Mastra> extends BaseService<{
62
74
  mastra?: MastraInstance;
63
75
  openrouter?: OpenRouterProvider;
64
76
  replicate?: Replicate;
65
- });
77
+ }, options?: AIServiceOptions);
66
78
  getMastra(): MastraInstance;
67
79
  prepareModel(model: string): ReturnType<OpenRouterProvider["chat"]>;
68
80
  prepareEmbeddingModel(model: string): ReturnType<OpenRouterProvider["textEmbeddingModel"]>;
@@ -13,11 +13,13 @@ var AIService = class extends BaseService {
13
13
  mastra;
14
14
  openrouter;
15
15
  replicate;
16
- constructor(repositories, services, libs) {
16
+ options;
17
+ constructor(repositories, services, libs, options) {
17
18
  super(repositories, services);
18
19
  this.mastra = libs.mastra;
19
20
  this.openrouter = libs.openrouter;
20
21
  this.replicate = libs.replicate;
22
+ this.options = options;
21
23
  }
22
24
  getMastra() {
23
25
  if (!this.mastra) throw new Error("Mastra is not available");
@@ -134,8 +136,8 @@ var AIService = class extends BaseService {
134
136
  }
135
137
  async generateText(params) {
136
138
  return this.throwableAsync(async () => {
137
- const { removeMDash = true, model, prompt, messages, ctx, ...rest } = params;
138
- const result = await generateText(messages ? {
139
+ const { removeMDash = this.options?.removeMDash ?? true, model, prompt, messages, ctx, retryAttempts = this.options?.retryAttempts ?? 0, retryModels = this.options?.retryModels ?? [], ...rest } = params;
140
+ const request = messages ? {
139
141
  ...rest,
140
142
  model: this.prepareModel(model),
141
143
  messages
@@ -143,26 +145,48 @@ var AIService = class extends BaseService {
143
145
  ...rest,
144
146
  model: this.prepareModel(model),
145
147
  prompt
146
- });
147
- if (this.repository.aiUsage) {
148
- const createUsageResult = await this.repository.aiUsage.create({
149
- userId: ctx?.actor?.userId,
148
+ };
149
+ try {
150
+ const result = await generateText(request);
151
+ if (this.repository.aiUsage) {
152
+ const createUsageResult = await this.repository.aiUsage.create({
153
+ userId: ctx?.actor?.userId,
154
+ model,
155
+ provider: "openrouter",
156
+ feature: "generateText",
157
+ traceId: result.providerMetadata?.openrouter?.traceId?.toString(),
158
+ inputTokens: result.usage.inputTokens,
159
+ outputTokens: result.usage.outputTokens,
160
+ totalTokens: result.usage.totalTokens,
161
+ cost: (result?.providerMetadata?.openrouter?.usage)?.cost ?? 0
162
+ });
163
+ if (createUsageResult.isErr()) return err(createUsageResult.error);
164
+ }
165
+ return ok(removeMDash ? result.text.replace(/\u2013|\u2014/g, "-") : result.text);
166
+ } catch (error) {
167
+ if (retryAttempts <= 0) throw error;
168
+ this.logger.warn(`generateText failed, retrying (${retryAttempts} attempts left)`, {
150
169
  model,
151
- provider: "openrouter",
152
- feature: "generateText",
153
- traceId: result.providerMetadata?.openrouter?.traceId?.toString(),
154
- inputTokens: result.usage.inputTokens,
155
- outputTokens: result.usage.outputTokens,
156
- totalTokens: result.usage.totalTokens,
157
- cost: (result?.providerMetadata?.openrouter?.usage)?.cost ?? 0
170
+ error
171
+ });
172
+ const delay = Math.min(1e3 * 2 ** ((this.options?.retryAttempts ?? 3) - retryAttempts), 1e4);
173
+ await new Promise((resolve) => setTimeout(resolve, delay));
174
+ const nextModel = retryModels?.[0] ?? model;
175
+ const nextRetryModels = retryModels ? [...retryModels.slice(1), model] : void 0;
176
+ return this.generateText({
177
+ ...rest,
178
+ ...messages ? { messages } : { prompt },
179
+ model: nextModel,
180
+ removeMDash,
181
+ ctx,
182
+ retryAttempts: retryAttempts - 1,
183
+ retryModels: nextRetryModels
158
184
  });
159
- if (createUsageResult.isErr()) return err(createUsageResult.error);
160
185
  }
161
- return ok(removeMDash ? result.text.replace(/\u2013|\u2014/g, "-") : result.text);
162
186
  });
163
187
  }
164
188
  async generateObject(params) {
165
- const { model, schema, prompt, messages, repairAttempts = 0, repairModel, ctx, ...rest } = params;
189
+ const { model, schema, prompt, messages, repairAttempts = this.options?.repairAttempts ?? 0, repairModel = this.options?.repairModel ?? model, ctx, retryAttempts = this.options?.retryAttempts ?? 0, retryModels = this.options?.retryModels ?? [], ...rest } = params;
166
190
  const request = messages ? {
167
191
  ...rest,
168
192
  model: this.prepareModel(model),
@@ -226,7 +250,26 @@ var AIService = class extends BaseService {
226
250
  }
227
251
  return this.error("PARSE_ERROR", "AI: Agent object failed without text", { cause: error });
228
252
  }
229
- return this.error("BAD_REQUEST", "AI: Provided failed to generate object", { cause: error });
253
+ if (retryAttempts <= 0) return this.error("BAD_REQUEST", "AI: Provider failed to generate object", { cause: error });
254
+ this.logger.warn(`generateObject failed, retrying (${retryAttempts} attempts left)`, {
255
+ model,
256
+ error
257
+ });
258
+ const delay = Math.min(1e3 * 2 ** ((this.options?.retryAttempts ?? 3) - retryAttempts), 1e4);
259
+ await new Promise((resolve) => setTimeout(resolve, delay));
260
+ const nextModel = retryModels?.[0] ?? model;
261
+ const nextRetryModels = retryModels ? [...retryModels.slice(1), model] : void 0;
262
+ return this.generateObject({
263
+ ...rest,
264
+ ...messages ? { messages } : { prompt },
265
+ model: nextModel,
266
+ schema,
267
+ repairAttempts,
268
+ repairModel,
269
+ ctx,
270
+ retryAttempts: retryAttempts - 1,
271
+ retryModels: nextRetryModels
272
+ });
230
273
  }
231
274
  }
232
275
  async generateReplicate(model, options) {
@@ -1 +1 @@
1
- {"version":3,"file":"ai.service.mjs","names":[],"sources":["../../../../src/modules/ai/ai.service.ts"],"sourcesContent":["import { OPENAI_TEXT_EMBEDDING_3_SMALL } from \"@m5kdev/commons/modules/ai/ai.constants\";\r\nimport { arrayToPseudoXML } from \"@m5kdev/commons/modules/ai/ai.utils\";\r\nimport type { Mastra } from \"@mastra/core\";\r\nimport { RequestContext } from \"@mastra/core/request-context\";\r\nimport type { FullOutput, MastraModelOutput } from \"@mastra/core/stream\";\r\nimport { MDocument } from \"@mastra/rag\";\r\nimport type { OpenRouterProvider } from \"@openrouter/ai-sdk-provider\";\r\nimport {\r\n embed,\r\n embedMany,\r\n generateText,\r\n type ModelMessage,\r\n NoObjectGeneratedError,\r\n Output,\r\n} from \"ai\";\r\nimport { jsonrepair } from \"jsonrepair\";\r\nimport { err, ok } from \"neverthrow\";\r\nimport type Replicate from \"replicate\";\r\nimport type { ZodType, z } from \"zod\";\r\nimport type { RequiredServiceActor } from \"../base/base.actor\";\r\nimport type { ServerResultAsync } from \"../base/base.dto\";\r\nimport { BaseService } from \"../base/base.service\";\r\nimport { repairJsonPrompt } from \"./ai.prompts\";\r\nimport type { AiUsageRepository, AiUsageRow } from \"./ai.repository\";\r\nimport type { IdeogramV3GenerateInput, IdeogramV3GenerateOutput } from \"./ideogram/ideogram.dto\";\r\nimport type { IdeogramService } from \"./ideogram/ideogram.service\";\r\n\r\ntype MastraAgent = ReturnType<Mastra[\"getAgent\"]>;\r\ntype MastraAgentGenerateOptions = Parameters<MastraAgent[\"generate\"]>[1];\r\ntype MessageListInput = { role: \"user\" | \"assistant\" | \"system\"; content: string }[];\r\ntype GenerateTextParams = Parameters<typeof generateText>[0];\r\ntype GenerateTextInput =\r\n | { prompt: string | ModelMessage[]; messages?: never }\r\n | { messages: ModelMessage[]; prompt?: never };\r\ntype AIServiceActorContext = { actor: RequiredServiceActor<\"user\"> };\r\ntype AIServiceGenerateTextParams = Omit<GenerateTextParams, \"model\" | \"prompt\" | \"messages\"> &\r\n GenerateTextInput & {\r\n model: string;\r\n removeMDash?: boolean;\r\n ctx?: AIServiceActorContext;\r\n };\r\ntype AIServiceGenerateObjectParams<T extends ZodType> = Omit<\r\n GenerateTextParams,\r\n \"model\" | \"prompt\" | \"messages\" | \"output\"\r\n> &\r\n GenerateTextInput & {\r\n model: string;\r\n schema: T;\r\n repairAttempts?: number;\r\n repairModel?: string;\r\n ctx?: AIServiceActorContext;\r\n };\r\n\r\nexport class AIService<MastraInstance extends Mastra> extends BaseService<\r\n { aiUsage?: AiUsageRepository },\r\n { ideogram?: IdeogramService }\r\n> {\r\n helpers = {\r\n arrayToPseudoXML,\r\n };\r\n\r\n mastra?: MastraInstance;\r\n openrouter?: OpenRouterProvider;\r\n replicate?: Replicate;\r\n\r\n constructor(\r\n repositories: { aiUsage?: AiUsageRepository },\r\n services: { ideogram?: IdeogramService },\r\n libs: { mastra?: MastraInstance; openrouter?: OpenRouterProvider; replicate?: Replicate }\r\n ) {\r\n super(repositories, services);\r\n this.mastra = libs.mastra;\r\n this.openrouter = libs.openrouter;\r\n this.replicate = libs.replicate;\r\n }\r\n\r\n getMastra(): MastraInstance {\r\n if (!this.mastra) {\r\n throw new Error(\"Mastra is not available\");\r\n }\r\n return this.mastra;\r\n }\r\n\r\n prepareModel(model: string): ReturnType<OpenRouterProvider[\"chat\"]> {\r\n if (!this.openrouter) {\r\n throw new Error(\"OpenRouter is not configured\");\r\n }\r\n return this.openrouter.chat(model, {\r\n usage: {\r\n include: true,\r\n },\r\n });\r\n }\r\n\r\n prepareEmbeddingModel(model: string): ReturnType<OpenRouterProvider[\"textEmbeddingModel\"]> {\r\n if (!this.openrouter) {\r\n throw new Error(\"OpenRouter is not configured\");\r\n }\r\n const openrouter = this.openrouter as OpenRouterProvider & {\r\n embeddingModel?: (modelId: string) => unknown;\r\n };\r\n return (openrouter.embeddingModel?.(model) ??\r\n openrouter.textEmbeddingModel(model)) as ReturnType<OpenRouterProvider[\"textEmbeddingModel\"]>;\r\n }\r\n\r\n async agentUse(\r\n agent: string,\r\n options: MastraAgentGenerateOptions & { prompt?: string; messages?: MessageListInput },\r\n ctx?: AIServiceActorContext & { model?: string }\r\n ): ServerResultAsync<Awaited<ReturnType<MastraModelOutput<any>[\"getFullOutput\"]>>> {\r\n return this.throwableAsync(async () => {\r\n this.logger.info(\"AGENT USE\");\r\n const { prompt, messages, ...rest } = options;\r\n const payload = messages || prompt;\r\n if (!payload) return this.error(\"BAD_REQUEST\", \"No prompt or messages provided\");\r\n const requestContext = options.requestContext ?? new RequestContext();\r\n\r\n if (ctx?.actor) {\r\n requestContext.set(\"userId\", ctx.actor.userId);\r\n }\r\n if (ctx?.model) {\r\n requestContext.set(\"model\", ctx.model);\r\n }\r\n const mAgent = this.getMastra().getAgent(agent);\r\n\r\n const result = await mAgent.generate(payload as any, {\r\n ...rest,\r\n requestContext: rest.requestContext ?? requestContext,\r\n });\r\n this.logger.info(\"AGENT USE DONE\");\r\n if (this.repository.aiUsage) {\r\n const createUsageResult = await this.repository.aiUsage.create({\r\n userId: ctx?.actor?.userId,\r\n model: ctx?.model ?? \"unknown\",\r\n provider: \"openrouter\",\r\n feature: agent,\r\n traceId: result.traceId,\r\n inputTokens: result.usage.inputTokens,\r\n outputTokens: result.usage.outputTokens,\r\n totalTokens: result.usage.totalTokens,\r\n cost: (result?.providerMetadata?.openrouter?.usage as any)?.cost ?? 0,\r\n });\r\n if (createUsageResult.isErr()) return err(createUsageResult.error);\r\n }\r\n this.logger.info(\"AGENT USE CREATED USAGE\");\r\n return ok(result);\r\n });\r\n }\r\n\r\n async agentText(\r\n agent: string,\r\n options: MastraAgentGenerateOptions & { prompt?: string; messages?: MessageListInput },\r\n ctx?: AIServiceActorContext & { model?: string }\r\n ): ServerResultAsync<string> {\r\n const result = await this.agentUse(agent, options, ctx);\r\n if (result.isErr())\r\n return this.error(\"SERVICE_UNAVAILABLE\", \"AI: Agent text failed\", { cause: result.error });\r\n return ok(result.value.text);\r\n }\r\n\r\n async agentTextResult(\r\n agent: string,\r\n options: MastraAgentGenerateOptions & { prompt?: string; messages?: MessageListInput },\r\n ctx?: AIServiceActorContext & { model?: string }\r\n ): ServerResultAsync<FullOutput<any>> {\r\n const result = await this.agentUse(agent, options, ctx);\r\n if (result.isErr()) return err(result.error);\r\n return ok(result.value);\r\n }\r\n\r\n async agentObject<T extends ZodType<any>>(\r\n agent: string,\r\n options: MastraAgentGenerateOptions & {\r\n schema: T;\r\n prompt?: string;\r\n messages?: MessageListInput;\r\n },\r\n ctx?: AIServiceActorContext & { model?: string }\r\n ): ServerResultAsync<z.infer<T>> {\r\n const { schema, ...rest } = options;\r\n const result = await this.agentUse(agent, { ...rest, structuredOutput: { schema } }, ctx);\r\n if (result.isErr())\r\n return this.error(\"SERVICE_UNAVAILABLE\", \"AI: Agent object failed\", { cause: result.error });\r\n return ok(result.value.object as z.infer<T>);\r\n }\r\n\r\n async agentObjectResult<T extends ZodType<any>>(\r\n agent: string,\r\n options: MastraAgentGenerateOptions & {\r\n schema: T;\r\n prompt?: string;\r\n messages?: MessageListInput;\r\n },\r\n ctx?: AIServiceActorContext & { model?: string }\r\n ): ServerResultAsync<FullOutput<any> & { object: z.infer<T> }> {\r\n this.logger.info(\"AGENT OBJECT RESULT\");\r\n const { schema, ...rest } = options;\r\n const result = await this.agentUse(agent, { ...rest, structuredOutput: { schema } }, ctx);\r\n if (result.isErr()) return err(result.error);\r\n this.logger.info(\"AGENT OBJECT RESULT DONE\");\r\n return ok({ ...result.value, object: result.value.object as z.infer<T> });\r\n }\r\n\r\n async embedDocument(\r\n value: string,\r\n options?: Parameters<ReturnType<typeof MDocument.fromText>[\"chunk\"]>[0],\r\n type: \"text\" | \"markdown\" | \"html\" | \"json\" = \"text\",\r\n model: string = OPENAI_TEXT_EMBEDDING_3_SMALL\r\n ): ServerResultAsync<{ embeddings: number[][]; chunks: { text: string }[] }> {\r\n return this.throwableAsync(async () => {\r\n if (type === \"text\") {\r\n const doc = MDocument.fromText(value);\r\n const chunks = await doc.chunk(\r\n options ?? {\r\n strategy: \"recursive\",\r\n maxSize: 512,\r\n overlap: 50,\r\n separators: [\"\\n\"],\r\n }\r\n );\r\n const embeddings = await this.embedMany(chunks, model);\r\n if (embeddings.isErr()) return err(embeddings.error);\r\n return ok({ embeddings: embeddings.value.embeddings, chunks });\r\n }\r\n return this.error(\"BAD_REQUEST\", \"Unsupported document type\");\r\n });\r\n }\r\n\r\n async embed(\r\n text: string,\r\n model: string = OPENAI_TEXT_EMBEDDING_3_SMALL\r\n ): ServerResultAsync<{ embedding: number[] }> {\r\n return this.throwableAsync(async () => {\r\n const result = await embed({\r\n model: this.prepareEmbeddingModel(model),\r\n value: text,\r\n });\r\n return ok(result);\r\n });\r\n }\r\n\r\n async embedMany(\r\n chunks: { text: string }[],\r\n model: string = OPENAI_TEXT_EMBEDDING_3_SMALL\r\n ): ServerResultAsync<{ embeddings: number[][] }> {\r\n return this.throwableAsync(async () => {\r\n const result = await embedMany({\r\n model: this.prepareEmbeddingModel(model),\r\n values: chunks.map((chunk) => chunk.text),\r\n });\r\n return ok(result);\r\n });\r\n }\r\n\r\n async generateText(params: AIServiceGenerateTextParams): ServerResultAsync<string> {\r\n return this.throwableAsync(async () => {\r\n const { removeMDash = true, model, prompt, messages, ctx, ...rest } = params;\r\n const request = messages\r\n ? { ...rest, model: this.prepareModel(model), messages }\r\n : { ...rest, model: this.prepareModel(model), prompt };\r\n const result = await generateText(request);\r\n if (this.repository.aiUsage) {\r\n const createUsageResult = await this.repository.aiUsage.create({\r\n userId: ctx?.actor?.userId,\r\n model,\r\n provider: \"openrouter\",\r\n feature: \"generateText\",\r\n traceId: result.providerMetadata?.openrouter?.traceId?.toString(),\r\n inputTokens: result.usage.inputTokens,\r\n outputTokens: result.usage.outputTokens,\r\n totalTokens: result.usage.totalTokens,\r\n cost: (result?.providerMetadata?.openrouter?.usage as any)?.cost ?? 0,\r\n });\r\n if (createUsageResult.isErr()) return err(createUsageResult.error);\r\n }\r\n return ok(removeMDash ? result.text.replace(/\\u2013|\\u2014/g, \"-\") : result.text);\r\n });\r\n }\r\n\r\n async generateObject<T extends ZodType>(\r\n params: AIServiceGenerateObjectParams<T>\r\n ): ServerResultAsync<z.infer<T>> {\r\n const {\r\n model,\r\n schema,\r\n prompt,\r\n messages,\r\n repairAttempts = 0,\r\n repairModel,\r\n ctx,\r\n ...rest\r\n } = params;\r\n const request = messages\r\n ? {\r\n ...rest,\r\n model: this.prepareModel(model),\r\n messages,\r\n output: Output.object({ schema }),\r\n }\r\n : {\r\n ...rest,\r\n model: this.prepareModel(model),\r\n prompt,\r\n output: Output.object({ schema }),\r\n };\r\n try {\r\n const result = await generateText(request);\r\n if (this.repository.aiUsage) {\r\n const createUsageResult = await this.repository.aiUsage.create({\r\n userId: ctx?.actor?.userId,\r\n model,\r\n provider: \"openrouter\",\r\n feature: \"generateObject\",\r\n traceId: result.providerMetadata?.openrouter?.traceId?.toString(),\r\n inputTokens: result.usage.inputTokens,\r\n outputTokens: result.usage.outputTokens,\r\n totalTokens: result.usage.totalTokens,\r\n cost: (result?.providerMetadata?.openrouter?.usage as any)?.cost ?? 0,\r\n });\r\n if (createUsageResult.isErr()) return err(createUsageResult.error);\r\n }\r\n return ok(result.output as z.infer<T>);\r\n } catch (error) {\r\n if (NoObjectGeneratedError.isInstance(error)) {\r\n if (this.repository.aiUsage) {\r\n const createUsageResult = await this.repository.aiUsage.create({\r\n userId: ctx?.actor?.userId,\r\n model,\r\n provider: \"openrouter\",\r\n feature: \"generateObject\",\r\n traceId: null,\r\n inputTokens: error?.usage?.inputTokens,\r\n outputTokens: error?.usage?.outputTokens,\r\n totalTokens: error?.usage?.totalTokens,\r\n cost: 0,\r\n });\r\n if (createUsageResult.isErr()) return err(createUsageResult.error);\r\n }\r\n if (error.text) {\r\n const repairedText = jsonrepair(error.text);\r\n const parsed = schema.safeParse(repairedText);\r\n if (parsed.success) return ok(parsed.data);\r\n\r\n if (repairAttempts === 0)\r\n return this.error(\"PARSE_ERROR\", \"AI: Agent object failed\", { cause: error });\r\n\r\n return this.generateObject({\r\n ...rest,\r\n prompt: repairJsonPrompt.compile({\r\n text: error.text,\r\n error: JSON.stringify(error.cause ?? \"Unknown error\"),\r\n }),\r\n repairAttempts: repairAttempts - 1,\r\n model: repairModel ?? model,\r\n schema,\r\n ctx,\r\n });\r\n }\r\n return this.error(\"PARSE_ERROR\", \"AI: Agent object failed without text\", {\r\n cause: error,\r\n });\r\n }\r\n return this.error(\"BAD_REQUEST\", \"AI: Provided failed to generate object\", { cause: error });\r\n }\r\n }\r\n\r\n async generateReplicate(\r\n model: Parameters<Replicate[\"run\"]>[0],\r\n options: Parameters<Replicate[\"run\"]>[1]\r\n ): ServerResultAsync<object> {\r\n return this.throwableAsync(async () => {\r\n if (!this.replicate) {\r\n return this.error(\"INTERNAL_SERVER_ERROR\", \"Replicate is not configured\");\r\n }\r\n try {\r\n return ok(await this.replicate.run(model, options));\r\n } catch (error) {\r\n return this.error(\"INTERNAL_SERVER_ERROR\", undefined, { cause: error });\r\n }\r\n });\r\n }\r\n\r\n async generateTranscript(\r\n file_url: string\r\n ): ServerResultAsync<{ text: string; metadata: unknown }> {\r\n const output = await this.generateReplicate(\r\n \"thomasmol/whisper-diarization:1495a9cddc83b2203b0d8d3516e38b80fd1572ebc4bc5700ac1da56a9b3ed886\",\r\n {\r\n input: {\r\n file_url,\r\n },\r\n }\r\n );\r\n\r\n if (output.isErr()) return err(output.error);\r\n\r\n try {\r\n const { segments } = output.value as { segments: { text: string }[] };\r\n return ok({ text: segments.map((segment) => segment.text).join(\"\"), metadata: segments });\r\n } catch (error) {\r\n return this.error(\"INTERNAL_SERVER_ERROR\", undefined, { cause: error });\r\n }\r\n }\r\n\r\n async generateIdeogram(\r\n input: IdeogramV3GenerateInput\r\n ): ServerResultAsync<IdeogramV3GenerateOutput> {\r\n if (!this.service.ideogram) {\r\n return this.error(\"INTERNAL_SERVER_ERROR\", \"Ideogram service is not available\");\r\n }\r\n return this.service.ideogram.generate(input);\r\n }\r\n\r\n async getUsage(\r\n userId: string\r\n ): ServerResultAsync<Pick<AiUsageRow, \"inputTokens\" | \"outputTokens\" | \"totalTokens\" | \"cost\">> {\r\n if (!this.repository.aiUsage) {\r\n return this.error(\"INTERNAL_SERVER_ERROR\", \"AI usage repository is not available\");\r\n }\r\n return this.repository.aiUsage.getUsage(userId);\r\n }\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"ai.service.mjs","names":[],"sources":["../../../../src/modules/ai/ai.service.ts"],"sourcesContent":["import { OPENAI_TEXT_EMBEDDING_3_SMALL } from \"@m5kdev/commons/modules/ai/ai.constants\";\r\nimport { arrayToPseudoXML } from \"@m5kdev/commons/modules/ai/ai.utils\";\r\nimport type { Mastra } from \"@mastra/core\";\r\nimport { RequestContext } from \"@mastra/core/request-context\";\r\nimport type { FullOutput, MastraModelOutput } from \"@mastra/core/stream\";\r\nimport { MDocument } from \"@mastra/rag\";\r\nimport type { OpenRouterProvider } from \"@openrouter/ai-sdk-provider\";\r\nimport {\r\n embed,\r\n embedMany,\r\n generateText,\r\n type ModelMessage,\r\n NoObjectGeneratedError,\r\n Output,\r\n} from \"ai\";\r\nimport { jsonrepair } from \"jsonrepair\";\r\nimport { err, ok } from \"neverthrow\";\r\nimport type Replicate from \"replicate\";\r\nimport type { ZodType, z } from \"zod\";\r\nimport type { RequiredServiceActor } from \"../base/base.actor\";\r\nimport type { ServerResultAsync } from \"../base/base.dto\";\r\nimport { BaseService } from \"../base/base.service\";\r\nimport { repairJsonPrompt } from \"./ai.prompts\";\r\nimport type { AiUsageRepository, AiUsageRow } from \"./ai.repository\";\r\nimport type { IdeogramV3GenerateInput, IdeogramV3GenerateOutput } from \"./ideogram/ideogram.dto\";\r\nimport type { IdeogramService } from \"./ideogram/ideogram.service\";\r\n\r\ntype MastraAgent = ReturnType<Mastra[\"getAgent\"]>;\r\ntype MastraAgentGenerateOptions = Parameters<MastraAgent[\"generate\"]>[1];\r\ntype MessageListInput = { role: \"user\" | \"assistant\" | \"system\"; content: string }[];\r\ntype GenerateTextParams = Parameters<typeof generateText>[0];\r\ntype GenerateTextInput =\r\n | { prompt: string | ModelMessage[]; messages?: never }\r\n | { messages: ModelMessage[]; prompt?: never };\r\ntype AIServiceActorContext = { actor: RequiredServiceActor<\"user\"> };\r\ntype AIServiceGenerateTextParams = Omit<GenerateTextParams, \"model\" | \"prompt\" | \"messages\"> &\r\n GenerateTextInput & {\r\n model: string;\r\n removeMDash?: boolean;\r\n ctx?: AIServiceActorContext;\r\n retryAttempts?: number;\r\n retryModels?: string[];\r\n };\r\ntype AIServiceGenerateObjectParams<T extends ZodType> = Omit<\r\n GenerateTextParams,\r\n \"model\" | \"prompt\" | \"messages\" | \"output\"\r\n> &\r\n GenerateTextInput & {\r\n model: string;\r\n schema: T;\r\n repairAttempts?: number;\r\n repairModel?: string;\r\n ctx?: AIServiceActorContext;\r\n retryAttempts?: number;\r\n retryModels?: string[];\r\n };\r\n\r\ntype AIServiceOptions = {\r\n retryAttempts?: number;\r\n retryModels?: string[];\r\n repairAttempts?: number;\r\n repairModel?: string;\r\n removeMDash?: boolean;\r\n};\r\n\r\nexport class AIService<MastraInstance extends Mastra> extends BaseService<\r\n { aiUsage?: AiUsageRepository },\r\n { ideogram?: IdeogramService }\r\n> {\r\n helpers = {\r\n arrayToPseudoXML,\r\n };\r\n\r\n mastra?: MastraInstance;\r\n openrouter?: OpenRouterProvider;\r\n replicate?: Replicate;\r\n options?: AIServiceOptions;\r\n\r\n constructor(\r\n repositories: { aiUsage?: AiUsageRepository },\r\n services: { ideogram?: IdeogramService },\r\n libs: { mastra?: MastraInstance; openrouter?: OpenRouterProvider; replicate?: Replicate },\r\n options?: AIServiceOptions\r\n ) {\r\n super(repositories, services);\r\n this.mastra = libs.mastra;\r\n this.openrouter = libs.openrouter;\r\n this.replicate = libs.replicate;\r\n this.options = options;\r\n }\r\n\r\n getMastra(): MastraInstance {\r\n if (!this.mastra) {\r\n throw new Error(\"Mastra is not available\");\r\n }\r\n return this.mastra;\r\n }\r\n\r\n prepareModel(model: string): ReturnType<OpenRouterProvider[\"chat\"]> {\r\n if (!this.openrouter) {\r\n throw new Error(\"OpenRouter is not configured\");\r\n }\r\n return this.openrouter.chat(model, {\r\n usage: {\r\n include: true,\r\n },\r\n });\r\n }\r\n\r\n prepareEmbeddingModel(model: string): ReturnType<OpenRouterProvider[\"textEmbeddingModel\"]> {\r\n if (!this.openrouter) {\r\n throw new Error(\"OpenRouter is not configured\");\r\n }\r\n const openrouter = this.openrouter as OpenRouterProvider & {\r\n embeddingModel?: (modelId: string) => unknown;\r\n };\r\n return (openrouter.embeddingModel?.(model) ??\r\n openrouter.textEmbeddingModel(model)) as ReturnType<OpenRouterProvider[\"textEmbeddingModel\"]>;\r\n }\r\n\r\n async agentUse(\r\n agent: string,\r\n options: MastraAgentGenerateOptions & { prompt?: string; messages?: MessageListInput },\r\n ctx?: AIServiceActorContext & { model?: string }\r\n ): ServerResultAsync<Awaited<ReturnType<MastraModelOutput<any>[\"getFullOutput\"]>>> {\r\n return this.throwableAsync(async () => {\r\n this.logger.info(\"AGENT USE\");\r\n const { prompt, messages, ...rest } = options;\r\n const payload = messages || prompt;\r\n if (!payload) return this.error(\"BAD_REQUEST\", \"No prompt or messages provided\");\r\n const requestContext = options.requestContext ?? new RequestContext();\r\n\r\n if (ctx?.actor) {\r\n requestContext.set(\"userId\", ctx.actor.userId);\r\n }\r\n if (ctx?.model) {\r\n requestContext.set(\"model\", ctx.model);\r\n }\r\n const mAgent = this.getMastra().getAgent(agent);\r\n\r\n const result = await mAgent.generate(payload as any, {\r\n ...rest,\r\n requestContext: rest.requestContext ?? requestContext,\r\n });\r\n this.logger.info(\"AGENT USE DONE\");\r\n if (this.repository.aiUsage) {\r\n const createUsageResult = await this.repository.aiUsage.create({\r\n userId: ctx?.actor?.userId,\r\n model: ctx?.model ?? \"unknown\",\r\n provider: \"openrouter\",\r\n feature: agent,\r\n traceId: result.traceId,\r\n inputTokens: result.usage.inputTokens,\r\n outputTokens: result.usage.outputTokens,\r\n totalTokens: result.usage.totalTokens,\r\n cost: (result?.providerMetadata?.openrouter?.usage as any)?.cost ?? 0,\r\n });\r\n if (createUsageResult.isErr()) return err(createUsageResult.error);\r\n }\r\n this.logger.info(\"AGENT USE CREATED USAGE\");\r\n return ok(result);\r\n });\r\n }\r\n\r\n async agentText(\r\n agent: string,\r\n options: MastraAgentGenerateOptions & { prompt?: string; messages?: MessageListInput },\r\n ctx?: AIServiceActorContext & { model?: string }\r\n ): ServerResultAsync<string> {\r\n const result = await this.agentUse(agent, options, ctx);\r\n if (result.isErr())\r\n return this.error(\"SERVICE_UNAVAILABLE\", \"AI: Agent text failed\", { cause: result.error });\r\n return ok(result.value.text);\r\n }\r\n\r\n async agentTextResult(\r\n agent: string,\r\n options: MastraAgentGenerateOptions & { prompt?: string; messages?: MessageListInput },\r\n ctx?: AIServiceActorContext & { model?: string }\r\n ): ServerResultAsync<FullOutput<any>> {\r\n const result = await this.agentUse(agent, options, ctx);\r\n if (result.isErr()) return err(result.error);\r\n return ok(result.value);\r\n }\r\n\r\n async agentObject<T extends ZodType<any>>(\r\n agent: string,\r\n options: MastraAgentGenerateOptions & {\r\n schema: T;\r\n prompt?: string;\r\n messages?: MessageListInput;\r\n },\r\n ctx?: AIServiceActorContext & { model?: string }\r\n ): ServerResultAsync<z.infer<T>> {\r\n const { schema, ...rest } = options;\r\n const result = await this.agentUse(agent, { ...rest, structuredOutput: { schema } }, ctx);\r\n if (result.isErr())\r\n return this.error(\"SERVICE_UNAVAILABLE\", \"AI: Agent object failed\", { cause: result.error });\r\n return ok(result.value.object as z.infer<T>);\r\n }\r\n\r\n async agentObjectResult<T extends ZodType<any>>(\r\n agent: string,\r\n options: MastraAgentGenerateOptions & {\r\n schema: T;\r\n prompt?: string;\r\n messages?: MessageListInput;\r\n },\r\n ctx?: AIServiceActorContext & { model?: string }\r\n ): ServerResultAsync<FullOutput<any> & { object: z.infer<T> }> {\r\n this.logger.info(\"AGENT OBJECT RESULT\");\r\n const { schema, ...rest } = options;\r\n const result = await this.agentUse(agent, { ...rest, structuredOutput: { schema } }, ctx);\r\n if (result.isErr()) return err(result.error);\r\n this.logger.info(\"AGENT OBJECT RESULT DONE\");\r\n return ok({ ...result.value, object: result.value.object as z.infer<T> });\r\n }\r\n\r\n async embedDocument(\r\n value: string,\r\n options?: Parameters<ReturnType<typeof MDocument.fromText>[\"chunk\"]>[0],\r\n type: \"text\" | \"markdown\" | \"html\" | \"json\" = \"text\",\r\n model: string = OPENAI_TEXT_EMBEDDING_3_SMALL\r\n ): ServerResultAsync<{ embeddings: number[][]; chunks: { text: string }[] }> {\r\n return this.throwableAsync(async () => {\r\n if (type === \"text\") {\r\n const doc = MDocument.fromText(value);\r\n const chunks = await doc.chunk(\r\n options ?? {\r\n strategy: \"recursive\",\r\n maxSize: 512,\r\n overlap: 50,\r\n separators: [\"\\n\"],\r\n }\r\n );\r\n const embeddings = await this.embedMany(chunks, model);\r\n if (embeddings.isErr()) return err(embeddings.error);\r\n return ok({ embeddings: embeddings.value.embeddings, chunks });\r\n }\r\n return this.error(\"BAD_REQUEST\", \"Unsupported document type\");\r\n });\r\n }\r\n\r\n async embed(\r\n text: string,\r\n model: string = OPENAI_TEXT_EMBEDDING_3_SMALL\r\n ): ServerResultAsync<{ embedding: number[] }> {\r\n return this.throwableAsync(async () => {\r\n const result = await embed({\r\n model: this.prepareEmbeddingModel(model),\r\n value: text,\r\n });\r\n return ok(result);\r\n });\r\n }\r\n\r\n async embedMany(\r\n chunks: { text: string }[],\r\n model: string = OPENAI_TEXT_EMBEDDING_3_SMALL\r\n ): ServerResultAsync<{ embeddings: number[][] }> {\r\n return this.throwableAsync(async () => {\r\n const result = await embedMany({\r\n model: this.prepareEmbeddingModel(model),\r\n values: chunks.map((chunk) => chunk.text),\r\n });\r\n return ok(result);\r\n });\r\n }\r\n\r\n async generateText(params: AIServiceGenerateTextParams): ServerResultAsync<string> {\r\n return this.throwableAsync(async () => {\r\n const {\r\n removeMDash = this.options?.removeMDash ?? true,\r\n model,\r\n prompt,\r\n messages,\r\n ctx,\r\n retryAttempts = this.options?.retryAttempts ?? 0,\r\n retryModels = this.options?.retryModels ?? [],\r\n ...rest\r\n } = params;\r\n const request = messages\r\n ? { ...rest, model: this.prepareModel(model), messages }\r\n : { ...rest, model: this.prepareModel(model), prompt };\r\n try {\r\n const result = await generateText(request);\r\n if (this.repository.aiUsage) {\r\n const createUsageResult = await this.repository.aiUsage.create({\r\n userId: ctx?.actor?.userId,\r\n model,\r\n provider: \"openrouter\",\r\n feature: \"generateText\",\r\n traceId: result.providerMetadata?.openrouter?.traceId?.toString(),\r\n inputTokens: result.usage.inputTokens,\r\n outputTokens: result.usage.outputTokens,\r\n totalTokens: result.usage.totalTokens,\r\n cost: (result?.providerMetadata?.openrouter?.usage as any)?.cost ?? 0,\r\n });\r\n if (createUsageResult.isErr()) return err(createUsageResult.error);\r\n }\r\n return ok(removeMDash ? result.text.replace(/\\u2013|\\u2014/g, \"-\") : result.text);\r\n } catch (error) {\r\n if (retryAttempts <= 0) throw error;\r\n this.logger.warn(`generateText failed, retrying (${retryAttempts} attempts left)`, {\r\n model,\r\n error,\r\n });\r\n // Exponential backoff: wait before retrying\r\n const delay = Math.min(\r\n 1000 * 2 ** ((this.options?.retryAttempts ?? 3) - retryAttempts),\r\n 10000\r\n );\r\n await new Promise<void>((resolve) => setTimeout(resolve, delay));\r\n const nextModel = retryModels?.[0] ?? model;\r\n const nextRetryModels = retryModels ? [...retryModels.slice(1), model] : undefined;\r\n return this.generateText({\r\n ...rest,\r\n ...(messages ? { messages } : { prompt: prompt! }),\r\n model: nextModel,\r\n removeMDash,\r\n ctx,\r\n retryAttempts: retryAttempts - 1,\r\n retryModels: nextRetryModels,\r\n } as AIServiceGenerateTextParams);\r\n }\r\n });\r\n }\r\n\r\n async generateObject<T extends ZodType>(\r\n params: AIServiceGenerateObjectParams<T>\r\n ): ServerResultAsync<z.infer<T>> {\r\n const {\r\n model,\r\n schema,\r\n prompt,\r\n messages,\r\n repairAttempts = this.options?.repairAttempts ?? 0,\r\n repairModel = this.options?.repairModel ?? model,\r\n ctx,\r\n retryAttempts = this.options?.retryAttempts ?? 0,\r\n retryModels = this.options?.retryModels ?? [],\r\n ...rest\r\n } = params;\r\n const request = messages\r\n ? {\r\n ...rest,\r\n model: this.prepareModel(model),\r\n messages,\r\n output: Output.object({ schema }),\r\n }\r\n : {\r\n ...rest,\r\n model: this.prepareModel(model),\r\n prompt,\r\n output: Output.object({ schema }),\r\n };\r\n try {\r\n const result = await generateText(request);\r\n if (this.repository.aiUsage) {\r\n const createUsageResult = await this.repository.aiUsage.create({\r\n userId: ctx?.actor?.userId,\r\n model,\r\n provider: \"openrouter\",\r\n feature: \"generateObject\",\r\n traceId: result.providerMetadata?.openrouter?.traceId?.toString(),\r\n inputTokens: result.usage.inputTokens,\r\n outputTokens: result.usage.outputTokens,\r\n totalTokens: result.usage.totalTokens,\r\n cost: (result?.providerMetadata?.openrouter?.usage as any)?.cost ?? 0,\r\n });\r\n if (createUsageResult.isErr()) return err(createUsageResult.error);\r\n }\r\n return ok(result.output as z.infer<T>);\r\n } catch (error) {\r\n if (NoObjectGeneratedError.isInstance(error)) {\r\n if (this.repository.aiUsage) {\r\n const createUsageResult = await this.repository.aiUsage.create({\r\n userId: ctx?.actor?.userId,\r\n model,\r\n provider: \"openrouter\",\r\n feature: \"generateObject\",\r\n traceId: null,\r\n inputTokens: error?.usage?.inputTokens,\r\n outputTokens: error?.usage?.outputTokens,\r\n totalTokens: error?.usage?.totalTokens,\r\n cost: 0,\r\n });\r\n if (createUsageResult.isErr()) return err(createUsageResult.error);\r\n }\r\n if (error.text) {\r\n const repairedText = jsonrepair(error.text);\r\n const parsed = schema.safeParse(repairedText);\r\n if (parsed.success) return ok(parsed.data);\r\n\r\n if (repairAttempts === 0)\r\n return this.error(\"PARSE_ERROR\", \"AI: Agent object failed\", { cause: error });\r\n\r\n return this.generateObject({\r\n ...rest,\r\n prompt: repairJsonPrompt.compile({\r\n text: error.text,\r\n error: JSON.stringify(error.cause ?? \"Unknown error\"),\r\n }),\r\n repairAttempts: repairAttempts - 1,\r\n model: repairModel ?? model,\r\n schema,\r\n ctx,\r\n });\r\n }\r\n return this.error(\"PARSE_ERROR\", \"AI: Agent object failed without text\", {\r\n cause: error,\r\n });\r\n }\r\n if (retryAttempts <= 0)\r\n return this.error(\"BAD_REQUEST\", \"AI: Provider failed to generate object\", {\r\n cause: error,\r\n });\r\n this.logger.warn(`generateObject failed, retrying (${retryAttempts} attempts left)`, {\r\n model,\r\n error,\r\n });\r\n // Exponential backoff: wait before retrying\r\n const delay = Math.min(\r\n 1000 * 2 ** ((this.options?.retryAttempts ?? 3) - retryAttempts),\r\n 10000\r\n );\r\n await new Promise<void>((resolve) => setTimeout(resolve, delay));\r\n const nextModel = retryModels?.[0] ?? model;\r\n const nextRetryModels = retryModels ? [...retryModels.slice(1), model] : undefined;\r\n return this.generateObject({\r\n ...rest,\r\n ...(messages ? { messages } : { prompt: prompt! }),\r\n model: nextModel,\r\n schema,\r\n repairAttempts,\r\n repairModel,\r\n ctx,\r\n retryAttempts: retryAttempts - 1,\r\n retryModels: nextRetryModels,\r\n } as AIServiceGenerateObjectParams<T>);\r\n }\r\n }\r\n\r\n async generateReplicate(\r\n model: Parameters<Replicate[\"run\"]>[0],\r\n options: Parameters<Replicate[\"run\"]>[1]\r\n ): ServerResultAsync<object> {\r\n return this.throwableAsync(async () => {\r\n if (!this.replicate) {\r\n return this.error(\"INTERNAL_SERVER_ERROR\", \"Replicate is not configured\");\r\n }\r\n try {\r\n return ok(await this.replicate.run(model, options));\r\n } catch (error) {\r\n return this.error(\"INTERNAL_SERVER_ERROR\", undefined, { cause: error });\r\n }\r\n });\r\n }\r\n\r\n async generateTranscript(\r\n file_url: string\r\n ): ServerResultAsync<{ text: string; metadata: unknown }> {\r\n const output = await this.generateReplicate(\r\n \"thomasmol/whisper-diarization:1495a9cddc83b2203b0d8d3516e38b80fd1572ebc4bc5700ac1da56a9b3ed886\",\r\n {\r\n input: {\r\n file_url,\r\n },\r\n }\r\n );\r\n\r\n if (output.isErr()) return err(output.error);\r\n\r\n try {\r\n const { segments } = output.value as { segments: { text: string }[] };\r\n return ok({ text: segments.map((segment) => segment.text).join(\"\"), metadata: segments });\r\n } catch (error) {\r\n return this.error(\"INTERNAL_SERVER_ERROR\", undefined, { cause: error });\r\n }\r\n }\r\n\r\n async generateIdeogram(\r\n input: IdeogramV3GenerateInput\r\n ): ServerResultAsync<IdeogramV3GenerateOutput> {\r\n if (!this.service.ideogram) {\r\n return this.error(\"INTERNAL_SERVER_ERROR\", \"Ideogram service is not available\");\r\n }\r\n return this.service.ideogram.generate(input);\r\n }\r\n\r\n async getUsage(\r\n userId: string\r\n ): ServerResultAsync<Pick<AiUsageRow, \"inputTokens\" | \"outputTokens\" | \"totalTokens\" | \"cost\">> {\r\n if (!this.repository.aiUsage) {\r\n return this.error(\"INTERNAL_SERVER_ERROR\", \"AI usage repository is not available\");\r\n }\r\n return this.repository.aiUsage.getUsage(userId);\r\n }\r\n}\r\n"],"mappings":"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"}
@@ -36,8 +36,8 @@ declare const accountClaimOutputSchema: z.ZodObject<{
36
36
  id: z.ZodString;
37
37
  createdAt: z.ZodDate;
38
38
  updatedAt: z.ZodNullable<z.ZodDate>;
39
- status: z.ZodString;
40
39
  expiresAt: z.ZodNullable<z.ZodDate>;
40
+ status: z.ZodString;
41
41
  claimUserId: z.ZodNullable<z.ZodString>;
42
42
  claimedAt: z.ZodNullable<z.ZodDate>;
43
43
  claimedEmail: z.ZodNullable<z.ZodString>;
@@ -58,8 +58,8 @@ declare const accountClaimMagicLinkOutputSchema: z.ZodObject<{
58
58
  id: z.ZodString;
59
59
  email: z.ZodString;
60
60
  createdAt: z.ZodDate;
61
- userId: z.ZodString;
62
61
  expiresAt: z.ZodNullable<z.ZodDate>;
62
+ userId: z.ZodString;
63
63
  claimId: z.ZodString;
64
64
  url: z.ZodString;
65
65
  }, z.core.$strip>;
@@ -59,8 +59,8 @@ declare function createAuthTRPC({
59
59
  id: string;
60
60
  createdAt: Date;
61
61
  updatedAt: Date | null;
62
- status: string;
63
62
  expiresAt: Date | null;
63
+ status: string;
64
64
  claimUserId: string | null;
65
65
  claimedAt: Date | null;
66
66
  claimedEmail: string | null;
@@ -76,8 +76,8 @@ declare function createAuthTRPC({
76
76
  id: string;
77
77
  email: string;
78
78
  createdAt: Date;
79
- userId: string;
80
79
  expiresAt: Date | null;
80
+ userId: string;
81
81
  claimId: string;
82
82
  url: string;
83
83
  };
@@ -91,8 +91,8 @@ declare function createAuthTRPC({
91
91
  id: string;
92
92
  email: string;
93
93
  createdAt: Date;
94
- userId: string;
95
94
  expiresAt: Date | null;
95
+ userId: string;
96
96
  claimId: string;
97
97
  url: string;
98
98
  }[];
@@ -27,10 +27,10 @@ declare const connectSelectOutputSchema: z.ZodObject<{
27
27
  id: z.ZodString;
28
28
  createdAt: z.ZodDate;
29
29
  updatedAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
30
- userId: z.ZodString;
31
- provider: z.ZodString;
32
30
  expiresAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
31
+ userId: z.ZodString;
33
32
  scope: z.ZodOptional<z.ZodNullable<z.ZodString>>;
33
+ provider: z.ZodString;
34
34
  accountType: z.ZodString;
35
35
  providerAccountId: z.ZodString;
36
36
  handle: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -51,10 +51,10 @@ declare const connectListOutputSchema: z.ZodArray<z.ZodObject<{
51
51
  id: z.ZodString;
52
52
  createdAt: z.ZodDate;
53
53
  updatedAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
54
- userId: z.ZodString;
55
- provider: z.ZodString;
56
54
  expiresAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
55
+ userId: z.ZodString;
57
56
  scope: z.ZodOptional<z.ZodNullable<z.ZodString>>;
57
+ provider: z.ZodString;
58
58
  accountType: z.ZodString;
59
59
  providerAccountId: z.ZodString;
60
60
  handle: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -396,12 +396,12 @@ declare class ConnectRepository extends BaseTableRepository<Orm, Schema, Record<
396
396
  id: string;
397
397
  createdAt: Date;
398
398
  updatedAt: Date | null;
399
- userId: string;
400
- provider: string;
401
399
  expiresAt: Date | null;
400
+ userId: string;
402
401
  accessToken: string;
403
402
  refreshToken: string | null;
404
403
  scope: string | null;
404
+ provider: string;
405
405
  accountType: string;
406
406
  providerAccountId: string;
407
407
  handle: string | null;
@@ -22,12 +22,12 @@ declare class ConnectService extends BaseService<{
22
22
  id: string;
23
23
  createdAt: Date;
24
24
  updatedAt: Date | null;
25
- userId: string;
26
- provider: string;
27
25
  expiresAt: Date | null;
26
+ userId: string;
28
27
  accessToken: string;
29
28
  refreshToken: string | null;
30
29
  scope: string | null;
30
+ provider: string;
31
31
  accountType: string;
32
32
  providerAccountId: string;
33
33
  handle: string | null;
@@ -43,12 +43,12 @@ declare class ConnectService extends BaseService<{
43
43
  id: string;
44
44
  createdAt: Date;
45
45
  updatedAt: Date | null;
46
- userId: string;
47
- provider: string;
48
46
  expiresAt: Date | null;
47
+ userId: string;
49
48
  accessToken: string;
50
49
  refreshToken: string | null;
51
50
  scope: string | null;
51
+ provider: string;
52
52
  accountType: string;
53
53
  providerAccountId: string;
54
54
  handle: string | null;
@@ -1,10 +1,10 @@
1
1
  import { BaseService } from "../base/base.service.mjs";
2
2
  import { err, ok } from "neverthrow";
3
3
  import { v4 } from "uuid";
4
- import { createWriteStream } from "node:fs";
5
- import { mkdir, readFile, writeFile } from "node:fs/promises";
6
4
  import path, { dirname } from "node:path";
7
5
  import { fileTypes } from "@m5kdev/commons/modules/file/file.constants";
6
+ import { createWriteStream } from "node:fs";
7
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
8
8
  import { tmpdir } from "node:os";
9
9
  import { Readable } from "node:stream";
10
10
  import { pipeline } from "node:stream/promises";
@@ -47,8 +47,8 @@ declare class RecurrenceService extends BaseService<{
47
47
  kind: string;
48
48
  enabled: boolean;
49
49
  recurrenceRules: {
50
- freq: number;
51
50
  interval: number;
51
+ freq: number;
52
52
  bysetpos?: number | number[] | null | undefined;
53
53
  bymonth?: number | number[] | null | undefined;
54
54
  bymonthday?: number | number[] | null | undefined;
@@ -50,8 +50,8 @@ declare function createRecurrenceTRPC({
50
50
  kind: string;
51
51
  enabled: boolean;
52
52
  recurrenceRules: {
53
- freq: number;
54
53
  interval: number;
54
+ freq: number;
55
55
  bysetpos?: number | number[] | null | undefined;
56
56
  bymonth?: number | number[] | null | undefined;
57
57
  bymonthday?: number | number[] | null | undefined;
@@ -150,8 +150,8 @@ declare function createRecurrenceTRPC({
150
150
  updateRule: _$_trpc_server0.TRPCMutationProcedure<{
151
151
  input: {
152
152
  id: string;
153
- freq: number;
154
153
  interval: number;
154
+ freq: number;
155
155
  bysetpos?: number | number[] | null | undefined;
156
156
  bymonth?: number | number[] | null | undefined;
157
157
  bymonthday?: number | number[] | null | undefined;
@@ -1,25 +1,60 @@
1
1
  import { BaseService } from "../base/base.service.mjs";
2
- import { err, ok } from "neverthrow";
2
+ import { ok } from "neverthrow";
3
3
  import { v4 } from "uuid";
4
- import { closeSync, existsSync, mkdirSync, openSync } from "node:fs";
5
4
  import path from "node:path";
5
+ import { closeSync, existsSync, mkdirSync, openSync } from "node:fs";
6
+ import { spawn } from "node:child_process";
6
7
  import ffbin from "ffmpeg-ffprobe-static";
7
- import ffmpeg from "fluent-ffmpeg";
8
8
  //#region src/modules/video/video.service.ts
9
9
  if (!ffbin.ffmpegPath || !ffbin.ffprobePath) throw new Error("FFmpeg or FFprobe not found");
10
- ffmpeg.setFfmpegPath(ffbin.ffmpegPath);
11
- ffmpeg.setFfprobePath(ffbin.ffprobePath);
12
10
  const uploadsDir = path.join(__dirname, "..", "uploads");
13
11
  if (!existsSync(uploadsDir)) mkdirSync(uploadsDir, { recursive: true });
12
+ const runFfmpeg = async (args) => {
13
+ await new Promise((resolve, reject) => {
14
+ const child = spawn(ffbin.ffmpegPath, [...args], { stdio: [
15
+ "ignore",
16
+ "ignore",
17
+ "pipe"
18
+ ] });
19
+ let stderr = "";
20
+ child.stderr?.setEncoding("utf8");
21
+ child.stderr?.on("data", (chunk) => {
22
+ stderr += chunk;
23
+ });
24
+ child.on("error", (error) => reject(error));
25
+ child.on("close", (code) => {
26
+ if (code === 0) {
27
+ resolve();
28
+ return;
29
+ }
30
+ reject(new Error(stderr || `ffmpeg exited with code ${code ?? "unknown"}`));
31
+ });
32
+ });
33
+ };
14
34
  var VideoService = class extends BaseService {
15
35
  async cut(file, start, end) {
16
36
  return this.throwableAsync(async () => {
17
37
  const duration = end - start;
18
38
  const output = path.join(uploadsDir, `${v4()}.mp4`);
19
39
  if (!existsSync(output)) closeSync(openSync(output, "w"));
20
- await new Promise((resolve, reject) => {
21
- ffmpeg(file).seekOutput(start).videoCodec("libx264").audioCodec("copy").outputOptions(["-y", "-movflags +faststart"]).duration(duration).on("end", () => resolve()).on("error", (e, _stdout, _stderr) => reject(e)).save(output);
22
- }).catch((error) => err(this.handleUnknownError(error)));
40
+ await runFfmpeg([
41
+ "-i",
42
+ file,
43
+ "-ss",
44
+ String(start),
45
+ "-t",
46
+ String(duration),
47
+ "-c:v",
48
+ "libx264",
49
+ "-c:a",
50
+ "copy",
51
+ "-movflags",
52
+ "+faststart",
53
+ "-y",
54
+ output
55
+ ]).catch((error) => {
56
+ throw this.handleUnknownError(error);
57
+ });
23
58
  return ok(output);
24
59
  });
25
60
  }
@@ -27,9 +62,23 @@ var VideoService = class extends BaseService {
27
62
  return this.throwableAsync(async () => {
28
63
  const output = path.join(uploadsDir, `${v4()}.wav`);
29
64
  if (!existsSync(output)) closeSync(openSync(output, "w"));
30
- await new Promise((resolve, reject) => {
31
- ffmpeg(input).noVideo().audioCodec("pcm_s16le").audioFrequency(hz).audioChannels(2).format("wav").outputOptions(["-y"]).on("end", () => resolve()).on("error", reject).save(output);
32
- }).catch((error) => err(this.handleUnknownError(error)));
65
+ await runFfmpeg([
66
+ "-i",
67
+ input,
68
+ "-vn",
69
+ "-c:a",
70
+ "pcm_s16le",
71
+ "-ar",
72
+ String(hz),
73
+ "-ac",
74
+ "2",
75
+ "-f",
76
+ "wav",
77
+ "-y",
78
+ output
79
+ ]).catch((error) => {
80
+ throw this.handleUnknownError(error);
81
+ });
33
82
  return ok(output);
34
83
  });
35
84
  }
@@ -37,9 +86,20 @@ var VideoService = class extends BaseService {
37
86
  return this.throwableAsync(async () => {
38
87
  const output = path.join(uploadsDir, `${v4()}.mp3`);
39
88
  if (!existsSync(output)) closeSync(openSync(output, "w"));
40
- await new Promise((resolve, reject) => {
41
- ffmpeg(input).outputOptions(["-y", `-map 0:a:${streamIndex}`]).audioCodec("libmp3lame").audioBitrate(kbps).on("end", () => resolve()).on("error", reject).save(output);
42
- }).catch((error) => err(this.handleUnknownError(error)));
89
+ await runFfmpeg([
90
+ "-i",
91
+ input,
92
+ "-map",
93
+ `0:a:${streamIndex}`,
94
+ "-c:a",
95
+ "libmp3lame",
96
+ "-b:a",
97
+ `${kbps}k`,
98
+ "-y",
99
+ output
100
+ ]).catch((error) => {
101
+ throw this.handleUnknownError(error);
102
+ });
43
103
  return ok(output);
44
104
  });
45
105
  }
@@ -1 +1 @@
1
- {"version":3,"file":"video.service.mjs","names":["uuidv4"],"sources":["../../../../src/modules/video/video.service.ts"],"sourcesContent":["import { closeSync, existsSync, mkdirSync, openSync } from \"node:fs\";\r\nimport path from \"node:path\";\r\n//\r\nimport ffbin from \"ffmpeg-ffprobe-static\";\r\nimport ffmpeg from \"fluent-ffmpeg\";\r\nimport { err, ok } from \"neverthrow\";\r\nimport { v4 as uuidv4 } from \"uuid\";\r\nimport type { ServerResultAsync } from \"../base/base.dto\";\r\nimport { BaseService } from \"../base/base.service\";\r\n\r\nif (!ffbin.ffmpegPath || !ffbin.ffprobePath) {\r\n throw new Error(\"FFmpeg or FFprobe not found\");\r\n}\r\n\r\nffmpeg.setFfmpegPath(ffbin.ffmpegPath);\r\nffmpeg.setFfprobePath(ffbin.ffprobePath);\r\n\r\nconst uploadsDir = path.join(__dirname, \"..\", \"uploads\");\r\nif (!existsSync(uploadsDir)) {\r\n mkdirSync(uploadsDir, { recursive: true });\r\n}\r\n\r\nexport class VideoService extends BaseService<never, never> {\r\n async cut(file: string, start: number, end: number): ServerResultAsync<string> {\r\n return this.throwableAsync(async () => {\r\n const duration = end - start;\r\n const output = path.join(uploadsDir, `${uuidv4()}.mp4`);\r\n if (!existsSync(output)) {\r\n closeSync(openSync(output, \"w\"));\r\n }\r\n\r\n await new Promise<void>((resolve, reject) => {\r\n ffmpeg(file)\r\n .seekOutput(start)\r\n .videoCodec(\"libx264\")\r\n .audioCodec(\"copy\")\r\n .outputOptions([\"-y\", \"-movflags +faststart\"])\r\n .duration(duration)\r\n .on(\"end\", () => resolve())\r\n .on(\"error\", (e: Error, _stdout: string | null, _stderr: string | null) => reject(e))\r\n .save(output);\r\n }).catch((error) => err(this.handleUnknownError(error)));\r\n\r\n return ok(output);\r\n });\r\n }\r\n\r\n async webmToWav(input: string, hz = 48000): ServerResultAsync<string> {\r\n return this.throwableAsync(async () => {\r\n const output = path.join(uploadsDir, `${uuidv4()}.wav`);\r\n if (!existsSync(output)) {\r\n closeSync(openSync(output, \"w\"));\r\n }\r\n await new Promise<void>((resolve, reject) => {\r\n ffmpeg(input)\r\n .noVideo()\r\n .audioCodec(\"pcm_s16le\") // WAV PCM 16-bit\r\n .audioFrequency(hz) // 48000 or 44100\r\n .audioChannels(2) // down/up-mix as needed\r\n .format(\"wav\")\r\n .outputOptions([\"-y\"])\r\n .on(\"end\", () => resolve())\r\n .on(\"error\", reject)\r\n .save(output);\r\n }).catch((error) => err(this.handleUnknownError(error)));\r\n return ok(output);\r\n });\r\n }\r\n\r\n async extractAudioMp3(input: string, kbps = 192, streamIndex = 0): ServerResultAsync<string> {\r\n return this.throwableAsync(async () => {\r\n const output = path.join(uploadsDir, `${uuidv4()}.mp3`);\r\n if (!existsSync(output)) {\r\n closeSync(openSync(output, \"w\"));\r\n }\r\n await new Promise<void>((resolve, reject) => {\r\n ffmpeg(input)\r\n .outputOptions([\"-y\", `-map 0:a:${streamIndex}`])\r\n .audioCodec(\"libmp3lame\")\r\n .audioBitrate(kbps)\r\n .on(\"end\", () => resolve())\r\n .on(\"error\", reject)\r\n .save(output);\r\n }).catch((error) => err(this.handleUnknownError(error)));\r\n\r\n return ok(output);\r\n });\r\n }\r\n}\r\n"],"mappings":";;;;;;;;AAUA,IAAI,CAAC,MAAM,cAAc,CAAC,MAAM,YAC9B,OAAM,IAAI,MAAM,8BAA8B;AAGhD,OAAO,cAAc,MAAM,WAAW;AACtC,OAAO,eAAe,MAAM,YAAY;AAExC,MAAM,aAAa,KAAK,KAAK,WAAW,MAAM,UAAU;AACxD,IAAI,CAAC,WAAW,WAAW,CACzB,WAAU,YAAY,EAAE,WAAW,MAAM,CAAC;AAG5C,IAAa,eAAb,cAAkC,YAA0B;CAC1D,MAAM,IAAI,MAAc,OAAe,KAAwC;AAC7E,SAAO,KAAK,eAAe,YAAY;GACrC,MAAM,WAAW,MAAM;GACvB,MAAM,SAAS,KAAK,KAAK,YAAY,GAAGA,IAAQ,CAAC,MAAM;AACvD,OAAI,CAAC,WAAW,OAAO,CACrB,WAAU,SAAS,QAAQ,IAAI,CAAC;AAGlC,SAAM,IAAI,SAAe,SAAS,WAAW;AAC3C,WAAO,KAAK,CACT,WAAW,MAAM,CACjB,WAAW,UAAU,CACrB,WAAW,OAAO,CAClB,cAAc,CAAC,MAAM,uBAAuB,CAAC,CAC7C,SAAS,SAAS,CAClB,GAAG,aAAa,SAAS,CAAC,CAC1B,GAAG,UAAU,GAAU,SAAwB,YAA2B,OAAO,EAAE,CAAC,CACpF,KAAK,OAAO;KACf,CAAC,OAAO,UAAU,IAAI,KAAK,mBAAmB,MAAM,CAAC,CAAC;AAExD,UAAO,GAAG,OAAO;IACjB;;CAGJ,MAAM,UAAU,OAAe,KAAK,MAAkC;AACpE,SAAO,KAAK,eAAe,YAAY;GACrC,MAAM,SAAS,KAAK,KAAK,YAAY,GAAGA,IAAQ,CAAC,MAAM;AACvD,OAAI,CAAC,WAAW,OAAO,CACrB,WAAU,SAAS,QAAQ,IAAI,CAAC;AAElC,SAAM,IAAI,SAAe,SAAS,WAAW;AAC3C,WAAO,MAAM,CACV,SAAS,CACT,WAAW,YAAY,CACvB,eAAe,GAAG,CAClB,cAAc,EAAE,CAChB,OAAO,MAAM,CACb,cAAc,CAAC,KAAK,CAAC,CACrB,GAAG,aAAa,SAAS,CAAC,CAC1B,GAAG,SAAS,OAAO,CACnB,KAAK,OAAO;KACf,CAAC,OAAO,UAAU,IAAI,KAAK,mBAAmB,MAAM,CAAC,CAAC;AACxD,UAAO,GAAG,OAAO;IACjB;;CAGJ,MAAM,gBAAgB,OAAe,OAAO,KAAK,cAAc,GAA8B;AAC3F,SAAO,KAAK,eAAe,YAAY;GACrC,MAAM,SAAS,KAAK,KAAK,YAAY,GAAGA,IAAQ,CAAC,MAAM;AACvD,OAAI,CAAC,WAAW,OAAO,CACrB,WAAU,SAAS,QAAQ,IAAI,CAAC;AAElC,SAAM,IAAI,SAAe,SAAS,WAAW;AAC3C,WAAO,MAAM,CACV,cAAc,CAAC,MAAM,YAAY,cAAc,CAAC,CAChD,WAAW,aAAa,CACxB,aAAa,KAAK,CAClB,GAAG,aAAa,SAAS,CAAC,CAC1B,GAAG,SAAS,OAAO,CACnB,KAAK,OAAO;KACf,CAAC,OAAO,UAAU,IAAI,KAAK,mBAAmB,MAAM,CAAC,CAAC;AAExD,UAAO,GAAG,OAAO;IACjB"}
1
+ {"version":3,"file":"video.service.mjs","names":["uuidv4"],"sources":["../../../../src/modules/video/video.service.ts"],"sourcesContent":["import { closeSync, existsSync, mkdirSync, openSync } from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport { spawn } from \"node:child_process\";\r\n//\r\nimport ffbin from \"ffmpeg-ffprobe-static\";\r\nimport { err, ok } from \"neverthrow\";\r\nimport { v4 as uuidv4 } from \"uuid\";\r\nimport type { ServerResultAsync } from \"../base/base.dto\";\r\nimport { BaseService } from \"../base/base.service\";\r\n\r\nif (!ffbin.ffmpegPath || !ffbin.ffprobePath) {\r\n throw new Error(\"FFmpeg or FFprobe not found\");\r\n}\r\n\r\nconst uploadsDir = path.join(__dirname, \"..\", \"uploads\");\r\nif (!existsSync(uploadsDir)) {\r\n mkdirSync(uploadsDir, { recursive: true });\r\n}\r\n\r\nconst runFfmpeg = async (args: readonly string[]): Promise<void> => {\r\n await new Promise<void>((resolve, reject) => {\r\n const child = spawn(ffbin.ffmpegPath as string, [...args], {\r\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\r\n });\r\n\r\n let stderr = \"\";\r\n child.stderr?.setEncoding(\"utf8\");\r\n child.stderr?.on(\"data\", (chunk: string) => {\r\n stderr += chunk;\r\n });\r\n\r\n child.on(\"error\", (error) => reject(error));\r\n child.on(\"close\", (code) => {\r\n if (code === 0) {\r\n resolve();\r\n return;\r\n }\r\n reject(new Error(stderr || `ffmpeg exited with code ${code ?? \"unknown\"}`));\r\n });\r\n });\r\n};\r\n\r\nexport class VideoService extends BaseService<never, never> {\r\n async cut(file: string, start: number, end: number): ServerResultAsync<string> {\r\n return this.throwableAsync(async () => {\r\n const duration = end - start;\r\n const output = path.join(uploadsDir, `${uuidv4()}.mp4`);\r\n if (!existsSync(output)) {\r\n closeSync(openSync(output, \"w\"));\r\n }\r\n\r\n await runFfmpeg([\r\n \"-i\",\r\n file,\r\n \"-ss\",\r\n String(start),\r\n \"-t\",\r\n String(duration),\r\n \"-c:v\",\r\n \"libx264\",\r\n \"-c:a\",\r\n \"copy\",\r\n \"-movflags\",\r\n \"+faststart\",\r\n \"-y\",\r\n output,\r\n ]).catch((error) => {\r\n throw this.handleUnknownError(error);\r\n });\r\n\r\n return ok(output);\r\n });\r\n }\r\n\r\n async webmToWav(input: string, hz = 48000): ServerResultAsync<string> {\r\n return this.throwableAsync(async () => {\r\n const output = path.join(uploadsDir, `${uuidv4()}.wav`);\r\n if (!existsSync(output)) {\r\n closeSync(openSync(output, \"w\"));\r\n }\r\n\r\n await runFfmpeg([\r\n \"-i\",\r\n input,\r\n \"-vn\",\r\n \"-c:a\",\r\n \"pcm_s16le\",\r\n \"-ar\",\r\n String(hz),\r\n \"-ac\",\r\n \"2\",\r\n \"-f\",\r\n \"wav\",\r\n \"-y\",\r\n output,\r\n ]).catch((error) => {\r\n throw this.handleUnknownError(error);\r\n });\r\n return ok(output);\r\n });\r\n }\r\n\r\n async extractAudioMp3(input: string, kbps = 192, streamIndex = 0): ServerResultAsync<string> {\r\n return this.throwableAsync(async () => {\r\n const output = path.join(uploadsDir, `${uuidv4()}.mp3`);\r\n if (!existsSync(output)) {\r\n closeSync(openSync(output, \"w\"));\r\n }\r\n await runFfmpeg([\r\n \"-i\",\r\n input,\r\n \"-map\",\r\n `0:a:${streamIndex}`,\r\n \"-c:a\",\r\n \"libmp3lame\",\r\n \"-b:a\",\r\n `${kbps}k`,\r\n \"-y\",\r\n output,\r\n ]).catch((error) => {\r\n throw this.handleUnknownError(error);\r\n });\r\n\r\n return ok(output);\r\n });\r\n }\r\n}\r\n"],"mappings":";;;;;;;;AAUA,IAAI,CAAC,MAAM,cAAc,CAAC,MAAM,YAC9B,OAAM,IAAI,MAAM,8BAA8B;AAGhD,MAAM,aAAa,KAAK,KAAK,WAAW,MAAM,UAAU;AACxD,IAAI,CAAC,WAAW,WAAW,CACzB,WAAU,YAAY,EAAE,WAAW,MAAM,CAAC;AAG5C,MAAM,YAAY,OAAO,SAA2C;AAClE,OAAM,IAAI,SAAe,SAAS,WAAW;EAC3C,MAAM,QAAQ,MAAM,MAAM,YAAsB,CAAC,GAAG,KAAK,EAAE,EACzD,OAAO;GAAC;GAAU;GAAU;GAAO,EACpC,CAAC;EAEF,IAAI,SAAS;AACb,QAAM,QAAQ,YAAY,OAAO;AACjC,QAAM,QAAQ,GAAG,SAAS,UAAkB;AAC1C,aAAU;IACV;AAEF,QAAM,GAAG,UAAU,UAAU,OAAO,MAAM,CAAC;AAC3C,QAAM,GAAG,UAAU,SAAS;AAC1B,OAAI,SAAS,GAAG;AACd,aAAS;AACT;;AAEF,UAAO,IAAI,MAAM,UAAU,2BAA2B,QAAQ,YAAY,CAAC;IAC3E;GACF;;AAGJ,IAAa,eAAb,cAAkC,YAA0B;CAC1D,MAAM,IAAI,MAAc,OAAe,KAAwC;AAC7E,SAAO,KAAK,eAAe,YAAY;GACrC,MAAM,WAAW,MAAM;GACvB,MAAM,SAAS,KAAK,KAAK,YAAY,GAAGA,IAAQ,CAAC,MAAM;AACvD,OAAI,CAAC,WAAW,OAAO,CACrB,WAAU,SAAS,QAAQ,IAAI,CAAC;AAGlC,SAAM,UAAU;IACd;IACA;IACA;IACA,OAAO,MAAM;IACb;IACA,OAAO,SAAS;IAChB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACD,CAAC,CAAC,OAAO,UAAU;AAClB,UAAM,KAAK,mBAAmB,MAAM;KACpC;AAEF,UAAO,GAAG,OAAO;IACjB;;CAGJ,MAAM,UAAU,OAAe,KAAK,MAAkC;AACpE,SAAO,KAAK,eAAe,YAAY;GACrC,MAAM,SAAS,KAAK,KAAK,YAAY,GAAGA,IAAQ,CAAC,MAAM;AACvD,OAAI,CAAC,WAAW,OAAO,CACrB,WAAU,SAAS,QAAQ,IAAI,CAAC;AAGlC,SAAM,UAAU;IACd;IACA;IACA;IACA;IACA;IACA;IACA,OAAO,GAAG;IACV;IACA;IACA;IACA;IACA;IACA;IACD,CAAC,CAAC,OAAO,UAAU;AAClB,UAAM,KAAK,mBAAmB,MAAM;KACpC;AACF,UAAO,GAAG,OAAO;IACjB;;CAGJ,MAAM,gBAAgB,OAAe,OAAO,KAAK,cAAc,GAA8B;AAC3F,SAAO,KAAK,eAAe,YAAY;GACrC,MAAM,SAAS,KAAK,KAAK,YAAY,GAAGA,IAAQ,CAAC,MAAM;AACvD,OAAI,CAAC,WAAW,OAAO,CACrB,WAAU,SAAS,QAAQ,IAAI,CAAC;AAElC,SAAM,UAAU;IACd;IACA;IACA;IACA,OAAO;IACP;IACA;IACA;IACA,GAAG,KAAK;IACR;IACA;IACD,CAAC,CAAC,OAAO,UAAU;AAClB,UAAM,KAAK,mBAAmB,MAAM;KACpC;AAEF,UAAO,GAAG,OAAO;IACjB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m5kdev/backend",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "description": "Composable Express server stack with Drizzle ORM and tRPC.",
5
5
  "license": "GPL-3.0-only",
6
6
  "repository": {
@@ -37,7 +37,6 @@
37
37
  "drizzle-zod": "0.8.2",
38
38
  "express": "4.21.2",
39
39
  "ffmpeg-ffprobe-static": "6.1.2-rc.1",
40
- "fluent-ffmpeg": "2.1.3",
41
40
  "ioredis": "5.7.0",
42
41
  "jsonrepair": "^3.13.3",
43
42
  "luxon": "3.7.1",
@@ -59,15 +58,14 @@
59
58
  "trpc-to-openapi": "2.3.0",
60
59
  "uuid": "11.0.5",
61
60
  "zod": "4.2.1",
62
- "@m5kdev/commons": "0.8.5",
63
- "@m5kdev/config": "0.8.5"
61
+ "@m5kdev/commons": "0.8.7",
62
+ "@m5kdev/config": "0.8.7"
64
63
  },
65
64
  "devDependencies": {
66
65
  "@jest/globals": "30.2.0",
67
66
  "@types/body-parser": "1.19.5",
68
67
  "@types/cors": "2.8.17",
69
68
  "@types/express": "4.17.21",
70
- "@types/fluent-ffmpeg": "2.1.27",
71
69
  "@types/jest": "30.0.0",
72
70
  "@types/luxon": "3.7.1",
73
71
  "@types/mustache": "4.2.6",