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á.
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:
| Feladat | Ma mivel csinálod | Kódban van? |
|---|---|---|
| Worker deploy, bindings, route, cron | wrangler.jsonc + wrangler deploy | ✔ igen |
| D1/R2/KV/Queue létrehozása env-enként | wrangler d1 create app-staging … kézzel, egyszer | ✘ nem — README-ben él |
| Az ID-k bemásolása a konfigba | copy-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ása | ké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.
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:
| Fogalom | Mit jelent |
|---|---|
| App | a projekted gyökere (alchemy("nev")); ez fogja össze az erőforrásokat |
| Resource | memoizá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 / stage | névtér az erőforrásoknak — ettől lesz ugyanabból a kódból dev, staging, prod vagy pr-123 |
| State | olvasható JSON a .alchemy/ mappában (vagy cserélhető backendben, akár R2-ben); a titkok titkosítva (ALCHEMY_PASSWORD) |
| finalize / adopt / destroy | finalize() takarítja, ami kikerült a kódból; adopt-tal meglévő erőforrást veszel át; destroy mindent lebont |
Így nézne ki a te stacked (12. modul konfigjának megfelelője, de kódként), stage-ekkel:
alchemy.run.tsimport 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.
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.
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).
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.
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 },
// …
});
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.
destroy és árva-kezelésHa 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.
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.
Nem kell mindenről lemondanod. A gyakorlati felosztás:
| Feladat | Alchemyvel | Wranglerrel (marad) |
|---|---|---|
| erőforrás-létrehozás, bindings, deploy | ✔ alchemy deploy | — |
| lokális fejlesztés | alchemy dev (Miniflare-emulációval) | vagy a megszokott nuxt dev / wrangler dev |
| élő logok | — | ✔ wrangler tail |
| ad-hoc D1-lekérdezés, export | — | ✔ wrangler d1 execute/export |
| kanári / gradual deployment / rollback | — | ✔ wrangler versions deploy, rollback |
| secretek élesben | alchemy.secret a bindingokban | vagy wrangler secret put |
adopt és a kézi rendrakás. Csapatban tedd megosztott backendre (pl. R2) és a deployt futtasd CI-ból, ne laptopokról.alchemy.run.ts ciklusokkal és feltételekkel már programot jelent, amit érteni kell.| Helyzeted | Ajánlás |
|---|---|
| 1 staging + 1 prod, kis csapat, ritkán változó erőforrások | Maradj 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örnyezet | Ez 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ásokkal | Erős érv mellette (14.4/4) — de a futásidejű onboarding maradjon a Workflow-runneren. |
| Több felhő, auditálható erőforrás-életciklus, compliance | Terraform a hivatalos Cloudflare-providerrel — érett, támogatott, auditálható. |
| Szeretsz kísérletezni, és van egy belső projekted | Tökéletes terep — az állapot olvasható JSON, a kockázat kicsi, tanulsz belőle. |
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.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.
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.
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.
wrangler types, ha Alchemyt használsz?
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.
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.
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.