#Passing Arguments

17 messages ยท Page 1 of 1 (latest)

north pawn
#

Hey all, finally found more time to learn Convex. I'm stuck on passing an argument, where for example, I have a table full of email addresses that acts as a whitelist - and I'd like my server to pass an email string to the function, Convex check the email does/doesn't exist, and return true/false.

Here's my server app and my functions;

//server.mjs
import { ConvexHttpClient } from "convex/browser";
import { api } from "./convex/_generated/api.js";
import * as dotenv from "dotenv";
dotenv.config({ path: ".env.local" });

const client = new ConvexHttpClient(process.env["CONVEX_URL"]);

client.query(api.whitelistFunctions.get).then(console.log);

client
    .query(api.whitelistFunctions.checkEmail, { email: "sample@email.com" })
    .then(console.log); // This will print true if the email exists, otherwise false

And my whitelistFunctions.js

//whitelistFunctions.js
import { query } from "./_generated/server";
import { v } from "convex/values";

export const get = query({
    args: {},
    handler: async (ctx) => {
        return await ctx.db.query("whitelist").collect();
    },
});

export const checkEmail = query({
    args: { email: v.string() },

    handler: async (ctx) => {
        if (!ctx.args.email) {
            throw new Error("Email argument is missing or undefined");
        }
        return await ctx.args.email;
    },
});

Get the error

Error: Uncaught TypeError: Cannot read properties of undefined (reading 'email')
    at handler (../convex/whitelistFunctions.js:15:13)

    at ConvexHttpClient.query (file:///C:/DEV/VS%20Code/Convex/karlstensWebsite/node_modules/convex/dist/esm/browser/http_client.js:122:15)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

*I should note that I started to simplify/hack checkEmail right back to the bone, so that essentially I was just returning the passed argument - but still couldn't figure it out.

pale tendon
#

Instead of as a property on ctx, args are passed as a second argument to the Convex function

#
handler: async (ctx, args) => {
        if (!args.email) {

instead of

handler: async (ctx) => {
        if (!ctx.args.email) {
north pawn
#

yah, I think I got this sorted....

export const checkEmail = query({
    args: { email: v.string() },

    handler: async (_, args) => {
        if (!args.email) {
            throw new Error("Email argument is missing or undefined");
        }
        return await args.email;
    },
});
#

So now that I have the argument passing - I'll find some time after cooking dinner to write the function to check the email passed in the argument against all emails in the table, and return true of false if it's found.

I have a working example done in Airatble, but differently - where I query my Airtable from my server via a CURL containing a formula query. I'm not sure if the same thing can be done in Convex? Or if, I'm just writing the Convex cloud function to do the specific task of finding/matching the given email.

pale tendon
north pawn
#

Cool, so I'm on the right track ๐Ÿ™‚

#

I wish you could poke chatGPT with all your updated docs - would make learning a bit quicker. ๐Ÿ˜› But I say that about everything now. If only OpenAI could cook my dinner too ๐Ÿ˜ฆ

pale tendon
#

You'll likely end up doing something like this:

  • write a query that returns whether an email is in the whitelist
  • write an action that takes the argument, calls the query to look for the email, reads the result and then makes a fetch() or use some JavaScript API to send the email.

something like

import { action, query } from "./_generated/server";

import postmark from "postmark";
import { v } from "convex/values";

export default action(async (ctx, { email }: { email: string }) => {
  const client = new postmark.ServerClient(
    "acbdef-asdfa-asdf-adsfasdf"
  );

  if (!ctx.runQuery(api.whitelistFunctions.emailAllowed, { email: email })) {
    throw new Error("that email isn't in the whitelist");
  }

  const result = await client.sendEmail({
    From: "me@gmail.com",
    To: email,
    Subject: "Hello from Postmark",
    HtmlBody: "<strong>Hello</strong> there!",
    TextBody: `Hello from Tom!`,
    MessageStream: "outbound",
  });
  console.log("email sent...");
  console.log(result);
});

export const emailAllowed = query({
  args: { email: v.string() },
  handler: async (ctx) => {
    const whitelist = ctx.db.query("whitelist").collect();
    const emails = whitelist.map((record) => record.email);
    return emails.includes(email);
  },
});
#

(postmark is just an example, one of many client libraries used to send emails)

north pawn
#

Ok, so I'm starting to understand Convex, and I'm really excited by this.

Here's my cloud function;

import { query } from "./_generated/server";
import { v } from "convex/values";

export const checkEmail = query({
    args: { email: v.string() },

    handler: async (ctx, args) => {
        const emailValid = await ctx.db
            .query("whitelist")
            .filter((q) => q.eq(q.field("Email"), args.email))
            .first();
        return emailValid;
    },
});

//... more functions, MORE POWER! ๐Ÿ™Š๐Ÿ™‰๐Ÿ™ˆ

And I set my server with promise.allSettled()

server.mjs

import { ConvexHttpClient } from "convex/browser";
import { api } from "./convex/_generated/api.js";
import * as dotenv from "dotenv";
dotenv.config({ path: ".env.local" });

const client = new ConvexHttpClient(process.env["CONVEX_URL"]);

Promise.allSettled([
    //client.query(api.whitelistFunctions.get),
    // ... more client.queries as needed
    client.query(api.whitelistFunctions.checkEmail, {
        email: "janesmith@gmail.com",
    }),
]).then((results) => {
    results.forEach((result) => {
        if (result.status === "fulfilled") {
            console.log(result.value);
        } else {
            console.error("Promise was rejected:", result.reason);
        }
    });
});

And it... just works! I'm really sold on Convex, I can't thank you enough Tom.

north pawn
#

One last question, to check against multiple fields, is it typical to stack filters() as such? Technically it works, but I do like to check my methods...

export const checkEmailValid = query({
    args: { email: v.string() },

    handler: async (ctx, args) => {
        const emailValid = await ctx.db
            .query("whitelist")
            .filter((q) => q.eq(q.field("Email"), args.email))
            .filter((q) => q.eq(q.field("Enabled"), true))
            .first();
        return !!emailValid;
    },
});
north pawn
#

One side question I have, is that I'm reading about limits - that filter is limited to 16384 documents/records.

I'm not fully up-to-scratch on table limits and batch queries, but say for example, I had 20,000 email addresses, and I had to query for an existing email in that 20,000 - how do I build a query function to cater for a large table beyond 16384 documents? I started reading about indexing, but although I could get my Table schema, I wasn't too sure on how to update it with the indexing mechanism.

#

Nor then how to update my query to leverage the index (which... I'm guessing resolves querying a table with more than 16384 documents?)

#

(oh gawd, I just realised, I'm that "one last question..." guy. I'm so sorry!)

umbral urchin