# Functions

A function is one ES module with a default `fetch` handler, deployed to the edge. Each request runs at the location nearest the caller with no server to size or keep awake. Create one with code, or without code to get a starter handler you replace later.

## The address

Every function is served at `https://hk-<project-slug>--<function-name>.harakumo.app` — never under the project's own site address. The create response carries it as `function.url`.

Functions created before the move to harakumo.app have moved there, and `url` is the address to use. Their old harakumo.com address no longer runs the function: it redirects to the new one (301 for GET and HEAD, 308 for anything else, so a POST is sent again as a POST with its body). Webhook senders and pages on other sites usually do not follow redirects, so give them the new address.

## Runtimes

| runtime | What the code gets |
| --- | --- |
| node22 (default), node20 | Web APIs plus the Node.js compatibility layer: `node:crypto`, `Buffer`, `process.env`. |
| edge | Web APIs only: fetch, Request and Response, crypto.subtle, streams. |

## Environment variables

- The project's production variables are available as `env.NAME` in the handler.
- They are read when the function deploys. After changing one, redeploy the function with its code (`PUT /api/functions/:id`).
- The function receives them as secrets: the handler reads `env.NAME` as usual, and the values cannot be read back from where the function runs. In Harakumo they stay visible to anyone who can read the project's environment (developers and above), so they are configuration, not a vault.

## Status and failed deploys

- `status` is `creating` while the first deploy runs, `active` once the function answers at `url`, and `error` when its last deploy (or a delete) did not go through. `last_error` says why; `url` is null unless the function is `active`.
- Code the edge refuses (a syntax error, an import that does not exist, too much work while the module loads) is answered with 400 and the edge's own reason. On create nothing is kept; on redeploy the running version keeps serving.
- Problems on our side answer 502 or 503 with what went wrong; a 503 carries Retry-After. A failed deploy is never reported as a working function.
- If the code uploaded but its address could not be connected, the function is kept with status `error`: redeploy it (`PUT /api/functions/:id`) to try again, or delete it.

## Versions and rollback

Every deploy that changes a function's code is a version, numbered from 1: the create, a redeploy with new code, and a rollback. Deploying the same code again is not a new version. The newest 20 are kept, and the running one too if it is older; `current` is the version the function runs now. Its page in the dashboard lists them.

A version whose `status` is `failed` was refused when it was deployed: it never ran, and the version before it kept serving. `last_error` says why.

A rollback deploys a kept version's code again, with the runtime it ran with, as a new version whose `rolled_back_from` names the one it came from. It runs with the project's production variables as they are at that moment: a version records the names of the variables it was deployed with (`env_keys`), never their values.

A rollback fails the way a redeploy does. When the code is refused, the version that was live keeps serving, and the answer says why and which version that is. It takes the same permission as a redeploy; anyone in the workspace can list versions, and a version's code is for developers and above, like the function's own code.

**List versions**

```js
const { versions, current } = await hk.functions.versions(fn.id);
// versions, newest first: [{ version: 3, status: 'deployed', current: true, rolled_back_from: 1, env_keys: ['API_KEY'], … }, …]
```

**Roll back**

```js
const { version, url } = await hk.functions.rollback(fn.id, 2);
// version 2's code is live again, as a new version
```

## Limits

- One module, at most 200 KB, no npm install and no bundler — bundle dependencies yourself if you need them.
- Code that does not parse, or imports something that does not exist, is refused with 400 and the edge's reason; nothing is created.
- Invoking from the API or dashboard is a test call: it forwards method, path, headers and body, waits up to 10 seconds and returns the status, headers and the first 16 KB of the body. Real traffic goes to the URL and is not counted or capped yet.
- Test calls are counted as test calls, never as traffic: `test_call_count` on the function, and "Function test runs" under Usage. `invocation_count` (requests to the URL) is null until traffic is measured.
- No logs and no scheduled (cron) runs yet: a function runs when its URL is called.
- Developers and above can read a function's stored source; viewers cannot.
- Keys: a key limited to the project can create, deploy and delete its functions; a read-only key can read and test-invoke them.

## Reference

**SDK**

```js
const code = `export default {
  async fetch(request, env) {
    return Response.json({ hello: 'world', region: env.REGION ?? 'edge' });
  },
};`;
const { function: fn } = await hk.functions.create(project.id, { name: 'hello', runtime: 'node22', code });
console.log(fn.url);                            // https://hk-<project>--hello.harakumo.app
await hk.functions.update(fn.id, { code });     // redeploy (also picks up env changes)
const { versions } = await hk.functions.versions(fn.id);
await hk.functions.rollback(fn.id, 1);          // version 1's code, live again as a new version
```

**CLI**

```bash
harakumo functions 12                            # a project's functions
harakumo functions deploy 12 hello ./hello.js    # create, or redeploy with new code
harakumo functions invoke 3 /ping
harakumo functions versions 3                    # every version, newest first
harakumo functions rollback 3 1                  # deploy version 1's code again
```

**REST**

```http
POST   /api/projects/:id/functions   { "name": "hello", "runtime"?: "node22"|"node20"|"edge", "code"? }
GET    /api/functions/:id            # detail; developers also get { source, envKeys }
PUT    /api/functions/:id            { "code": "export default { fetch() { … } }" }   # redeploy
POST   /api/functions/:id            { "method"?, "path"?, "headers"?, "body"? }       # test invoke
DELETE /api/functions/:id
GET    /api/functions/:id/versions[?source=1]          # newest first, with current
GET    /api/functions/:id/versions/:n
POST   /api/functions/:id/versions/:n/rollback         # a new version with rolled_back_from
```
