Skip to content

Instantly share code, notes, and snippets.

@tomredman
Created March 13, 2026 17:17
Show Gist options
  • Select an option

  • Save tomredman/df6edb966b29d149db0e3aa1e53de275 to your computer and use it in GitHub Desktop.

Select an option

Save tomredman/df6edb966b29d149db0e3aa1e53de275 to your computer and use it in GitHub Desktop.
Example of using Convex and Cloudflare workers/queues

I create public functions for workers to consume, authenticated by a shared token you generate and store on Convex & Cloudflare:

// convex/fub/worker/api.ts
// consumable by a CF worker
export const getSyncProgress = query({
  args: {
    ...secretArg,
    progressId: v.string(),
  },
  returns: v.any(),
  handler: async (ctx, args) => {
    validateSecret(args.secret);
    return await ctx.db.get(args.progressId as Id<"fubListSyncProgress">);
  },
});
// trigger a CF worker/queue/etc.
export const triggerFubSyncForTeams = internalAction({
  args: {
    teamIds: v.array(v.id("teams")),
  },
  handler: async (ctx, args) => {
    const accountId = process.env.CF_ACCOUNT_ID!;
    const queueId = process.env.CF_FUB_SYNC_QUEUE_ID!;

    const client = new Cloudflare({
      apiToken: process.env.CF_WORKERS_TOKEN!,
    });

    for (const teamId of args.teamIds) {
      await client.queues.messages.push(queueId, {
        account_id: accountId,
        body: {
          type: "sync-init" as const,
          teamId,
          requestedAt: Date.now(),
        },
      });
    }
  },
});

And use it in the Worker:

// cloudflare/workers/sync/src/index.ts
import { ConvexClient } from "./lib/convex-client";

const convex = new ConvexClient(c.env);
const progress = await convex.query("fub/worker/api:getSyncProgress", {
    progressId,
  });
// workers/src/lib/convex-client.ts
import { Env } from './env';

/**
 * Convex HTTP client helper for calling public mutations/queries.
 * Uses the shared secret for authentication.
 */

type ConvexFunctionArgs = Record<string, unknown>;

export class ConvexClient {
	private baseUrl: string;
	private secret: string;

	constructor(env: Env) {
		this.baseUrl = env.CONVEX_URL;
		this.secret = env.CF_WORKER_SHARED_SECRET;
	}

	/**
	 * Call a Convex mutation via HTTP.
	 */
	async mutation<T = unknown>(path: string, args: ConvexFunctionArgs = {}): Promise<T> {
		return this.call<T>('mutation', path, args);
	}

	/**
	 * Call a Convex query via HTTP.
	 */
	async query<T = unknown>(path: string, args: ConvexFunctionArgs = {}): Promise<T> {
		return this.call<T>('query', path, args);
	}

	private async call<T>(type: 'query' | 'mutation', path: string, args: ConvexFunctionArgs): Promise<T> {
		// Add secret to args for authentication
		const argsWithSecret = {
			...args,
			secret: this.secret,
		};

		const url = `${this.baseUrl}/api/${type}`;

		const response = await fetch(url, {
			method: 'POST',
			headers: {
				'Content-Type': 'application/json',
			},
			body: JSON.stringify({
				path,
				args: argsWithSecret,
				format: 'json',
			}),
		});

		if (!response.ok) {
			const errorText = await response.text();
			throw new Error(`Convex ${type} ${path} failed: ${response.status} ${errorText}`);
		}

		const result = (await response.json()) as { status: string; value?: T; errorMessage?: string };

		// Convex HTTP API returns { status: "success", value: T } or { status: "error", errorMessage: string }
		if (result.status === 'error') {
			throw new Error(`Convex ${type} ${path} error: ${result.errorMessage}`);
		}

		return result.value as T;
	}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment