1# gcp-cloud-function
2
3A public JSON notes API on an Effect-native 2nd-gen Cloud Function, backed by
4Firestore.
5
6| Route | Does |
7| ------------------- | --------------------------------- |
8| `POST /notes` | create a note from `{ title, body }` |
9| `GET /notes` | list notes |
10| `GET /notes/:id` | read one note |
11| `DELETE /notes/:id` | delete a note |
12
13## Architecture
14
15- `src/Notes.ts` — `GCP.CloudFunctions.Function` with `main: import.meta.url`.
16 Alchemy bundles it for Node.js 22, uploads the archive, and serves `fetch`
17 through the Functions Framework. No Dockerfile, no local image build.
18- `src/resources.ts` — a named `FIRESTORE_NATIVE` `GCP.Firestore.Database` in
19 the stack's region. Each note is a document at `notes/{id}`.
20- `alchemy.run.ts` — the stack, plus a `GCP.IAM.Member` that makes the
21 function public.
22
23## Bindings and IAM
24
25| Binding | Grants |
26| ---------------------------------------------------------------- | ---------------------------------------------------------------- |
27| `GCP.Firestore.ReadWriteDatabase` (`ReadWriteDatabaseHttp`) | `roles/datastore.user` to the function's runtime service account, on the project under an IAM Condition matching only `NotesDb` |
28| `GCP.IAM.Member("PublicInvoker", …)` on the function's Cloud Run service | `roles/run.invoker` to `allUsers` |
29
30Alchemy mints a dedicated runtime service account for the function; the
31Firestore grant goes to that account only. Firestore databases have no
32resource-level IAM policy, so the grant sits on the project with the
33condition `resource.name == "<db>" || resource.name.startsWith("<db>/")`.
34
35Drop the `PublicInvoker` member to keep the function private — callers then
36need a Google identity token.
37
38## Deploy
39
40Credentials come from your alchemy profile: run `alchemy profile` once and pick GCP (*Service account JSON* for a key file, or *Stored* for an access token or key kept in `~/.alchemy/credentials`, plus a default region), then deploy with `--profile <name>`.
41
42```sh
43pnpm deploy --profile <name>
44```
45
46The first deploy takes a few minutes: Cloud Build builds the function and
47Firestore provisions the database. A fresh Firestore grant can take a few
48more minutes to propagate, during which the API answers `500`.
49
50```sh
51curl -X POST "$url/notes" -H 'content-type: application/json' \
52 -d '{"title":"groceries","body":"eggs, milk"}'
53curl "$url/notes"
54```
55
56## Test
57
58```sh
59ALCHEMY_PROFILE=<name> bun test
60```
61
62Deploys the stack, drives the full CRUD cycle over HTTP, checks the
63document in Firestore directly, then destroys the stack and verifies the
64function and database are gone.
65
66## Destroy
67
68```sh
69pnpm destroy --profile <name>
70```