14. modulAlchemy: Infrastructure as TypeScript
Cloudflare for Devs · 14. modul — eszköz-mélymerülés

Alchemy: Infrastructure as TypeScript

A wrangler a Workeredet deployolja — de az erőforrásaidat (D1, bucket, queue környezetenként) továbbra is kézzel hozod létre. Ez a modul arról szól, hogyan lesz ebből is kód, mit nyersz vele konkrétan, hol a határa, és mikor ne nyúlj hozzá.

14.1A rés, amit betölt

Idézzük fel a 12. modul tanulságát: a wrangler.jsonc már ma is IaC — verziózza a bindingokat, route-okat, cronokat, limiteket. De van egy lyuk a képben, amit a 8. modulban is kimondtunk:

FeladatMa mivel csinálodKódban van?
Worker deploy, bindings, route, cronwrangler.jsonc + wrangler deploy✔ igen
D1/R2/KV/Queue létrehozása env-enkéntwrangler d1 create app-staging … kézzel, egyszer✘ nem — README-ben él
Az ID-k bemásolása a konfigbacopy-paste a terminálból✘ manuális
Új környezet felhúzása (pl. egy PR-hoz)a fenti kézi lépések megismétlése✘ ezért nincs per-PR környezeted
Erőforrások takarításakézzel, ha valaki emlékszik rá✘ szemetel

Ez a lyuk kicsinek tűnik, amíg egy stagingod és egy prodod van. Amint N környezet kellene (PR-onként egy, tenantonként egy, demó-példány egy ügyfélnek), a kézi lépések ismételgetése lesz a szűk keresztmetszet. Az Alchemy pontosan ezt a rést zárja be — de nem YAML-lel vagy HCL-lel, hanem TypeScripttel.

14.2Mi ez, és mi a modellje?

Az Alchemy egy TypeScript-natív IaC könyvtár: az erőforrás nem egy deklaratív blokk egy külön nyelvben, hanem egy async függvény, amit await-elsz. Nincs mögötte provider-processz, nincs külön futtatókörnyezet — sima JavaScript fut, ott, ahol te futtatod.

import alchemy from "alchemy";
import { Worker, D1Database, R2Bucket, KVNamespace } from "alchemy/cloudflare";

const app = await alchemy("my-saas");       // ① app — a projekt gyökere

const db = await D1Database("app-db");       // ② resource — létrehozza VAGY frissíti
console.log(db.id);                          // ③ az eredmény azonnal használható

await app.finalize();                        // ④ egyeztet + takarítja az árvákat

A modell öt fogalomból áll:

FogalomMit jelent
Appa projekted gyökere (alchemy("nev")); ez fogja össze az erőforrásokat
Resourcememoizált async függvény: ha még nincs, létrehozza; ha van, frissíti; a visszatérési érték a valódi erőforrás (ID-vel, névvel)
Scope / stagenévtér az erőforrásoknak — ettől lesz ugyanabból a kódból dev, staging, prod vagy pr-123
Stateolvasható JSON a .alchemy/ mappában (vagy cserélhető backendben, akár R2-ben); a titkok titkosítva (ALCHEMY_PASSWORD)
finalize / adopt / destroyfinalize() takarítja, ami kikerült a kódból; adopt-tal meglévő erőforrást veszel át; destroy mindent lebont
A fontos gondolat: mivel ez „csak" TypeScript, az infrastruktúra-leírásodban használhatsz ciklust, feltételt, függvényt, importot — és ugyanazokat a típusokat, amiket az alkalmazásod használ. A Terraform/Pulumi világában ez vagy DSL-korlátokba ütközik, vagy egy külön nyelvi runtime-ba.

14.3Konkrét példa 1 — a teljes SaaS-infra egy fájlban

Így nézne ki a te stacked (12. modul konfigjának megfelelője, de kódként), stage-ekkel:

alchemy.run.ts
import alchemy from "alchemy";
import { Worker, D1Database, R2Bucket, KVNamespace } from "alchemy/cloudflare";

const app = await alchemy("my-saas");
const stage = app.stage;                    // "dev" | "staging" | "prod" | "pr-123"…
const isProd = stage === "prod";

// ---- erőforrások: a NÉV tartalmazza a stage-et, így nem ütköznek ----
const db = await D1Database("db", {
  name: `app-${stage}`,
  primaryLocationHint: isProd ? "weur" : undefined,   // 5b. modul
  migrationsDir: "./migrations",                        // deploykor alkalmazza
});

const uploads = await R2Bucket("uploads", { name: `uploads-${stage}` });
const config  = await KVNamespace("config",  { title: `config-${stage}` });

// ---- a Worker: a bindingokba a FENTI OBJEKTUMOKAT adod át ----
export const api = await Worker("api", {
  name: `my-saas-${stage}`,
  entrypoint: "./.output/server/index.mjs",      // Nuxt build kimenete (13. modul)
  compatibilityFlags: ["nodejs_compat"],
  assets: "./.output/public",

  bindings: {
    DB: db,                                      // nincs ID-copy-paste!
    UPLOADS: uploads,
    CONFIG: config,
    APP_ENV: stage,                              // sima var
    STRIPE_KEY: alchemy.secret.env.STRIPE_KEY,   // titkosítva az állapotban
  },

  routes: isProd
    ? ["app.example.com/*"]
    : [`${stage}.example.com/*`],              // stage-enként saját domain

  crons: isProd ? ["0 1 * * *"] : [],             // cron csak élesben!
  observability: { enabled: true,
                   headSamplingRate: isProd ? 0.1 : 1 },
});

console.log(`✅ ${stage}: ${api.url}`);
await app.finalize();
npx alchemy deploy --stage staging
npx alchemy deploy --stage prod
npx alchemy destroy --stage pr-123

Vesd össze a 12. modul konfigjával, ahol a staging és a production blokk szó szerint duplikálva volt (mert a bindingok nem öröklődnek). Itt ugyanazt a leírást paraméterezed — a duplikáció eltűnik, és a különbségek (isProd) egy helyen, olvashatóan látszanak.

14.4Mit ad ez a wrangler mellé? — hat konkrét dolog

1. Az erőforrás-létrehozás is kód lesz

Nincs több „futtasd le ezt a hat parancsot, aztán másold be az ID-ket". Az új fejlesztő egyetlen alchemy deploy --stage dev-vel felhúzza a saját teljes környezetét. Az ID-k sosem kerülnek kézzel a konfigba: a DB: db binding maga az objektum.

2. Ephemeral környezet PR-onként — a 8. modul fájó pontja

Emlékszel, mit kellett kimondani a 8. modulban? Hogy a PR-preview kód-preview, nem környezet-preview, és valódi per-PR izolációt a platform nem ad készen. Az Alchemy stage-modelljével ez néhány sor CI:

.github/workflows/pr.yml (kivonat)
jobs:
  preview:
    steps:
      - run: npm ci && npm run build
      - run: npx alchemy deploy --stage pr-${{ github.event.number }}
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CF_TOKEN }}
          ALCHEMY_PASSWORD: ${{ secrets.ALCHEMY_PASSWORD }}
      # → saját Worker + saját D1 + saját bucket + saját URL, migrációkkal együtt

  cleanup:                      # PR zárásakor fut
    if: github.event.action == 'closed'
    steps:
      - run: npx alchemy destroy --stage pr-${{ github.event.number }}

Ez az a képesség, amiért önmagában érdemes megfontolni: minden PR kap egy teljesen izolált, valódi környezetet friss sémával — és a bezárásakor nyomtalanul eltűnik. Wranglerrel ezt magadnak kellene összeszkriptelned (a 8. modulban a „szkriptelt per-PR erőforrás" opció ~egy napnyi CI-munkára becsültem).

3. A típusok a forrásból jönnek, nem generált fájlból

A 11. modulban a wrangler types generált egy Env típust, amit szinkronban kellett tartani. Itt a Worker-objektum maga hordozza a típust:

// src/index.ts
import type { api } from "../alchemy.run";
type Env = typeof api.Env;      // a bindingok típusa a definícióból származik

Ha kiveszel egy bindinget a alchemy.run.ts-ből, a kódod azonnal fordítási hibát ad — nem kell külön generálási lépésre emlékezni.

4. Programozhatóság: ciklusok és feltételek az infrában

Ez a te per-tenant irányodban (5b. modul) különösen érdekes. Egy statikus konfigban nem tudsz „minden enterprise tenantnak külön adatbázist" leírni — kódban triviális:

const ENTERPRISE = [
  { slug: "acme",   region: "weur" },
  { slug: "globex", region: "oc"   },
  { slug: "initech", region: "enam" },
];

const tenantDbs = Object.fromEntries(
  await Promise.all(ENTERPRISE.map(async (t) => [
    `DB_${t.slug.toUpperCase()}`,
    await D1Database(`db-${t.slug}`, {
      name: `tenant-${t.slug}-${stage}`,
      primaryLocationHint: t.region,      // mindenki a saját régiójában (5b.2)
      migrationsDir: "./migrations",       // a séma mindenhol egységes
    }),
  ]))
);

export const api = await Worker("api", {
  bindings: { DB: sharedDb, ...tenantDbs, UPLOADS: uploads },
  // …
});
Fontos határvonal: ez deploy-idejű provisioning — a csapatod által ismert, listázható tenantokra jó (enterprise ügyfelek, néhány tucat). A futásidejű onboardingra (amikor éjjel 2-kor regisztrál egy új tenant) továbbra is az 5b. modul mintája kell: REST API + registry + Workflow-runner. A kettő megfér egymás mellett, de ne keverd össze őket.

5. Titkok kezelése és olvasható állapot

Az alchemy.secret.env.X-szel megjelölt értékek titkosítva kerülnek az állapotfájlba (ALCHEMY_PASSWORD-del), tehát az állapot verziózható vagy megosztható anélkül, hogy kulcsok szivárognának. Maga az állapot pedig sima, olvasható JSON — egy PR-ban látod, mi változik az infrában, ami a Terraform-plan élményének a lightweight megfelelője.

6. Takarítás: destroy és árva-kezelés

Ha kiveszel egy erőforrást a kódból, a finalize() észreveszi és eltakarítja. Ez az, ami kézi világban sosem történik meg: marad tíz elárvult KV-namespace és három „temp" bucket, amiről senki nem tudja, kell-e még.

14.5Konkrét példa 2 — több Worker egy monorepóban

A 11. modulban láttad, hogy nagyobb rendszer szétválik: fő app + háttér-Worker. Alchemyben a Workerek közti kapcsolat is csak egy binding:

// háttér-Worker: queue consumer, cron
const jobs = await Worker("jobs", {
  name: `jobs-${stage}`,
  entrypoint: "./workers/jobs/index.ts",
  bindings: { DB: db, UPLOADS: uploads },
  crons: ["*/15 * * * *"],
});

// fő app: service bindinggel hívja a háttér-Workert (11. modul)
export const api = await Worker("api", {
  name: `api-${stage}`,
  entrypoint: "./.output/server/index.mjs",
  assets: "./.output/public",
  bindings: { DB: db, UPLOADS: uploads, JOBS_SERVICE: jobs },
});

Nincs „melyik Workert deployoljam előbb, és hogy hivatkozzon rá a másik" tánc — a függőség a kódban látszik, a sorrendet az await-ek adják.

14.6Hogyan viszonyul a wranglerhez? Együtt vagy helyette?

Nem kell mindenről lemondanod. A gyakorlati felosztás:

FeladatAlchemyvelWranglerrel (marad)
erőforrás-létrehozás, bindings, deployalchemy deploy
lokális fejlesztésalchemy dev (Miniflare-emulációval)vagy a megszokott nuxt dev / wrangler dev
élő logokwrangler tail
ad-hoc D1-lekérdezés, exportwrangler d1 execute/export
kanári / gradual deployment / rollbackwrangler versions deploy, rollback
secretek élesbenalchemy.secret a bindingokbanvagy wrangler secret put
Ezt mérlegeld a legkomolyabban: a 8. modul release-stratégiája (version-feltöltés forgalom nélkül → 10%-os kanári → 100%, vagy másodperces rollback) a wrangler versions/deployments világára épül. Ha az Alchemy egyszerű „deploy = élesítés" modelljére állsz át a production úton, ezt a biztonsági hálót elveszítheted. Praktikus kompromisszum: az erőforrás-provisioninget és a nem-produkciós stage-eket viszi az Alchemy, a production release pedig marad a wrangler version-alapú folyamatán.

14.7Kockázatok — az őszinte rész

14.8Döntési útmutató

HelyzetedAjánlás
1 staging + 1 prod, kis csapat, ritkán változó erőforrásokMaradj a wranglernél + néhány dokumentált create parancs. Az Alchemy itt megoldás egy nem létező problémára.
Sok PR, és fáj, hogy nincs izolált környezetEz az Alchemy fő érve. Vezesd be előbb csak a nem-produkciós stage-ekre — a kockázat így közel nulla.
Több tucat, listázható tenant saját erőforrásokkalErős érv mellette (14.4/4) — de a futásidejű onboarding maradjon a Workflow-runneren.
Több felhő, auditálható erőforrás-életciklus, complianceTerraform a hivatalos Cloudflare-providerrel — érett, támogatott, auditálható.
Szeretsz kísérletezni, és van egy belső projektedTökéletes terep — az állapot olvasható JSON, a kockázat kicsi, tanulsz belőle.
Az én javaslatom a te helyzetedre: ne az éles infrával kezdd. Vedd elő egy belső vagy új projekten, vagy — ami a legnagyobb hasznot hozza — a PR-környezetekre: hagyd a productiont a jelenlegi wrangler-folyamaton (versions, kanári, rollback), és engedd az Alchemyt a pr-* stage-eken dolgozni. Ha fél év múlva stabilnak bizonyul és a csapat szereti, ráérsz feljebb vinni. Ha nem, egy CI-workflow törlése az egész visszavonás.

14.9Ellenőrizd magad

  1. Milyen konkrét rést tölt be az Alchemy a wrangler mellett?
    Válasz

    Az erőforrások (D1, R2, KV, Queue…) létrehozását és életciklusát. A wrangler-konfig a bindingokat, route-okat, cronokat verziózza, de az erőforrásokat kézzel hozod létre, az ID-ket kézzel másolod, és nincs mód új környezetet egy paranccsal felhúzni vagy elpusztítani.

  2. Miben más az „erőforrás = await-elt async függvény" modell, mint a Terraform deklaratív blokkjai?
    Válasz

    Nincs külön nyelv és provider-processz: sima TypeScript fut, az eredmény azonnal használható objektum (ID-vel), és az infrastruktúra-leírásban használhatsz ciklust, feltételt, függvényt, importot — ugyanazokkal a típusokkal, amiket az alkalmazásod is használ.

  3. Hogyan old meg per-PR környezetet, és miért nem tudja ezt a Workers Builds önmagában?
    Válasz

    Stage-ekkel: alchemy deploy --stage pr-123 saját Workert és saját erőforrásokat hoz létre a névtérben, PR-záráskor destroy. A Workers Builds PR-preview-ja ezzel szemben kód-preview: a version a build configjának bindingjaira mutat, tehát a PR-ek közös (staging) erőforrásokon osztoznak.

  4. Miért nem kell wrangler types, ha Alchemyt használsz?
    Válasz

    Mert a Worker-erőforrás objektuma hordozza a bindingok típusát: type Env = typeof api.Env. A típus a definícióból származik, tehát nincs külön generálási lépés, és egy binding törlése azonnal fordítási hibát okoz a kódban.

  5. Mi az a képesség, amit elveszíthetsz, ha a production deployt is átteszed Alchemyre?
    Válasz

    A version/deployment-alapú release-eszközöket: forgalom nélküli version-feltöltés, százalékos kanári, version affinity/override és a másodperces rollback (8. modul). Ezért érdemes a production release-t a wrangler folyamatán hagyni, és az Alchemyt a provisioningra és a nem-produkciós stage-ekre használni.

  6. Két fejlesztő párhuzamosan deployol a laptopjáról ugyanarra a stage-re. Mi a veszély és mi a védekezés?
    Válasz

    Az állapot szétcsúszhat (nincs erős lockolás), így az Alchemy „nem látja" a másik által létrehozott erőforrásokat — ilyenkor adopt és kézi rendrakás kell. Védekezés: megosztott state-backend (pl. R2), és a közös stage-ek deployja CI-ból fusson, ne gépekről.

Előző13. modul — Nuxt-fejlesztés Cloudflare-en Következő 15. modul — AI a Cloudflare platformon