@trigger.dev/convex 0.0.1 → 4.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ export declare const tick: import("convex/server").RegisteredMutation<"internal", {
2
+ generation: number;
3
+ id: import("convex/values").GenericId<"runs">;
4
+ }, Promise<null>>;
5
+ export declare const step: import("convex/server").RegisteredAction<"internal", {
6
+ generation: number;
7
+ id: import("convex/values").GenericId<"runs">;
8
+ }, Promise<null>>;
9
+ export declare const recordError: import("convex/server").RegisteredMutation<"internal", {
10
+ generation: number;
11
+ id: import("convex/values").GenericId<"runs">;
12
+ message: string;
13
+ permanent: boolean;
14
+ }, Promise<null>>;
@@ -0,0 +1,118 @@
1
+ import { v } from "convex/values";
2
+ import { internal } from "./api.js";
3
+ import { internalAction, internalMutation } from "./server.js";
4
+ import { pause, scheduleRefresh } from "./runs.js";
5
+ import { ApiError, inlineValue, request } from "./apiClient.js";
6
+ import { IDEMPOTENCY_TTL, MAX_SYNC_FAILURES, REQUEST_TIMEOUT_MS, SUBMISSION_WINDOW_MS, WATCHDOG_MARGIN_MS, pollDelay, } from "./shared.js";
7
+ const jobArgs = { id: v.id("runs"), generation: v.number() };
8
+ export const tick = internalMutation({
9
+ args: jobArgs,
10
+ returns: v.null(),
11
+ handler: async (ctx, { id, generation }) => {
12
+ const run = await ctx.db.get("runs", id);
13
+ if (!run ||
14
+ run.generation !== generation ||
15
+ run.syncStatus === "complete" ||
16
+ run.syncStatus === "paused")
17
+ return null;
18
+ if (!run.runId && Date.now() - run._creationTime >= SUBMISSION_WINDOW_MS) {
19
+ await pause(ctx, run, "Submission retry window expired; check Trigger before triggering again");
20
+ return null;
21
+ }
22
+ const nextGeneration = generation + 1;
23
+ await ctx.db.patch("runs", id, {
24
+ generation: nextGeneration,
25
+ checks: run.checks + 1,
26
+ checking: true,
27
+ refreshRequested: undefined,
28
+ lastCheckAt: run.runId ? Date.now() : run.lastCheckAt,
29
+ });
30
+ // Schedule the watchdog transactionally before starting an action that can be interrupted.
31
+ await ctx.scheduler.runAfter(Math.max(pollDelay(run.checks), REQUEST_TIMEOUT_MS + WATCHDOG_MARGIN_MS), internal.worker.tick, { id, generation: nextGeneration });
32
+ await ctx.scheduler.runAfter(0, internal.worker.step, { id, generation: nextGeneration });
33
+ return null;
34
+ },
35
+ });
36
+ export const step = internalAction({
37
+ args: jobArgs,
38
+ returns: v.null(),
39
+ handler: async (ctx, { id, generation }) => {
40
+ const loaded = await ctx.runQuery(internal.runs.load, { id });
41
+ if (!loaded ||
42
+ loaded.run.generation !== generation ||
43
+ loaded.run.syncStatus === "complete" ||
44
+ loaded.run.syncStatus === "paused")
45
+ return null;
46
+ const { run, request: saved } = loaded;
47
+ try {
48
+ if (!run.runId) {
49
+ const result = await request(saved.config, `/api/v1/tasks/${encodeURIComponent(run.taskId)}/trigger`, {
50
+ payload: saved.payload,
51
+ options: {
52
+ ...saved.options,
53
+ idempotencyKey: `convex:${id}`,
54
+ idempotencyKeyTTL: IDEMPOTENCY_TTL,
55
+ },
56
+ });
57
+ if (typeof result.id !== "string")
58
+ throw new ApiError("Missing run ID in Trigger.dev response", true);
59
+ await ctx.runMutation(internal.runs.recordSubmission, { id, generation, runId: result.id });
60
+ }
61
+ else {
62
+ const result = await request(saved.config, `/api/v3/runs/${encodeURIComponent(run.runId)}`);
63
+ if (typeof result.status !== "string" || typeof result.isCompleted !== "boolean") {
64
+ throw new ApiError("Missing run status in Trigger.dev response", true);
65
+ }
66
+ const output = inlineValue(result.output);
67
+ await ctx.runMutation(internal.runs.recordStatus, {
68
+ id,
69
+ generation,
70
+ status: result.status,
71
+ completed: result.isCompleted || result.status === "EXPIRED" || result.status === "TIMED_OUT",
72
+ output: output.value,
73
+ outputOmitted: output.omitted,
74
+ outputPresignedUrl: typeof result.outputPresignedUrl === "string" ? result.outputPresignedUrl : undefined,
75
+ error: inlineValue(result.error).value,
76
+ metadata: inlineValue(result.metadata).value,
77
+ });
78
+ }
79
+ }
80
+ catch (error) {
81
+ await ctx.runMutation(internal.worker.recordError, {
82
+ id,
83
+ generation,
84
+ message: error instanceof ApiError
85
+ ? error.message
86
+ : "Run synchronization failed; inspect Convex function logs",
87
+ permanent: error instanceof ApiError && !error.retryable,
88
+ });
89
+ // Preserve diagnostic detail in server logs without including the request config.
90
+ console.error("Trigger.dev synchronization failed", {
91
+ id,
92
+ error: error instanceof Error ? error.name : "UnknownError",
93
+ });
94
+ }
95
+ return null;
96
+ },
97
+ });
98
+ export const recordError = internalMutation({
99
+ args: { ...jobArgs, message: v.string(), permanent: v.boolean() },
100
+ returns: v.null(),
101
+ handler: async (ctx, { id, generation, message, permanent }) => {
102
+ const run = await ctx.db.get("runs", id);
103
+ if (!run || run.generation !== generation || run.syncStatus === "complete")
104
+ return null;
105
+ const failures = run.failures + 1;
106
+ await ctx.db.patch("runs", id, {
107
+ failures,
108
+ syncError: message,
109
+ checking: undefined,
110
+ updatedAt: Date.now(),
111
+ });
112
+ if (permanent || failures >= MAX_SYNC_FAILURES)
113
+ await pause(ctx, run, message);
114
+ else
115
+ await scheduleRefresh(ctx, run);
116
+ return null;
117
+ },
118
+ });
package/package.json CHANGED
@@ -1,16 +1,49 @@
1
1
  {
2
2
  "name": "@trigger.dev/convex",
3
- "version": "0.0.1",
3
+ "version": "4.8.3",
4
4
  "description": "Run Trigger.dev tasks from Convex mutations and query their results reactively",
5
5
  "license": "Apache-2.0",
6
+ "type": "module",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
6
10
  "repository": {
7
11
  "type": "git",
8
12
  "url": "https://github.com/triggerdotdev/trigger.dev",
9
13
  "directory": "packages/convex"
10
14
  },
11
- "homepage": "https://trigger.dev",
12
- "publishConfig": {
13
- "access": "public"
15
+ "files": [
16
+ "dist",
17
+ "src/component",
18
+ "!src/component/**/*.test.ts"
19
+ ],
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/client/index.d.ts",
23
+ "default": "./dist/client/index.js"
24
+ },
25
+ "./convex.config.js": {
26
+ "types": "./dist/component/convex.config.d.ts",
27
+ "default": "./dist/component/convex.config.js"
28
+ },
29
+ "./_generated/component.js": {
30
+ "types": "./dist/component/_generated/component.d.ts"
31
+ },
32
+ "./package.json": "./package.json"
33
+ },
34
+ "peerDependencies": {
35
+ "convex": "^1.48.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "24.13.3",
39
+ "convex": "^1.48.0",
40
+ "convex-test": "^0.0.65",
41
+ "typescript": "7.0.2",
42
+ "vitest": "4.1.11"
14
43
  },
15
- "files": ["README.md"]
16
- }
44
+ "scripts": {
45
+ "build": "rm -rf dist && tsc -p tsconfig.json",
46
+ "test": "vitest run",
47
+ "typecheck": "tsc -p tsconfig.json --noEmit"
48
+ }
49
+ }
@@ -0,0 +1,18 @@
1
+ import type { ApiFromModules, FilterApi, FunctionReference } from "convex/server";
2
+ import type * as runs from "../runs.js";
3
+
4
+ type PublicApi = FilterApi<ApiFromModules<{ runs: typeof runs }>, FunctionReference<any, "public">>;
5
+
6
+ // Derive the component boundary from its functions, so it cannot drift from their validators.
7
+ export type ComponentApi<Name extends string | undefined = string | undefined> = {
8
+ runs: {
9
+ [Key in keyof PublicApi["runs"]]: PublicApi["runs"][Key] extends FunctionReference<
10
+ infer Type,
11
+ any,
12
+ infer Args,
13
+ infer Result
14
+ >
15
+ ? FunctionReference<Type, "internal", Args, Result, Name>
16
+ : never;
17
+ };
18
+ };
@@ -0,0 +1,6 @@
1
+ import { anyApi, type ApiFromModules, type FilterApi, type FunctionReference } from "convex/server";
2
+ import type * as runs from "./runs.js";
3
+ import type * as worker from "./worker.js";
4
+
5
+ type Api = ApiFromModules<{ runs: typeof runs; worker: typeof worker }>;
6
+ export const internal = anyApi as unknown as FilterApi<Api, FunctionReference<any, "internal">>;
@@ -0,0 +1,57 @@
1
+ import { MAX_INLINE_BYTES, REQUEST_TIMEOUT_MS, type Config } from "./shared.js";
2
+
3
+ export class ApiError extends Error {
4
+ constructor(
5
+ message: string,
6
+ public retryable: boolean
7
+ ) {
8
+ super(message);
9
+ }
10
+ }
11
+
12
+ export async function request(
13
+ config: Config,
14
+ path: string,
15
+ body?: unknown
16
+ ): Promise<Record<string, unknown>> {
17
+ const controller = new AbortController();
18
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
19
+ try {
20
+ const response = await fetch(config.baseUrl + path, {
21
+ method: body === undefined ? "GET" : "POST",
22
+ headers: { Authorization: `Bearer ${config.secretKey}`, "Content-Type": "application/json" },
23
+ body: body === undefined ? undefined : JSON.stringify(body),
24
+ signal: controller.signal,
25
+ redirect: "error",
26
+ });
27
+ if (!response.ok) {
28
+ await response.body?.cancel();
29
+ const retryHeader = response.headers.get("x-should-retry");
30
+ // Never persist response bodies, which could contain credentials or payloads.
31
+ throw new ApiError(
32
+ `Trigger.dev API returned HTTP ${response.status}`,
33
+ retryHeader === "true" ||
34
+ (retryHeader !== "false" &&
35
+ (response.status === 408 ||
36
+ response.status === 409 ||
37
+ response.status === 429 ||
38
+ response.status >= 500))
39
+ );
40
+ }
41
+ const data: unknown = await response.json();
42
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
43
+ throw new ApiError("Invalid Trigger.dev response", true);
44
+ }
45
+ return data as Record<string, unknown>;
46
+ } finally {
47
+ clearTimeout(timeout);
48
+ }
49
+ }
50
+
51
+ export function inlineValue(value: unknown): { value?: any; omitted: boolean } {
52
+ if (value === undefined) return { omitted: false };
53
+ if (new TextEncoder().encode(JSON.stringify(value)).byteLength > MAX_INLINE_BYTES) {
54
+ return { omitted: true };
55
+ }
56
+ return { value, omitted: false };
57
+ }
@@ -0,0 +1,3 @@
1
+ import { defineComponent } from "convex/server";
2
+
3
+ export default defineComponent("trigger");