Tom Redman
Tom Redman3w ago

Could not resolve "crypto" (or "node:crypto")?

[ERROR] Could not resolve "node:crypto"

convex/workpools/queues.nobundle.ts:24:27:
24 │ import { createHash } from "node:crypto";
~~~~~~~~~~~~~

The package "node:crypto" wasn't found on the file system but is built into node. Are you
trying to bundle for node? You can use "platform: 'node'" to do that, which will remove this
error.

It looks like you are using Node APIs from a file without the "use node" directive.
Split out actions using Node.js APIs like this into a new file only containing actions that uses "use node" so these actions will run in a Node.js environment.
For more information see https://docs.convex.dev/functions/runtimes#nodejs-runtime
[ERROR] Could not resolve "node:crypto"

convex/workpools/queues.nobundle.ts:24:27:
24 │ import { createHash } from "node:crypto";
~~~~~~~~~~~~~

The package "node:crypto" wasn't found on the file system but is built into node. Are you
trying to bundle for node? You can use "platform: 'node'" to do that, which will remove this
error.

It looks like you are using Node APIs from a file without the "use node" directive.
Split out actions using Node.js APIs like this into a new file only containing actions that uses "use node" so these actions will run in a Node.js environment.
For more information see https://docs.convex.dev/functions/runtimes#nodejs-runtime
Yet, here is my file:
/**
* Deterministically bucket any string into [1..10] using a SHA-256 hash.
* @param input - The string to bucket. Often a Convex document _id
* @param salt - An optional salt to add to the input. Salt lets you "reshuffle"
* buckets without changing inputs (use a constant like a namespace or version).
* @returns A number between 1 and 10
*/

"use node";
import { createHash } from "node:crypto";

/** Deterministically bucket any string into [1..n] */
export function bucket1toN(input: string, salt = "", n: number): number {
// Hash (optionally namespaced via `salt`)
const digest = createHash("sha256")
.update(salt)
.update("\0")
.update(input)
.digest();

// Use 32-bit words; reject the tiny top range to remove modulo bias
const N = 2 ** 32;
const limit = Math.floor(N / n) * n; // 4,294,967,290

for (let i = 0; i < digest.length; i += 4) {
const word = digest.readUInt32BE(i);
if (word < limit) return (word % n) + 1;
}

// Vanishingly rare fallback: rehash deterministically and try again
const d2 = createHash("sha256").update(digest).update("!").digest();
const w2 = d2.readUInt32BE(0);
return ((w2 % limit) % n) + 1;
}
/**
* Deterministically bucket any string into [1..10] using a SHA-256 hash.
* @param input - The string to bucket. Often a Convex document _id
* @param salt - An optional salt to add to the input. Salt lets you "reshuffle"
* buckets without changing inputs (use a constant like a namespace or version).
* @returns A number between 1 and 10
*/

"use node";
import { createHash } from "node:crypto";

/** Deterministically bucket any string into [1..n] */
export function bucket1toN(input: string, salt = "", n: number): number {
// Hash (optionally namespaced via `salt`)
const digest = createHash("sha256")
.update(salt)
.update("\0")
.update(input)
.digest();

// Use 32-bit words; reject the tiny top range to remove modulo bias
const N = 2 ** 32;
const limit = Math.floor(N / n) * n; // 4,294,967,290

for (let i = 0; i < digest.length; i += 4) {
const word = digest.readUInt32BE(i);
if (word < limit) return (word % n) + 1;
}

// Vanishingly rare fallback: rehash deterministically and try again
const d2 = createHash("sha256").update(digest).update("!").digest();
const w2 = d2.readUInt32BE(0);
return ((w2 % limit) % n) + 1;
}
12 Replies
Convex Bot
Convex Bot3w ago
Thanks for posting in <#1088161997662724167>. Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets. - Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.) - Use search.convex.dev to search Docs, Stack, and Discord all at once. - Additionally, you can post your questions in the Convex Community's <#1228095053885476985> channel to receive a response from AI. - Avoid tagging staff unless specifically instructed. Thank you!
Tom Redman
Tom RedmanOP3w ago
'crypto' is part of the Convex runtime, yet it seems I still need to use 'use node'? Further, that also doesn't seem to work, as I get the error above. I really don't want to have to create an action just to turn a string into a number – feels like the right thing for a plain old function. I've tried: - adding .nobundle.ts to the filename - using crypto and node:crypto - adding "use node"; everywhere that uses this I just want to make a string a number 😭 Switching to subtleCrypto I'm not getting the errors anymore:
/** Deterministically bucket any string into [1..n] in the browser (unbiased). */
export async function bucketN(
input: string,
n: number,
salt = ""
): Promise<number> {
if (!Number.isInteger(n) || n < 1) throw new RangeError("n must be >= 1");
if (n === 1) return 1;

const enc = new TextEncoder();
const msg = concatUint8([
enc.encode(salt),
new Uint8Array([0]),
enc.encode(input),
]);
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", msg));

// Rejection sampling over 32-bit words to remove modulo bias
const N = 2 ** 32;
const limit = Math.floor(N / n) * n; // largest multiple of n below 2^32
const dv = new DataView(digest.buffer, digest.byteOffset, digest.byteLength);
for (let i = 0; i + 4 <= digest.length; i += 4) {
const word = dv.getUint32(i, false); // big-endian
if (word < limit) return (word % n) + 1;
}

// Vanishingly rare: rehash deterministically and try again
const d2 = new Uint8Array(
await crypto.subtle.digest(
"SHA-256",
concatUint8([digest, enc.encode("!")])
)
);
const w2 = new DataView(d2.buffer, d2.byteOffset, d2.byteLength).getUint32(
0,
false
);
return (w2 % n) + 1;
}

function concatUint8(arrays: Uint8Array[]): Uint8Array {
const len = arrays.reduce((s, a) => s + a.length, 0);
const out = new Uint8Array(len);
let off = 0;
for (const a of arrays) {
out.set(a, off);
off += a.length;
}
return out;
}
/** Deterministically bucket any string into [1..n] in the browser (unbiased). */
export async function bucketN(
input: string,
n: number,
salt = ""
): Promise<number> {
if (!Number.isInteger(n) || n < 1) throw new RangeError("n must be >= 1");
if (n === 1) return 1;

const enc = new TextEncoder();
const msg = concatUint8([
enc.encode(salt),
new Uint8Array([0]),
enc.encode(input),
]);
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", msg));

// Rejection sampling over 32-bit words to remove modulo bias
const N = 2 ** 32;
const limit = Math.floor(N / n) * n; // largest multiple of n below 2^32
const dv = new DataView(digest.buffer, digest.byteOffset, digest.byteLength);
for (let i = 0; i + 4 <= digest.length; i += 4) {
const word = dv.getUint32(i, false); // big-endian
if (word < limit) return (word % n) + 1;
}

// Vanishingly rare: rehash deterministically and try again
const d2 = new Uint8Array(
await crypto.subtle.digest(
"SHA-256",
concatUint8([digest, enc.encode("!")])
)
);
const w2 = new DataView(d2.buffer, d2.byteOffset, d2.byteLength).getUint32(
0,
false
);
return (w2 % n) + 1;
}

function concatUint8(arrays: Uint8Array[]): Uint8Array {
const len = arrays.reduce((s, a) => s + a.length, 0);
const out = new Uint8Array(len);
let off = 0;
for (const a of arrays) {
out.set(a, off);
off += a.length;
}
return out;
}
Still unsure how to use the available runtime libraries without using it in an action, and/or why subtle crypto works but cryto doesn't.
Tom Redman
Tom RedmanOP3w ago
Still errors. Not sure where to go from here.
Uncaught ReferenceError: crypto is not defined
at fetchContactsForListsFn (../../../convex/fub/api/fubApi.ts:224:4)
at async handler (../../../convex/fub/api/fubApi.ts:172:39)
Uncaught ReferenceError: crypto is not defined
at fetchContactsForListsFn (../../../convex/fub/api/fubApi.ts:224:4)
at async handler (../../../convex/fub/api/fubApi.ts:172:39)
No description
erquhart
erquhart3w ago
Any chance "use node" has to be first, before the comments? Can't think of any other reason why it would be ignored like that I import from node:crypto in a project and it does work
Tom Redman
Tom RedmanOP3w ago
Thanks @erquhart 🙂 I tried both ways! I have noticed sometimes comments in the "wrong" place can mess with the parser (e.g. an entire that is commented out can still throw syntax/TS errors). However, I ended up just biting the bullet and making it an action on its own. I don't love the idea that I'm firing off a full function call just to convert a string to a number, but that's where I'm at:
"use node";
import crypto from "node:crypto";
import { internalAction } from "../_generated/server";
import { v } from "convex/values";

export const bucketNAction = internalAction({
args: {
input: v.string(),
n: v.number(),
salt: v.string(),
},
handler: async (ctx, args) => {
const { input, n, salt } = args;
if (!Number.isInteger(n) || n < 1) throw new RangeError("n must be >= 1");
if (n === 1) return 1;

const enc = new TextEncoder();
const msg = new Uint8Array([...enc.encode(salt), 0, ...enc.encode(input)]);
const d = new Uint8Array(await crypto.subtle.digest("SHA-256", msg));
const w =
new DataView(d.buffer, d.byteOffset, d.byteLength).getUint32(0, false) >>>
0;

return Math.floor((w * n) / 2 ** 32) + 1;
},
});
"use node";
import crypto from "node:crypto";
import { internalAction } from "../_generated/server";
import { v } from "convex/values";

export const bucketNAction = internalAction({
args: {
input: v.string(),
n: v.number(),
salt: v.string(),
},
handler: async (ctx, args) => {
const { input, n, salt } = args;
if (!Number.isInteger(n) || n < 1) throw new RangeError("n must be >= 1");
if (n === 1) return 1;

const enc = new TextEncoder();
const msg = new Uint8Array([...enc.encode(salt), 0, ...enc.encode(input)]);
const d = new Uint8Array(await crypto.subtle.digest("SHA-256", msg));
const w =
new DataView(d.buffer, d.byteOffset, d.byteLength).getUint32(0, false) >>>
0;

return Math.floor((w * n) / 2 ** 32) + 1;
},
});
I call this via:
"use node";
import { Workpool } from "@convex-dev/workpool";
import { GenericId } from "convex/values";
import { TableNames } from "../_generated/dataModel";
import { ActionCtx } from "../_generated/server";
import { internal } from "../_generated/api";

export const getQueue = async (
ctx: ActionCtx,
namespace: string,
_id: string | GenericId<TableNames>,
n: number,
queues: Workpool[]
) => {
const bucket = await ctx.runAction(internal.workpools.queues.bucketNAction, {
input: _id,
n,
salt: namespace,
});
console.log("bucket", bucket);
return queues[bucket - 1];
};
"use node";
import { Workpool } from "@convex-dev/workpool";
import { GenericId } from "convex/values";
import { TableNames } from "../_generated/dataModel";
import { ActionCtx } from "../_generated/server";
import { internal } from "../_generated/api";

export const getQueue = async (
ctx: ActionCtx,
namespace: string,
_id: string | GenericId<TableNames>,
n: number,
queues: Workpool[]
) => {
const bucket = await ctx.runAction(internal.workpools.queues.bucketNAction, {
input: _id,
n,
salt: namespace,
});
console.log("bucket", bucket);
return queues[bucket - 1];
};
And I call that from two utility functions (one parallel and one sequential):
"use node";

import { Workpool } from "@convex-dev/workpool";
import { components } from "../_generated/api";
import { TableNames } from "../_generated/dataModel";
import { GenericId } from "convex/values";
import { getQueue } from "./getQueue";
import { ActionCtx } from "../_generated/server";

const maxParallelism = 1;

const sequentialQueue1 = new Workpool(components.sequentialQueue1, {
maxParallelism: maxParallelism,
});

const sequentialQueue2 = new Workpool(components.sequentialQueue2, {
maxParallelism: maxParallelism,
});

const sequentialQueue3 = new Workpool(components.sequentialQueue3, {
maxParallelism: maxParallelism,
});

const sequentialQueue4 = new Workpool(components.sequentialQueue4, {
maxParallelism: maxParallelism,
});

const sequentialQueue5 = new Workpool(components.sequentialQueue5, {
maxParallelism: maxParallelism,
});

const sequentialQueue6 = new Workpool(components.sequentialQueue6, {
maxParallelism: maxParallelism,
});

const sequentialQueue7 = new Workpool(components.sequentialQueue7, {
maxParallelism: maxParallelism,
});

const sequentialQueue8 = new Workpool(components.sequentialQueue8, {
maxParallelism: maxParallelism,
});

const sequentialQueue9 = new Workpool(components.sequentialQueue9, {
maxParallelism: maxParallelism,
});

const sequentialQueue10 = new Workpool(components.sequentialQueue10, {
maxParallelism: maxParallelism,
});

const sequentialQueues = [
sequentialQueue1,
sequentialQueue2,
sequentialQueue3,
sequentialQueue4,
sequentialQueue5,
sequentialQueue6,
sequentialQueue7,
sequentialQueue8,
sequentialQueue9,
sequentialQueue10,
];

export const getSequentialQueue = async (
ctx: ActionCtx,
_id: string | GenericId<TableNames>
) => {
return await getQueue(
ctx,
"sequentialQueues",
_id,
sequentialQueues.length,
sequentialQueues
);
};
"use node";

import { Workpool } from "@convex-dev/workpool";
import { components } from "../_generated/api";
import { TableNames } from "../_generated/dataModel";
import { GenericId } from "convex/values";
import { getQueue } from "./getQueue";
import { ActionCtx } from "../_generated/server";

const maxParallelism = 1;

const sequentialQueue1 = new Workpool(components.sequentialQueue1, {
maxParallelism: maxParallelism,
});

const sequentialQueue2 = new Workpool(components.sequentialQueue2, {
maxParallelism: maxParallelism,
});

const sequentialQueue3 = new Workpool(components.sequentialQueue3, {
maxParallelism: maxParallelism,
});

const sequentialQueue4 = new Workpool(components.sequentialQueue4, {
maxParallelism: maxParallelism,
});

const sequentialQueue5 = new Workpool(components.sequentialQueue5, {
maxParallelism: maxParallelism,
});

const sequentialQueue6 = new Workpool(components.sequentialQueue6, {
maxParallelism: maxParallelism,
});

const sequentialQueue7 = new Workpool(components.sequentialQueue7, {
maxParallelism: maxParallelism,
});

const sequentialQueue8 = new Workpool(components.sequentialQueue8, {
maxParallelism: maxParallelism,
});

const sequentialQueue9 = new Workpool(components.sequentialQueue9, {
maxParallelism: maxParallelism,
});

const sequentialQueue10 = new Workpool(components.sequentialQueue10, {
maxParallelism: maxParallelism,
});

const sequentialQueues = [
sequentialQueue1,
sequentialQueue2,
sequentialQueue3,
sequentialQueue4,
sequentialQueue5,
sequentialQueue6,
sequentialQueue7,
sequentialQueue8,
sequentialQueue9,
sequentialQueue10,
];

export const getSequentialQueue = async (
ctx: ActionCtx,
_id: string | GenericId<TableNames>
) => {
return await getQueue(
ctx,
"sequentialQueues",
_id,
sequentialQueues.length,
sequentialQueues
);
};
erquhart
erquhart3w ago
I know for a fact subtle crypto works in the Convex runtime Really odd that you're getting an error for that The better auth component depends on it
Tom Redman
Tom RedmanOP3w ago
Then you can "namespace" each queue up to the number of queue's you've defined:
await (
await getSequentialQueue(ctx, progressId)
).enqueueActionBatch(
ctx,
internal.fub.api.fubApi.processListAsync,
listIds.map((_, index) => ({
progressId,
listIndex: index,
isInitialRequestForList: true,
}))
);
};
await (
await getSequentialQueue(ctx, progressId)
).enqueueActionBatch(
ctx,
internal.fub.api.fubApi.processListAsync,
listIds.map((_, index) => ({
progressId,
listIndex: index,
isInitialRequestForList: true,
}))
);
};
I know for a fact subtle crypto works in the Convex runtime
Do I need to import anything for that? It compiled fine, but failed with "module not found" when I tried to actually use it (FWIW this last approach is functioning well)
erquhart
erquhart3w ago
You don't need to import crypto, it's global in the convex runtime But yeah maybe just take the win if it's working lol.
Tom Redman
Tom RedmanOP3w ago
I'll bug Ian when he's back from PTO. Thanks as always @erquhart I appreciate you!
Pierre
Pierre2w ago
@Tom Redman FYI, it works correctly for me when you don't do the import: Instead of:
import { createHash } from "node:crypto";
import { createHash } from "node:crypto";
which causes a bug, you need to use directly createHash in your code without the import:
crypto.createHash()
// wherever you need it. `crypto` will be correctly recognized by the TS compiler although it's not imported.
crypto.createHash()
// wherever you need it. `crypto` will be correctly recognized by the TS compiler although it's not imported.
Tom Redman
Tom RedmanOP2w ago
Thank you! Will try this.
Patrik Duksin
Patrik Duksin7d ago
up on that issue
[CONVEX A(onramp_actions:getApplePayLink)] Uncaught ReferenceError: crypto is not defined
at Object.default (../convex/utils/coinbase.ts:26:2)
at getDefault (../../../node_modules/.pnpm/valibot@1.1.0_typescript@5.9.2/node_modules/valibot/dist/index.js:3027:35)
at ~run [as ~run] (../../../node_modules/.pnpm/valibot@1.1.0_typescript@5.9.2/node_modules/valibot/dist/index.js:4723:37)
at ~run [as ~run] (../../../node_modules/.pnpm/valibot@1.1.0_typescript@5.9.2/node_modules/valibot/dist/index.js:3639:16)
at safeParse (../../../node_modules/.pnpm/valibot@1.1.0_typescript@5.9.2/node_modules/valibot/dist/index.js:7012:9)
at buildOnrampOrder (../convex/onramp_actions.ts:27:20)
at handler (../convex/onramp_actions.ts:49:19)
at async handler (../../../node_modules/.pnpm/convex-helpers@0.1.104_@standard-schema+spec@1.0.0_convex@1.26.2_react@19.1.0__hono@4.9_f5c0cc9bf036ab99ba0a09c381d1004d/node_modules/convex-helpers/server/customFunctions.js:268:27)
[CONVEX A(onramp_actions:getApplePayLink)] Uncaught ReferenceError: crypto is not defined
at Object.default (../convex/utils/coinbase.ts:26:2)
at getDefault (../../../node_modules/.pnpm/valibot@1.1.0_typescript@5.9.2/node_modules/valibot/dist/index.js:3027:35)
at ~run [as ~run] (../../../node_modules/.pnpm/valibot@1.1.0_typescript@5.9.2/node_modules/valibot/dist/index.js:4723:37)
at ~run [as ~run] (../../../node_modules/.pnpm/valibot@1.1.0_typescript@5.9.2/node_modules/valibot/dist/index.js:3639:16)
at safeParse (../../../node_modules/.pnpm/valibot@1.1.0_typescript@5.9.2/node_modules/valibot/dist/index.js:7012:9)
at buildOnrampOrder (../convex/onramp_actions.ts:27:20)
at handler (../convex/onramp_actions.ts:49:19)
at async handler (../../../node_modules/.pnpm/convex-helpers@0.1.104_@standard-schema+spec@1.0.0_convex@1.26.2_react@19.1.0__hono@4.9_f5c0cc9bf036ab99ba0a09c381d1004d/node_modules/convex-helpers/server/customFunctions.js:268:27)
with use:node it works fine tho!

Did you find this page helpful?