# Technical documentation — edge_functions

Evidence date: 2026-10-16.

Source: `git@bitbucket.org:overdrive/edge_functions.git`, `main` at `7449104` (2024-05-02, Jaco Brits, "add new functions").

Function comments contain the Supabase local demo bearer from the CLI scaffold. This document does not copy that token.

## What this repository is

A Supabase CLI project that holds three Deno edge functions. The npm package supplies the CLI only. Function code is not bundled by npm. Each function imports remote modules at runtime.

The lockfile name and `project_id` are `datafy_edge_fuctions`. The Bitbucket slug is `edge_functions`. History is four commits on `main`, 2024-03-29 through 2024-05-02, all by Jaco Brits. No tags, no other branches.

`readme.txt` records a hosted function URL for `hello` on project ref `natpyffafbqetnbeoctm`. This inspection did not call it.

## Repository layout

```text
.gitignore                          generic ignore template from the initial commit
bitbucket-pipelines.yml             SonarQube on branch main
package.json                        devDependency supabase ^1.151.1, no scripts
package-lock.json                   lockfile name datafy_edge_fuctions, supabase 1.151.1
readme.txt                          Windows setup, local serve, deploy hello
sonar-project.properties            projectKey only
.vscode/extensions.json             recommends denoland.vscode-deno
.vscode/settings.json               Deno on supabase/functions
supabase/.gitignore                 .branches, .temp, .env
supabase/config.toml                local stack, project_id datafy_edge_fuctions
supabase/seed.sql                   empty
supabase/functions/hello/index.ts
supabase/functions/callsheet/index.ts
supabase/functions/upload_sj/index.ts
```

Not present: `supabase/migrations`, `deno.json`, `import_map.json`, tests, Dockerfile, `README.md`, `sonarqube_scan.bat`. The onboarding issue listed `sonarqube_scan.bat`. It is not in this revision.

## Runtime

| Item | Value | Evidence |
|---|---|---|
| Function runtime | Deno (`Deno.serve`, `Deno.env`) | the three `index.ts` files |
| Language | TypeScript, no compile step in repo | extension `.ts`, no build script |
| CLI | `supabase` `1.151.1` | `package-lock.json` |
| CLI npm engine | `>=8` | lockfile `engines` on `node_modules/supabase` |
| Local Postgres | major version 15 | `supabase/config.toml` `[db]` |
| Hosted Postgres version | not recorded | |
| Deno version | not pinned | `readme.txt` points at `https://deno.land/install.ps1` |

`.vscode/settings.json` sets `deno.unstable: true` and `deno.enablePaths` to `supabase/functions`. `deno.lint` is true. Formatter is `denoland.vscode-deno`.

There is no import map. Import specifiers are absolute URLs or `npm:` specifiers inside each file.

## Local stack (`config.toml`)

`project_id = "datafy_edge_fuctions"`.

The file matches a `supabase init` template of this CLI generation. Values a developer would hit from the readme:

| Service | Port | Enabled |
|---|---|---|
| API | 54321 | yes |
| DB | 54322 | yes |
| Shadow DB | 54320 | (diff port) |
| Studio | 54323 | yes |
| Inbucket | 54324 | yes |
| Analytics | 54327 | yes |
| Pooler | 54329 | no |

API `schemas` are `public`, `storage`, `graphql_public`. `max_rows` is 1000. Storage `file_size_limit` is `50MiB`.

Auth block (local stack only): `site_url` `http://127.0.0.1:3000`, `jwt_expiry` 3600, refresh rotation on, signup on, email confirmations off, SMS signup on, Twilio disabled, Apple OAuth disabled. The functions do not use this Auth server.

Experimental S3 and OrioleDB keys are env placeholders (`S3_HOST`, `S3_REGION`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`). `orioledb_version` is empty. No function reads them.

No `[functions.hello]`, `[functions.callsheet]`, or `[functions.upload_sj]` section. No `verify_jwt`, `import_map`, or `entrypoint` override. Entrypoint is the default `index.ts`. The directory name is the function name, which `readme.txt` states for `hello`.

## HTTP surface

| Function | Path | Methods checked | Reads the request |
|---|---|---|---|
| `hello` | `/functions/v1/hello` | no | no |
| `callsheet` | `/functions/v1/callsheet` | no | no |
| `upload_sj` | `/functions/v1/upload_sj` | no | no |

Local origin in the readme and in the comments: `http://127.0.0.1:54321`. Hosted origin in the readme, `hello` only: `https://natpyffafbqetnbeoctm.supabase.co`.

Response header set by all three, when they return: `Content-Type: application/json`. No CORS headers are set in the functions. What the gateway adds is **Unknown** from this repo.

Scaffold comments show `POST` and `{"name":"Functions"}`. That body is not parsed.

## Environment

| Variable | Readers | Notes |
|---|---|---|
| `SUPABASE_URL` | all three | `Deno.env.get(...)!` |
| `SUPABASE_ANON_KEY` | all three | same |
| `SUPABASE_STORAGE_URL` | `upload_sj` | not a default hosted edge variable name |
| `SUPABASE_ANON_STORAGE_KEY` | `upload_sj` | same |
| `MY_NAME` | none | example in `readme.txt` only |
| `OPENAI_API_KEY` | none | Studio `openai_api_key = "env(OPENAI_API_KEY)"` in `config.toml` |
| `SONAR_HOST_URL`, `SONAR_TOKEN` | pipeline | Bitbucket variables, not in git |
| `SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN` | none | local Auth template |
| `SUPABASE_AUTH_EXTERNAL_APPLE_SECRET` | none | local Auth template |

`supabase/.gitignore` ignores `.env`. No `.env` file is committed. Missing variables are not checked. The `!` assertions only affect TypeScript. At runtime `Deno.env.get` returns `undefined`, and that value is passed to `createClient`.

Hosted edge functions usually receive `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and `SUPABASE_SERVICE_ROLE_KEY` from the platform. This repo never reads the service-role key. It does read the two `*_STORAGE_*` names, which the platform does not document as automatic. **Unknown / Requires Confirmation** whether those two are set on the project.

## Function `hello`

File: `supabase/functions/hello/index.ts` (38 lines).

Client: `npm:@supabase/supabase-js`, no version.

Query: `from('sites').select('name').limit(1).single()`.

The destructure renames `data` to `name`. The JSON message uses `name?.name`, so `name` must be a row with a `name` column. One row returns `Hello <value>!`. Zero rows or a PostgREST error: log `error`, still return HTTP 200 and `Hello undefined!`.

Module scope logs `Hello from Functions!` once per isolate, not once per request.

## Function `callsheet`

File: `supabase/functions/callsheet/index.ts` (74 lines).

Imports: `npm:@supabase/supabase-js`, and `{ xlsx, readXLSX }` from `https://deno.land/x/flat/mod.ts` (unpinned). `readXLSX` is unused.

Storage call:

```text
from('uploads').download('callsheet/CMR_CALL_CYCLE_2023.xlsx')
```

Then `xlsx.read(data)` and `console.log(workbook.SheetNames)`. `error` from `download` is ignored.

Return body, keys always empty objects: `message`, `result`, `sent`.

If `data` is null, `xlsx.read` still runs. No `try/catch`.

Commented code mentions `readCSV` (not imported) and the Free State filename that `upload_sj` downloads. That code is not on the live path.

## Function `upload_sj`

File: `supabase/functions/upload_sj/index.ts` (73 lines).

Imports:

| Specifier | Binding | Used |
|---|---|---|
| `npm:@supabase/supabase-js` | `createClient` | yes, twice |
| `npm:@supabase/storage-js` | `createClient as createStorageClient` | yes, argument is the second Supabase client object |
| `npm:std/encoding/base64` | `decode` | no |
| `npm:xlsx/mod` | `read` | yes, twice |

`npm:std/...` is not the Deno standard-library URL (`https://deno.land/std/...`). Whether that npm specifier resolves was not tested.

Flow:

1. Data client from `SUPABASE_URL` and `SUPABASE_ANON_KEY`.
2. Second Supabase client from `SUPABASE_STORAGE_URL` and `SUPABASE_ANON_STORAGE_KEY`.
3. `createStorageClient(supabaseStorage)`. **Inferred** from the storage-js client shape: the constructor takes a URL and a headers object. Passing a client object does not match that. Not executed in this inspection.
4. `from('uploads/callsheet').download("FState Region ALL CHANNEL REPORTS 10 MARCH 2024.xlsx")`. `from` is the bucket argument in both supabase-js and storage-js. `callsheet` passes only `uploads` as the bucket. A bucket id containing `/` is a different, likely invalid, bucket name. **Requires confirmation** of which layout exists in the project.
5. On `downloadError`, `throw downloadError`.
6. `read(file)` then `read(firstSheet, { header: 1 })`. **Inferred:** SheetJS `read` parses a workbook file. `{ header: 1 }` is the option for `utils.sheet_to_json`, which is not imported. The second call is not a confirmed row parser.
7. `from('report_sj').upsert(parsedData)` with no options.
8. `if (error)` assigns `{ message: 'Uploaded unsuccessfully', status: 500 }` to `data` and then `throw error`. `else` assigns `{ message: 'Uploaded successfully', status: 200 }` to `data`.

`data` is a `const` binding from the destructure. Either assignment is a TypeError in JavaScript before `return new Response`. The `status` fields would have been JSON properties, not `Response` status codes. `new Response` is called with headers only.

`Deno.serve(async (req, res) =>` — the platform handler receives a `Request`. The second parameter is unused.

## Data

No DDL in the repo. Empty `seed.sql`. Objects the functions assume:

| Object | Use | Defined here |
|---|---|---|
| table `public.sites` column `name` | `hello` select, limit 1, single | no |
| table `report_sj` | `upload_sj` upsert, no column list | no |
| bucket `uploads` object `callsheet/CMR_CALL_CYCLE_2023.xlsx` | `callsheet` | no |
| bucket string `uploads/callsheet`, object `FState Region ALL CHANNEL REPORTS 10 MARCH 2024.xlsx` | `upload_sj` | no |

RLS, grants, primary keys, and bucket policies are **Unknown / Requires Confirmation**. Upsert conflict behaviour cannot be stated until `report_sj` has a known primary key or unique constraint.

The Excel workbooks are not in git. `.gitignore` does not specifically ignore `.xlsx`. They were simply never committed.

## Auth

Two different mechanisms:

1. Gateway JWT. **Documented** off for `npx supabase functions serve --no-verify-jwt` and for `npx supabase functions deploy hello --no-verify-jwt`. Not recorded for the other functions. No `verify_jwt` key in `config.toml`.
2. Supabase API key inside the function. **Implemented** as the anon key. The service role is unused.

Local Auth settings in `config.toml` do not apply to requests that only hit these functions.

The curl comments embed the well-known local demo anon JWT that `supabase functions new` writes. It is the local demo signing string, not evidence of a production JWT secret. It is still committed in three files. This Silicon Ark set does not repeat it.

## Deployment and development

Documented sequence in `readme.txt`:

1. Install Deno on Windows (`irm https://deno.land/install.ps1 | iex`).
2. Supabase functions quickstart (`https://supabase.com/docs/guides/functions/quickstart`) and the YouTube URL in the readme. The video was not reviewed.
3. `npx supabase init`.
4. `npx supabase functions new hello`.
5. `npx supabase start`.
6. `npx supabase functions serve --no-verify-jwt`.
7. `npx supabase login`.
8. `npx supabase functions deploy hello --no-verify-jwt`.

`package.json` has no `scripts`, so `npx supabase` is the only npm entry. Dependencies are not installed in git (`node_modules/` is ignored). The lockfile is committed.

There is no CI deploy. Pipelines only scan.

**Unknown / Requires Confirmation:** which machine last ran the deploy, whether `callsheet` and `upload_sj` were deployed by hand, and whether project `natpyffafbqetnbeoctm` is still linked to this repo.

## CI

`bitbucket-pipelines.yml`:

- Image `maven:3.3.9`. The repo has no `pom.xml` or Java sources. The image is only the pipe's container.
- Step name `SonarQube analysis`.
- Pipe `sonarsource/sonarqube-scan:2.0.1`.
- Variables `SONAR_HOST_URL`, `SONAR_TOKEN`.
- Cache path `~/.sonar`.
- `clone.depth: full`.
- Only branch pattern `{main}`.

`sonar.projectKey=overdrive_edge_functions_d17c9661-3898-4839-85d2-04c487089cf1`. No sources, tests, or exclusion properties. The scanner's own defaults decide which files are analysed. Those defaults are not copied into the repo.

Commit subjects for the pipeline files say they were created online with Bitbucket (`323c389`, `2f97818`).

## Integrations

| System | Direction | Trigger | Payload | Auth | Failure in this repo |
|---|---|---|---|---|---|
| Supabase PostgREST `sites` | out | each `hello` request | `select name` limit 1 | anon key | logged; HTTP 200 and `Hello undefined!` |
| Supabase PostgREST `report_sj` | out | `upload_sj` after parse | `upsert` of `parsedData` | anon key | source throws; JSON status object is not returned |
| Supabase Storage `uploads` | out | each `callsheet` request | download one object | anon key on `SUPABASE_URL` | `error` ignored; `xlsx.read` still called |
| Storage via `SUPABASE_STORAGE_URL` | out | each `upload_sj` request | download one object | `SUPABASE_ANON_STORAGE_KEY` | `throw downloadError` |
| SonarQube | out | push to `main` | workspace files | `SONAR_TOKEN` | not defined in the YAML |
| deno.land and npm specifiers | out | isolate start | module source | none in repo | not handled |

No webhooks, queues, cron, or workers. `config.toml` enables Realtime locally. No function subscribes to it.

## Security

- Anon key only. A bug that should have used the service role will fail closed if RLS denies the anon role, and will succeed if RLS is absent or the anon role is granted write. The policy is not in git, so the effective grant is **Unknown**.
- `hello` deploy is documented with JWT verification disabled. Combined with an open anon policy, the greeting query would be reachable without a user token. **Inferred** from the flag plus the client. Confirm the live project settings before relying on that.
- `upload_sj` upsert is the only write. It is not guarded by a caller check in the function.
- Workbook paths are constants, so this repo does not show a path-injection input. The commented `req.url.substring(1)` would have used the request path as a file path. It is not active.
- Pipeline secrets are variable references, not literals.
- Do not treat `MY_NAME=Chewbacca` as a live secret. It is an example string and it is unused.
- The local demo JWT in the curl comments should not be copied into new docs or into a hosted project as if it were that project's anon key.

## Testing

No `*_test.ts`, no `deno test` task, no npm test script, no pipeline test step. The curl blocks in the function files are comments, not a test suite.

This inspection did not run `supabase start` or invoke the functions. Statements about TypeError on `const` assignment and about unused imports are from reading the source. Statements about storage-js and SheetJS call shapes are labelled **Inferred** and were not executed.

## Monitoring and operations

No log drain, metric, or alert config. The only application logs are `console.log` in `hello` (startup line and query error) and `callsheet` (sheet names). `upload_sj` does not log before it throws.

No health endpoint beyond whatever the platform provides for a function URL. `hello`'s 200 response is not a readiness check: it also returns 200 when the query failed.

## Discrepancies

| Topic | A | B |
|---|---|---|
| Names | repo `edge_functions` | `project_id` and lockfile `datafy_edge_fuctions` |
| `hello` input | comment body `{"name":"Functions"}` | handler reads `sites.name` |
| `callsheet` result | comments discuss CSV per sheet | response payload stays empty |
| Workbook location | `callsheet`: bucket `uploads`, path `callsheet/...` | `upload_sj`: `from('uploads/callsheet')` |
| `upload_sj` outcome | strings say uploaded / status 500 | both branches assign to `const data` and do not return |
| Deploy scope | readme deploys `hello` only | three function directories exist |
| CI image | `maven:3.3.9` | sources are Deno |
| Discovery listing | OVER-100 listed `sonarqube_scan.bat` | file absent at `7449104` |
| Datafy host | `terraform_scripts` documents `supabase.datafy.co.za` | this readme documents project ref `natpyffafbqetnbeoctm` |

The last row is a possible relationship, not a demonstrated one.
