Import error for a valid relative file path with Supabase Deno Edge Functions

The Context

In my current project, we are using a limited number of Supabase Edge Functions (Serverless) which use the Deno runtime to run any server-side code we need.

The Problem

On my second function (a batch create endpoint), I needed a relative import to get some autogenerated database types:
import { Database as DatabaseType } from '../types/database.types.ts'
According to the relevant Deno docs, this is the correct way to do a relative import, however adding this line introduced the following error when trying to serve the function locally (running the CLI command supabase functions server <function-name>):
error: Module not found "file:///home/deno/types/database.types.ts". at file:///home/deno/functions/batch-create/index.ts:4:42 2023/01/10 16:32:10 Sent Header: Host [docker] 2023/01/10 16:32:10 Sent Header: User-Agent [Go-http-client/1.1] 2023/01/10 16:32:10 Send Done 2023/01/10 16:32:10 Recv First Byte Error: error executing command

The Solution

The interesting line is Module not found "file:///home/deno/types/database.types.ts". at file:///home/deno/functions/batch-create/index.ts:4:42, which tells me the error is with the added import.
The interesting thing is the location home/deno/functions - the home directory on my computer is empty, so where is the code actually running?
As I used the supabase CLI to run the command, I know this could be something I’m doing wrong with Deno, or some unexpected behaviour introduced by the Supabase CLI.
I know that, under the hood, Supabase needs the Docker Daemon to run (i.e. I know it uses a Docker process). I run docker ps to see all of the processes and find something that looks relevant:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3caefdac19e0 "/bin/deno run --all…" 3 minutes ago Up 3 minutes 8081/tcp supabase_deno_relay_backend
I see there’s a linux Docker image running called supabase_deno_relay_backend - sounds like this might be where Deno is running my code. Let’s look inside the docker container to check:
docker exec -it 3caefdac19e0 bash deno@3caefdac19e0:/app$ ls home deno@3caefdac19e0:/app$ cd /home deno@3caefdac19e0:/home$ ls deno deno@3caefdac19e0:/home$ cd deno deno@3caefdac19e0:~$ ls functions deno@3caefdac19e0:~$ cd functions deno@3caefdac19e0:~/functions$ ls batch-create deno@3caefdac19e0:~/functions$ cd batch-create/ deno@3caefdac19e0:~/functions/batch-create-products$ ls index.ts
cat index.ts prints out my source code.
Going back to the original error, it now makes sense: Module not found "file:///home/deno/types/database.types.ts"!
The Supabase CLI only copied over source code from within supabase/functions/, so importing something locally from outside the functions directory (e.g. ../../../example.ts ) will trigger Module not found!
Quick solution: move database.types.ts within /supabase/functions/ folder so it is copied into the docker image where the runtime environment exists.

The Code

Jumping into the supabase/cli repo (it’s open source), we can see what’s going on in the file. It’s good to press . with the repo open on Github to open it in VSCode in the browser. You can then easily cmd+shft+f (grep search) for home/deno/functions.
You find this file written in Go, which is the directory we are copying files over to.
We can also find the functions dir by grep searching to see where this mapping comes from.
We see that a variable called binds is created: binds := []string{filepath.Join(cwd, utils.FunctionsDir) + ":" + relayFuncDir + ":ro,z"} which maps the two directories (/home/deno/functions to /supabase/functions) to one another.
The program then runs docker start on the container:
utils.DockerStart( // utils comes from '/supabase/cli/internal/utils' ctx, container.Config{ // container comes from 'docker/api/types/container' Image: utils.DenoRelayImage, Env: append(env, userEnv...), }, container.HostConfig{ Binds: binds, // the binding that maps the '/supabase/functions' dir }, utils.DenoRelayId, )