Telegram Serverless lader dig køre backend-kode til din bot og Mini App direkte på Telegrams infrastruktur — ingen servere der skal klargøres, ingen containere der skal holdes kørende, ingen skalering at bekymre sig om. Du skriver simpel JavaScript-kode, udruller den med en enkelt kommando, og Telegram kører den i en hurtig, isoleret V8-sandbox, der ligger lige ved siden af Bot API’et og en indbygget database.
Hvis du nogensinde har forbundet en bot til en VPS, en cloud-funktion eller et hosting-panel bare for at svare på en /start, så er det her den del, du ikke længere behøver at gøre.
Hvorfor serverless
En Telegram-bot er i bund og grund et program, der reagerer på opdateringer. Traditionelt skulle du hoste det program et sted, der altid er tændt, tilgængeligt og sikkert — og derefter sørge for, at det forbliver sådan. Telegram Serverless fjerner det lag fuldstændigt:
- Ingen infrastruktur. Der er ingen maskiner, der skal lejes, opdateres eller overvåges. Din kode kører on-demand og skalerer automatisk med din bot.
- Legitimationsoplysninger inkluderet. Telegram Bot API, en SQLite-baseret database og udgående HTTP er tilgængelige for hvert modul fra start — intet skal installeres, ingen legitimationsoplysninger skal forbindes.
- Hurtig, isoleret eksekvering. Hver kørsel foregår i en letvægts V8-isolat, tæt på Telegrams egne systemer, så kald til Bot API’et og din database er hurtige og pålidelige.
- En ægte udvikler-workflow. Et projekt lever i en mappe på din maskine under versionsstyring. Du redigerer filer, ser præcis hvad der er ændret, udruller atomart og opdaterer dit databaseskema med reviewede migreringer — præcis som du allerede arbejder med alt andet.
Den mentale model: Hvordan projektet fungerer
Du arbejder på tre steder, og de korresponderer direkte med hinanden:
| Hvor | Hvad der findes der |
|---|---|
| Din projektmappe | JavaScript-moduler — skema, delt kode, opdaterings-handlere, Mini App-endpoints — og, hvis du har en, Mini App’ens front-end |
| Skyen | Den udrullede kopi af disse moduler, din bots database og Mini App’ens filer |
tgcloud CLI |
Broen — den viser dig forskelle og synkroniserer dem |
Du logger aldrig ind via SSH til noget. Du redigerer filer lokalt, kører npx tgcloud push, og platformen tager over derfra. Din bots trafik håndteres af de udrullede moduler; din database forbliver gemt mellem kørsler.
Et projekt består af kun fire slags kode, alt sammen inde i én tgcloud/-mappe:
tgcloud/
├─ endpoints/ # funktioner din Mini App kalder
├─ handlers/ # funktioner der reagerer på Telegram-opdateringstyper
├─ lib/ # delt kode du importerer fra hvor som helst
└─ schema.js # dine databasetabeller
Når en opdatering ankommer — en besked, et knaptryk, en inline-forespørgsel — sender Telegram den til den matchende handler (handlers/message.js, handlers/callback_query.js, …) og kalder dens standard-eksport. Den funktion taler med Bot API’et og databasen via SDK’et og returnerer. Det er hele loopet. En opdatering uden en matchende handler ignoreres blot, så du tilføjer kun de handlere, du har brug for.
En Mini App er valgfri. Hvis din bot har én, lever dens front-end i samme projekt, ved siden af tgcloud/. Serverless hoster dens build-output og kører de endpoints/, den kalder. Se
Mini App front-end
.
Hurtig demo
Her er en komplet, fungerende demo-bot. Den svarer på hver besked og husker, hvor mange den har set fra hver chat.
tgcloud/schema.js
import { table, integer } from 'sdk/db';
export const counters = table('counters', {
chatId: integer('chat_id').primaryKey() ,
seen: integer('seen').notNull().default(0) ,
});
tgcloud/handlers/message.js
import { api, db } from 'sdk';
import { sql } from 'sdk/db';
import { counters } from '../schema.js';
export default async function (message) {
const chatId = message.chat.id;
// Indsæt tælleren, eller øg den hvis chatten allerede har én — og få
// den resulterende række tilbage i samme statement via .returning().
const [row] = await db.insert(counters)
.values({ chatId, seen: 1 })
.onConflictDoUpdate({
target: counters.chatId,
set: { seen: sql`${counters.seen} + 1` },
})
.returning()
.run();
await api.sendMessage({
chat_id: chatId,
text: `Hej! Jeg har set ${row.seen} besked(er) fra dig.`,
});
}
Udrul den:
npx tgcloud push # upload modulerne
npx tgcloud migrate # opret `counters`-tabellen
Det er en live-bot med vedvarende tilstand og uden server. Alt i den — api, db, table() DSL’en — er beskrevet i sektionerne nedenfor.
Serverless er en generel backend til Telegram-bots og Mini Apps, ikke en skabelon til én type app. Den er ideel til:
- Samtale-AI-bots der skal gemme tilstand pr. bruger i en database.
- Mini App-backends der gemmer brugerdata og serverer dynamisk indhold.
- Spil og værktøjer — inklusiv ranglister, quizzer og mere.
- Automatiseringer og integrationer der kalder tredjeparts HTTP-API’er og skubber resultater ind i chats.
Kom i gang
Denne gennemgang tager dig fra en tom mappe til en live-bot, der svarer på beskeder og gemmer data. Den forudsætter, at du har
Node.js
18 eller nyere installeret og en bot registreret hos
@BotFather
. Ved slutningen vil du have brugt alle de kommandoer, du har brug for i hverdagen: push, migrate, run og status.
Før alt andet, tænd for Serverless. I @BotFather , åbn din bot → Serverless og tænd for den. Det aktiverer funktionen for denne bot og låser op for dens CLI-adgangstoken, handlere, bibliotek og database.
1. Opret et projekt
Den hurtigste måde at starte på er projekt-generatoren, som opretter et projekt og installerer CLI’en i det:
npm create @tgcloud/bot example_bot
cd example_bot
Argumentet er målmappen: skriv . for at oprette i den aktuelle mappe, eller en hvilken som helst sti. Det virker også i en eksisterende mappe og overskriver aldrig filer, du allerede har.
Dette giver dig et projekt, der er klar til redigering:
example_bot/
├─ tgcloud/ # alt hvad platformen kører
│ ├─ handlers/
│ │ └─ message.js # en start-beskedhandler (echoer tekst tilbage)
│ ├─ lib/ # dine delte moduler går her (tomme i starten)
│ └─ schema.js # dine databasetabeller
├─ docs/
│ └─ tgcloud-sdk.md # SDK-reference (til dig og dine AI-værktøjer)
├─ AGENTS.md # orientering til AI-kodningsassistenter
├─ package.json
└─ tgcloud.jsonc # projektkonfiguration
De oprettede filer er selv-dokumenterende — hver indeholder kommenterede eksempler på, hvad du kan gøre derefter.
CLI’en installeres i projektet som en lokal dev-dependency, så du kører den med npx tgcloud <command> (npx finder kopien i dit projekts node_modules), eller gennem de npm run-genveje, som scaffold’en tilføjer til package.json (npm run deploy, npm run status). Som standard er der ingen global tgcloud på din PATH.
Du kan også installere den globalt — npm install -g @tgcloud/cli — hvis du hellere vil skrive en simpel tgcloud fra hvor som helst. Det er praktisk til at køre tgcloud init i en hvilken som helst tom mappe, og det er hvad shell-tab-fuldførelse kræver. Uanset hvad får du det samme projekt.
Bygger du en Mini App? Start fra dens front-end i stedet. Opret den med dit sædvanlige værktøj, og kør derefter projekt-generatoren i den mappe. Se Mini App front-end .
2. Forbind din bot
Hvert projekt er bundet til én bot. Forbind dem med login, som spørger efter dit CLI-adgangstoken (
@BotFather
→ din bot → Serverless → CLI Access → Access token — et separat token fra din
bots API-token
) og gemmer det lokalt:
npx tgcloud login
Tokenet har formatet app<id>:<secret>. CLI’en gemmer det i .tgcloud/, som er git-ignored, og udskriver aldrig den hemmelige del. Login er den eneste gang, du bliver spurgt om det — se
Autentificering
for hvordan tokens løses i CI.
3. Se dig omkring
To kommandoer fortæller dig, hvordan situationen er til enhver tid, begge fuldt offline:
npx tgcloud status # hvad har ændret sig lokalt vs. den udrullede kopi
npx tgcloud diff # linje-for-linje ændringerne
Lige efter init er alt nyt, og intet er udrullet endnu. status viser startfilerne, der venter på at blive uploadet.
4. Udrul
Send dine moduler til skyen:
npx tgcloud push
push uploader alle ændrede moduler i én atomar batch og opdaterer din lokale optegnelse over, hvad skyen nu indeholder. Din bot er live: åbn den i Telegram og send den en besked — start-handleren echoer den tilbage.
Udrulning rører aldrig ved din database. At pushe kode og ændre dit databaseskema er bevidst adskilte trin, så en kode-udrulning aldrig kan overraske dig med en datamigrering. Det er, hvad næste trin er til for.
5. Tilføj en databasetabel
Lad os få botten til at huske noget. Åbn tgcloud/schema.js og deklarér en tabel:
import { table, integer, text, sql } from 'sdk/db';
export const messages = table('messages', {
id: integer('id').primaryKey({ autoIncrement: true }) ,
chatId: integer('chat_id').notNull() ,
text: text('text') ,
created: integer('created_at', { mode: 'timestamp' }).default(sql`(unixepoch())`) ,
});
Udrul skemaet, og anvend det derefter på databasen:
npx tgcloud push # uploader det nye skema
npx tgcloud migrate # opretter `messages`-tabellen
push rapporterer, at skemaet ikke er synkroniseret og viser dig den afventende ændring, men anvender intet. migrate guider dig gennem ændringen og, efter din bekræftelse, opretter tabellen. Denne to-trins model — og hvad der sker ved mere risikable ændringer som sletninger — er dækket i
Migreringer
.
6. Gem og læs data
Brug nu tabellen fra din handler. Redigér tgcloud/handlers/message.js:
import { api, db } from 'sdk';
import { eq } from 'sdk/db';
import { messages } from '../schema.js';
export default async function (message) {
// Gem denne besked.
await db.insert(messages)
.values({ chatId: message.chat.id, text: message.text })
.run();
// Tæl hvor mange vi har gemt for denne chat.
const count = await db.$count(messages, eq(messages.chatId, message.chat.id));
await api.sendMessage({
chat_id: message.chat.id,
text: `Gemt. Det er ${count} besked(er) fra denne chat indtil videre.`,
});
}
Udrul den opdaterede handler med npx tgcloud push, send derefter din bot et par beskeder og se tallet stige. Databasen forbliver gemt mellem kørsler — det er din bots hukommelse.
7. Test uden udrulning
Du behøver ikke at udrulle for at prøve en ændring. npx tgcloud run kører en handler på platformen ved hjælp af dine lokale filer, uden at publicere dem:
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hello" }'
Argumentet er payloaden, din handler modtager — for handlers/message, en
Besked
— skrevet i
JSON5
(så du kan undlade at citere nøgler). Kommandoen udskriver alt, hvad handleren loggede med console.*, returværdien og hvor lang tid det tog. Dette er det tætteste loop til at iterere på logik — ingen udrulning, ingen ventetid på en rigtig besked.
8. Hold dig synkroniseret
Mens du arbejder, holder en håndfuld kommandoer dit lokale projekt og skyen på linje: npx tgcloud status viser hvad der er ændret, npx tgcloud push udruller, npx tgcloud pull bringer dit lokale projekt i overensstemmelse med skyen, npx tgcloud fetch tjekker hvad der er udrullet uden at røre dine filer, og npx tgcloud reset kasserer lokale ændringer.
Hvis to personer (eller to maskiner) udruller til den samme bot, detekterer platformen konflikten, og
pushstopper for at lade digpullførst — du kan aldrig lydløst overskrive en andens arbejde. Se Staying in sync .
Byg med AI
Foretrækker du at bygge med en AI-assistent — eller er den eneste koder på dit team en AI? Du kan stadig sende en bot. Vi har taget et første skridt for at få en AI-agent til at føle sig hjemme i et projekt: hvert nyt projekt oprettes med en AGENTS.md og en docs/tgcloud-sdk.md reference, som agentiske kodningsværktøjer læser automatisk.
Sammen med en lille, selvstændig runtime — ét SDK, ingen npm-pakker at kæmpe med — giver det assistenten en flyvende start på de konventioner, generisk kodegenerering har tendens til at overse her: relative imports med .js, ingen fremmednøgler, hvert db-kald er async, én handler pr. opdateringstype, og to-trins push/migrate-flowet.
Prøv det:
npm create @tgcloud/bot my-bot
cd my-bot
opencode # eller Claude Code, Cursor, … — enhver agent der læser AGENTS.md
Spørg derefter, på almindeligt sprog:
Skriv en bot der husker hver persons huskeliste — tilføj et element når de sender tekst, og vis hele listen når de sender /list.
Assistenten redigerer schema.js og dine handlere for dig; du gennemser, tester en ændring øjeblikkeligt med npx tgcloud run, og går derefter live med npx tgcloud push og npx tgcloud migrate. AGENTS.md er en del af dit projekt — redigér den efterhånden som botten vokser, så vejledningen forbliver præcis.
På farten med BotFather
Kun din telefon ved hånden? Hele projektet lever også i @BotFather — åbn din bot → Serverless og du får alt det, CLI’en styrer, på en touchskærm:
- Handlere — opret, redigér og test-kør opdaterings-handlere; BotFather holder webhook’en synkroniseret med de handlere, du har (det samme In sync / Out of sync som CLI’en rapporterer).
- Endpoints — funktionerne din Mini App kalder.
- Bibliotek — dine delte
lib/-moduler. - Database — redigér
schema.jsi den samme Drizzle-lignende syntaks, gennemse afventende ændringer og anvend dem; Gem udruller. - Mini App-filer — front-enden dine brugere åbner i Telegram.
- CLI-adgang — hent CLI-adgangstokenet her, når du er tilbage ved et tastatur.
Det er ét og samme cloud-projekt, så du kan starte en handler på din telefon og npx tgcloud pull den til din laptop senere — intet er bundet til én klient. Kørsel af en handler viser endda dens Console-output direkte i chatten, præcis som
npx tgcloud run
.
Projekter og moduler
Et serverless-projekt er en almindelig mappe under versionsstyring. Alt hvad platformen kører, sidder i én mappe, tgcloud/; resten af projektet er dit — en mini-app front-end, konfiguration, dokumentation. Der er intet build-trin for botten, ingen node_modules ved runtime, og intet server-indgangspunkt.
Anatomi af et projekt
example_bot/
├─ tgcloud/ # den udrullede del — intet andet er
│ ├─ handlers/ # opdaterings-handlere — flad, kun ét niveau
│ │ ├─ message.js
│ │ └─ callback_query.js
│ ├─ endpoints/ # funktioner din Mini App kalder — flad, kun ét niveau
│ │ └─ getProfile.js
│ ├─ lib/ # delte moduler; undermapper tilladt
│ │ ├─ reply.js
│ │ └─ internal/util.js
│ └─ schema.js # databaseskema — én fil
└─ .tgcloud/ # CLI-tilstand, git-ignored
Kun .js-filer inde i tgcloud/ — schema.js, lib/, handlers/, endpoints/ — bliver udrullet. Alt andet — Markdown, konfigurationsfiler, .tgcloud/-mappen, alt uden for tgcloud/ — forbliver på din maskine. Modulnavne er relative til tgcloud/. Filen tgcloud/handlers/message.js er modulet handlers/message, og det er det navn npx tgcloud run tager.
Opgradering fra et ældre projekt? Projekter oprettet før CLI 0.2.0 beholdt
schema.js,lib/oghandlers/i projektroden. Kørnpx tgcloud upgradeén gang for at flytte dem ind itgcloud/.
schema.js — din database. Den deklarerer tabeller som navngivne eksport-elementer ved hjælp af skema-DSL’en og lever i tgcloud/ som én enkelt fil. Den udrulles som ethvert andet modul, men udrulning af den ændrer aldrig databasen — skema-ændringer anvendes separat med npx tgcloud migrate. Se
The database
.
lib/ — delt kode, alt hvad du vil genbruge på tværs af handlere: rene hjælpere, database-adgangslag, formatering, integrationer med eksterne tjenester. lib/ er den eneste mappe, der må indeholde undermapper (lib/internal/util.js, lib/payments/stripe.js), så du kan organisere en større kodebase, som du vil. Moduler i lib/ bliver aldrig kaldt direkte af platformen; de eksisterer for at blive importeret af handlere og af hinanden.
handlers/ — indgangspunkterne for din bot. Hver fil svarer til én Telegram-opdateringstype, og platformen sender hver indkommende opdatering til den matchende handler:
| Fil | Håndterer |
|---|---|
handlers/message.js |
Nye indkommende beskeder |
handlers/inline_query.js |
Nye indkommende inline forespørgsler |
handlers/callback_query.js |
Nye indkommende callback-forespørgsler |
| … | enhver anden Bot API-opdateringstype |
handlers/ er flad — ingen undermapper. En handlers export default er den funktion, platformen kalder (se
Handlers
).
En opdateringstype håndteres kun hvis dens handler-fil eksisterer og ikke er tom. Hvis der ikke er nogen handlers/<type>.js — eller filen er tom — ignoreres opdateringer af den type, og platformen kører intet for dem. Så behold kun de handlere, du faktisk har brug for: hver du tilføjer, er en anden opdateringstype, din bot vågner op for at behandle, og at udelade resten betyder, at Telegram ikke starter din kode for opdateringer, du alligevel ville kassere.
For at oprette en ny handler, kør
npx tgcloud add handlers/<type>.
endpoints/ — serversiden af din Mini App. Hver fil er en funktion, som Mini App’en kalder ved navn, så endpoints/getProfile.js besvarer POST /api/getProfile. Platformen tjekker, at kaldet kommer fra en Telegram-bruger inde i din Mini App, før din kode kører, og giver dig den bruger. endpoints/ er flad, ligesom handlers/. Se
Endpoints
.
.tgcloud/ — maskinlokal tilstand administreret udelukkende af CLI’en, inklusiv dine gemte legitimationsoplysninger. Den er git-ignored, og du bør aldrig læse fra eller skrive til den manuelt — brug CLI-kommandoerne i stedet.
Modulsystemet
Ved runtime kan et modul se præcis to ting: platformens SDK og de andre moduler i dit projekt. Der er ingen npm-pakker, intet filsystem og intet netværk undtagen gennem SDK’ets fetch.
Importér dine egne moduler, som du ville i ethvert JavaScript-projekt — ved relativ sti, med .js-udvidelsen — og SDK’et ved sit navn:
// tgcloud/handlers/message.js
import { users } from '../schema.js'; // skema-modulet
import { addItem } from '../lib/cart.js'; // et lib-modul
import { format } from '../lib/internal/fmt.js'; // indlejret lib-modul
import { db, api, fetch } from 'sdk'; // platformens SDK
// tgcloud/lib/cart.js
import { format } from './internal/fmt.js'; // en nabo
import { users } from '../schema.js';
En relativ import skal slutte på .js og kan ikke føre uden for tgcloud/:
import { users } from '../schema'; // tilføj .js
import config from '../../config.js'; // uden for tgcloud/
import { z } from 'zod'; // ingen npm-pakker
Hvert modul har også et navn — dets sti inde i tgcloud/ uden udvidelsen, såsom schema, lib/cart eller handlers/message. Det navn er, hvad npx tgcloud run og CLI’ens output bruger, og et modul kan også importeres ved det (from 'lib/cart', ingen udvidelse). Foretræk relative imports i din kode. De er, hvad din editor forstår — jump-to-definition, autocomplete, omdøbning.
Hvad der er tilgængeligt ved runtime er præcis to ting: sdk og dets undermoduler (sdk/db, sdk/api, sdk/fetch) — hele platformens overflade, se
The SDK
— og dine egne moduler i tgcloud/. Det er hele listen. Hvis din kode importerer noget andet, vil det ikke kunne løses. Denne begrænsning er, hvad der holder moduler hurtige at indlæse og sikre at køre.
Handlere
En handler er et modul i handlers/, hvis standard-eksport platformen påkalder, når en matchende opdatering ankommer.
// handlers/message.js
import { api } from 'sdk';
export default async function (message) {
await api.sendMessage({
chat_id: message.chat.id,
text: `Du sagde: ${message.text ?? '(ingen tekst)'}`,
});
}
En handler modtager opdateringens payload som sit argument — platformen pakker Telegram
Update
ud for dig. handlers/message.js får
Message
(dvs. update.message); handlers/callback_query.js får
CallbackQuery
; og så videre. Handlerens andet argument er et kontekst-objekt pr. kørsel, ctx. Det bærer den rå Update som ctx.update — ræk ud efter den, når du har brug for noget uden for payloaden, såsom update_id.
En handler kan være async (er det normalt) og kan returnere en værdi. Den når Bot API’et, databasen og udgående HTTP gennem SDK’et.
Du behøver ikke en rigtig opdatering for at teste en handler. npx tgcloud run kører den på platformen med den payload, du leverer, og din aktuelle lokale kode:
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hi" }'
Argumentet er payloaden — det samme objekt din handler modtager — i JSON5. For at levere handlerens ctx (dens andet argument), tilføj --ctx, f.eks. --ctx '{ update: { update_id: 1 } }'. Dette kører mod dine lokale filer, så du kan prøve ændringer før udrulning. Se
run
.
Endpoints
Et endpoint er et modul i endpoints/, som din Mini App kalder — stedet for alt det, front-enden ikke kan gøre alene, som at læse og skrive i databasen, kalde Bot API’et eller tale med eksterne tjenester med dine hemmeligheder.
// endpoints/getProfile.js
import { db, EndpointError } from 'sdk';
import { eq } from 'sdk/db';
import { profiles } from '../schema.js';
export default async function (input, ctx) {
const user = ctx.initData.user;
const profile = await db.select().from(profiles).where(eq(profiles.userId, user.id)).get();
if (!profile) throw new EndpointError('Profil ikke fundet', { userId: user.id });
return profile;
}
Det første argument, input, er det JSON-objekt Mini App’en sendte. Det andet, ctx, bærer ctx.initData — Mini App’ens
init data
(user, chat, start_param, auth_date, …), allerede verificeret af platformen. Det har samme form som Telegram.WebApp.initDataUnsafe på klienten, men her kan du stole på det. Et kald uden gyldig init data afvises med 401, før din kode kører, så ctx.initData.user er virkelig den person, der åbnede din Mini App. Endpoints kan kun kaldes på denne måde — fra din bots Mini App, af en Telegram-bruger — så der er ingen offentlig API at beskytte og intet login at bygge.
Hvad end endpointet returnerer, sendes tilbage som JSON. For at afvise et kald, throw new EndpointError(description, parameters?). Mini App’en får beskrivelsen og parameters-objektet, som de er. Hvis du har brug for at forgrene dig baseret på fejlen i front-enden, så sæt en kode i parameters — { code: 'OUT_OF_STOCK' }. Enhver anden undtagelse rapporteres som en generisk intern fejl, og den rigtige besked går kun til dine logs, så interne detaljer aldrig lækkes til klienten.
Kald af et endpoint fra Mini App’en
Mini App’en kalder et endpoint ved navn med Telegram.WebApp.Serverless.call fra det officielle
Mini Apps SDK
, telegram-web-app.js — scriptet din Mini App allerede inkluderer. Det sender init-data for dig og kalder tilbage med en fejl eller endpointets resultat:
Telegram.WebApp.Serverless.call('getProfile', { lang: 'da' }, function (err, profile) {
if (err) {
// err.message — beskrivelsen; err.parameters — hvad din EndpointError bar
if (err.type === 'ENDPOINT_ERROR') showMessage(err.message);
return;
}
render(profile);
});
Inputtet er valgfrit og skal være et simpelt objekt. Uden det er kaldet Serverless.call('getProfile', callback).
err.type er 'ENDPOINT_ERROR', når dit endpoint kastede en EndpointError. Enhver anden værdi betyder, at platformen afviste kaldet, eller at endpointet fejlede uventet, for eksempel 'UNAUTHORIZED', når init-data mangler eller er ugyldige.
Endpoint-navne er som JavaScript-identifikatorer — bogstaver, cifre og _, ikke startende med et ciffer (getProfile, place_order). Opret ét med
npx tgcloud add endpoints/<name>
, og prøv det uden Mini App’en med
run
, hvor du selv sender init-data:
npx tgcloud run endpoints/getProfile '{}' --ctx '{ initData: { user: { id: 1, first_name: "Ann" } } }'
Hvad bliver udrullet
Når du npx tgcloud push, indsamler CLI’en hver .js-fil under schema.js, lib/, handlers/ og endpoints/, og sender det præcise sæt som dit projekts modulrum — sammen med Mini App’ens build-mappe, hvis tgcloud.jsonc navngiver en (se nedenfor). Alt hvad der findes i skyen, men mangler i dit projekt, fjernes, så den udrullede tilstand altid afspejler din mappe — sletninger inkluderet. Intet uden for tgcloud/ udrulles som kode — resten af projektroden er din. Inde i tgcloud/ markeres en .js-fil, der ikke er på en af disse placeringer (tgcloud/util.js, tgcloud/helpers/x.js), så den ikke lydløst forbliver uudrullet. Markdown, dotfiles og .tgcloud/ bliver aldrig udrullet.
Mini App front-end
Projektroden er din, og det er det naturlige sted for din Mini App’s front-end — bygget med React, Vue, Svelte eller almindelig HTML, af Vite eller enhver anden bundler. Serverless hoster dens build-output ved siden af din bot. Én push udruller koden og de statiske filer sammen, og appen serveres på https://app<app_id>.tgcloud.ai/ (push udskriver den præcise adresse — brug den som din Mini App’s URL i
@BotFather
).
Anmeld misbrug. Mini Apps på tgcloud.ai er publiceret af uafhængige udviklere. For at anmelde ulovligt indhold eller misbrug på et
app<app_id>.tgcloud.aiunderdomæne, send en mail til [email protected] .
For at starte, opret front-enden først, med hvilket som helst værktøj du kan lide, og tilføj derefter Serverless ovenpå den:
npm create vite@latest example_app # eller create-vue, …
cd example_app
npm create @tgcloud/bot .
Projekt-generatoren overskriver aldrig dine filer. Den tilføjer tgcloud/-mappen ved siden af front-enden, genkender build-mappen og tilføjer et npm run deploy-script, der bygger appen og udruller den sammen med botten.
Hvad der hostes, indstilles i tgcloud.jsonc, projektets konfigurationsfil i projektroden:
{
"$schema": "./node_modules/@tgcloud/cli/schema/tgcloud.json",
"static": {
"source": "dist",
"spa": true,
"immutable": ["/assets/*"],
"headers": [{ "path": "/*", "set": { "Referrer-Policy": "no-referrer" } }],
"redirects": [{ "from": "/old", "to": "/new", "status": 301 }]
}
}
Kun "source" er påkrævet. Det er build-mappen, relativ til projektroden. De andre nøgler i "static" konfigurerer, hvordan den serveres (nedenfor). init opretter tgcloud.jsonc for dig. Når den genkender front-enden, udfylder den også "source":
| Front-end | init sætter "source" til |
|---|---|
| Vite — React, Vue, Svelte, Solid, … | dist |
| Next.js, Nuxt, SvelteKit, React Router, TanStack Start, Angular | intet — konfigurer en statisk eksport først, og sæt derefter "source" til dens mappe |
Med enhver anden opsætning skal du selv sætte "source" til din build-mappe.
$schema-linjen giver din editor autocomplete og validering for hver nøgle. Filen er JSON med kommentarer og efterfølgende kommaer tilladt. Hvis et andet værktøj du bruger skal læse den som almindelig JSON, navngiv den tgcloud.json i stedet — så skal det være streng JSON.
Byg før du pusher. push uploader mappen præcis som den er på disken — undtagen filer og mapper hvis navn starter med et punktum, som springes over — og kører aldrig dit build, så en almindelig npx tgcloud push efter redigering af front-enden ville udrulle den forrige build. Brug npm run deploy (den kører dit build-script, derefter tgcloud push), eller byg selv først.
Servings-mulighederne:
spa— serverindex.htmlfor stier, der ikke eksisterer og ikke har en filendelse, så en klient-side router virker ved genindlæsning. Stier med en endelse får stadig404.immutable— mønstre for indholdshashed-filer (Vites/assets/*), som browsere kan cache i et år. Brug det kun til indholdshashed-filer. Enhver anden fil, der matches her, ville forblive forældet i browsere i et år, og der er ingen måde at genkalde den på.headers— svar-headere for de stier, en regel matcher (en præcis sti eller et præfiks der ender på/*). Ennull-værdi fjerner en header.redirects— redirect-regler, hvor det første match vinder. En eksisterende fil slår altid en regel.statuser301, medmindre du sætter den."static": "dist"— en forkortelse for{ "source": "dist" }, når du intet andet behøver."static": false— fjern det udrullede site ved næste push. At slette nøglen i stedet stopper bare dette projekt fra at administrere statiske filer, og hvad end der er udrullet, forbliver.
Sitet serveres fra roden af sin egen host, så din bundlers standard base (/) er korrekt. Telegram Web åbner Mini Apps i en iframe, så tilføj ikke X-Frame-Options eller en restriktiv frame-ancestors-politik.
Samme host besvarer dine
endpoints
under /api/ — det er dér, Telegram.WebApp.Serverless.call('getProfile', …) går hen. Læg ikke dine egne filer under /api/ i build-mappen.
Databasen
Hver bot får sin egen database — et SQLite-baseret lager, der forbliver gemt mellem kørsler og er tilgængeligt for hvert modul gennem db. Du beskriver dine tabeller i schema.js med en lille, typet DSL; du læser og skriver dem med en flydende query-builder; og du udvikler dem med reviewede migreringer.
Kender du Drizzle ORM ? Så ved du allerede, hvordan du taler med databasen her. Skema-DSL’en og query-builderen følger Drizzle — kolonne-byggere,
select().from().where(), operatorerne,onConflictDoUpdate,.returning()ogsql-tagget opfører sig alle, som du forventer, så læsning og skrivning af data er den velkendte API, du allerede bruger. Du importerer det bare frasdk/db, og et par platforms-specifikke ting (navnlig ingen fremmednøgler ) bliver påpeget, hvor de opstår.
Deklarering af tabeller
Tabeller er navngivne eksport-elementer i schema.js. At kalde table() bygger en beskrivelse ved indlæsningstid (den rører ikke databasen); platformen opdager de eksporterede tabeller, når du udruller schema.js, og migrerer databasen for at matche.
import { table, integer, text, boolean, json, index, sql } from 'sdk/db';
export const users = table('users', {
id: integer('id').primaryKey({ autoIncrement: true }) ,
tgId: integer('tg_id').unique() ,
name: text('name').notNull() ,
lang: text('lang').default('en') ,
isAdmin: boolean('is_admin').default(false) ,
prefs: json('prefs') ,
created: integer('created_at', { mode: 'timestamp' }).default(sql`(unixepoch())`) ,
}, (t) => ({
createdIdx: index('idx_users_created').on(t.created) ,
}));
table(name, columns, extras?):
nameer SQL-tabelnavnet;columnser et kort over JS-egenskab → kolonne-definition;extraser en valgfri callback(t) => ({ … })hvorteksponerer kolonnerne (t.createder en reference til den kolonne) — deklarér indekser og tabel-niveau begrænsninger her.
Kolonnetyper
| Fabrik | SQLite type | Noter |
|---|---|---|
text() |
TEXT | |
integer() |
INTEGER | |
real() |
REAL | alias float() |
numeric() |
NUMERIC | |
blob() |
BLOB | læser/skriver Uint8Array |
boolean() |
INTEGER | gemt som 0/1, læses som true/false |
json() |
TEXT | auto JSON.stringify / JSON.parse |
Kolonnenavn-argumentet er valgfrit — udelad det, og JS-nøglen bruges.
mode-indstillingen styrer hvordan værdier konverteres mellem SQLite og JavaScript.
mode |
Gemt som | JS værdi |
|---|---|---|
boolean |
INTEGER 0/1 | boolean |
json |
TEXT (JSON) | ethvert objekt/array |
timestamp |
INTEGER (unix sekunder) | Date |
timestamp_ms |
INTEGER (unix ms) | Date |
bytes |
BLOB | Uint8Array |
boolean() og json() er forkortelser for integer(name, { mode: 'boolean' }) og text(name, { mode: 'json' }).
En blob() læser og skriver en Uint8Array — runtime har ingen Node Buffer (og en Buffer er en Uint8Array underklasse, så dette er den bærbare basistype). mode ovenfor styrer kun hvordan en værdi kodes, uafhængigt af kolonnens lagringstype: så blob('col', { mode: 'json' }) er BLOB-modstykket til json() — den samme JSON-kodning, holdt i en BLOB-kolonne i stedet for TEXT.
TLDR:
blob()brugerUint8Array.modestyrer kodning, ikke lagring, såblob(..., { mode: 'json' })gemmer JSON som en BLOB, mensjson()gemmer det som TEXT.
Kolonne-modifier-elementer
Kolonne-modifier-elementer kædes på en kolonne.
integer('id').primaryKey({ autoIncrement: true })
text('name').notNull()
text('tg').unique()
text('lang').default('en')
integer('created_at', { mode: 'timestamp' }).default(sql`(unixepoch())`)
text('slug').generatedAlwaysAs(sql`lower(name)`, { mode: 'stored' }) // eller 'virtual'
text('email').deprecated('replaced by login') // markerer kolonnen til fjernelse
.default()på enjson()-kolonne koder værdien for dig; ensql`…`default videregives ordret..deprecated()er terminal — se Migreringer .
Indekser og begrænsninger
Indekser og begrænsninger deklareres i extras-callback’en, hvor kolonnerne er i scope.
table('t', { /* … */ }, (t) => ({
uq: unique('uq_email').on(t.email) ,
chk: check('chk_done', sql`${t.done} in (0, 1)`) ,
idx: index('idx_name').on(t.col) ,
uidx: uniqueIndex('uidx_email').on(t.email) ,
lower: index('idx_lower').on(sql`lower(${t.email})`) , // udtryks-indeks
active: index('idx_active').on(t.userId).where(sql`done = 0`) , // delvist indeks
}));
Tabel-niveau modifier-elementer kædes efter table(...): .strict(), .withoutRowid(), .deprecated('reason').
Ingen fremmednøgler
Runtime kører med PRAGMA foreign_keys slået fra. En deklareret fremmednøgle ville være lydløst inaktiv — ingen kaskader, ingen beskyttelse mod forældreløse rækker — hvilket er værre end slet ikke at have nogen, så DSL’en gør det umuligt. Navnlig vil .references() og tabel-niveau foreignKey() kaste en fejl når de deklareres, og et skema der bruger dem, vil ikke blive udrullet.
Du bør modellere relationer med almindelige kolonner (userId: integer('user_id')) og håndhæve integritet i din applikationskode: indsæt forældre før børn, slet børn før forældre, håndtér fejl korrekt og ryd op i forældreløse rækker med en LEFT JOIN … WHERE parent.id IS NULL, når du har brug for det.
Fraværet af fremmednøgler er en bevidst begrænsning, ikke en forglemmelse. Planlæg efter det tidligt, når du designer din bot.
Querying
db er en flydende query-builder. Hver forespørgsel er asynkron — terminal-metoderne (.all(), .get(), .values(), .run()) returnerer Promises, så altid await.
import { db } from 'sdk';
import { eq, and, desc, asc, count, sql } from 'sdk/db';
import { users, todos } from '../schema.js';
await db.select().from(todos).all(); // alle rækker
await db.select().from(todos).where(eq(todos.id, 1)).get(); // første række eller null
await db.select().from(todos).values(); // rækker som værdi-arrays
await db.select().from(todos)
.where(and(eq(todos.userId, uid) , eq(todos.done, false)))
.orderBy(desc(todos.priority) , asc(todos.id))
.limit(10).offset(20)
.all();
// custom projektion: { alias: columnRef | sqlExpr | aggregate }
await db.select({ id: todos.id, title: todos.text, n: count() })
.from(todos).groupBy(todos.userId).having(sql`count(*) > ${1}`).all();
// række-tælling — en hjælper, ikke en builder-terminal:
await db.$count(todos); // alle rækker
await db.$count(todos, eq(todos.done, false)); // med et filter
- Kædelige:
.where(),.orderBy(),.limit(),.offset(),.groupBy(),.having(),.distinct(). - Terminaler:
.all(),.get(),.values(). - Tæl rækker med
db.$count(table, where?)(ellercount()i en projektion).
Indsæt, opdater, slet:
await db.insert(todos).values({ userId: 1, text: 'Køb mælk' }).run();
await db.insert(todos).values([{ text: 'A' }, { text: 'B' }]).run(); // batch
await db.insert(todos).values({ text: 'X' }).returning().run(); // RETURNING *
await db.insert(users).values({ tgId: 42, name: 'Ann' })
.onConflictDoUpdate({ target: users.tgId, set: { name: 'Ann' } }).run();
await db.update(todos).set({ done: true }).where(eq(todos.id, 1)).run();
await db.delete(todos).where(eq(todos.id, 1)).run();
Bemærk at en batch-indsættelse er ét statement, så den er begrænset af SQLite’s variabel-grænse (
rækker × kolonner); hvis du går over den, fejler indsættelsen — opdel den selv.
Operatorer importeres fra sdk/db:
import {
eq, ne, gt, gte, lt, lte,
like, notLike,
isNull, isNotNull, and, or, not,
between, notBetween, inArray, notInArray,
count, sum, avg, min, max,
asc, desc,
} from 'sdk/db';
.where(a, b) med flere argumenter er det samme som and(a, b). En sammenlignings andet argument er en værdi som standard, men kan være en anden kolonne eller sql`…` — f.eks. eq(a.x, b.y). Aggregater (count/sum/avg/min/max) er sql-fragmenter til en .select({ … }) projektion.
Rå SQL — når builderen ikke er nok, så brug rå SQL. Moden vælges ved metode: db.run til skrivning, db.all til mange rækker, db.get til én række, db.values til rækker som positionelle arrays.
await db.run('UPDATE todos SET done = 1 WHERE id = :id', { ':id': 5 });
// → { rowsAffected: 3, lastInsertRowid: 0, rows: [] }
await db.all(sql`SELECT * FROM todos WHERE done = ${false}`);
await db.get(sql`SELECT count(*) AS c FROM todos`);
await db.values(sql`SELECT id, text FROM todos`); // → [[1, 'køb mælk']]
En skrivning resulterer i et kørselsresultat — rowsAffected, lastInsertRowid og rows, som bærer en RETURNING-klausul, hvis SQL’en havde én. Brug db.values, når en forespørgsel projicerer to kolonner med samme navn (et join som SELECT u.id, o.id): db.all nøgler rækker efter kolonnenavn, så den anden id ville overskrive den første.
sql`…` tagget gør ${value} til en bundet parameter og ${table.column} til en identifikator, og splejser indlejrede sql-fragmenter. Brug sql.raw('…') til et literalt udtryk uden parametre.
Rå forespørgsler er ikke bundet til en tabel, så deres rækker kommer tilbage uden mode-konvertering — en boolean er
0/1, en JSON-kolonne er en streng, et tidsstempel er et tal. Kun den tabel-bundne builder konverterer værdier.
Migreringer
Din database ændrer sig, efterhånden som din bot vokser. Platformen holder det sikkert ved at adskille udrulning af kode fra ændring af data, og ved at klassificere hver skema-ændring efter, hvor risikabel den er.
Udrulning rører aldrig ved databasen. Når du npx tgcloud push en ændret schema.js, registrerer platformen det nye skema og fortæller dig, hvad databasen ville ændre — men anvender intet:
npx tgcloud push # udrul schema.js; rapporterer afventende DB-ændringer
npx tgcloud migrate # gennemse og anvend dem
npx tgcloud migrate beregner forskellen mellem dit skema og den live database og guider dig gennem den. Du bliver altid spurgt, før noget anvendes. Dette betyder, at en rutinemæssig kode-udrulning aldrig kan udløse en datamigrering ved et uheld.
Hver afventende ændring bærer en status, der bestemmer hvordan migrate behandler den:
| Status | Betydning | I migrate |
|---|---|---|
| safe | Additiv og ikke-blokerende — en ny tabel, kolonne eller indeks | Anvendt sammen i ét trin, ved bekræftelse |
| warning | Potentielt destruktiv eller langsom — sletning, eller et indeks på en enorm tabel | Præsenteret én ad gangen, hver bekræftet separat |
| manual | Kan ikke gøres automatisk — f.eks. ændring af en kolonnetype | Vist med vejledning; du udfører det manuelt |
| undocumented | Eksisterer i databasen men ikke i dit skema | Vist for opmærksomhed; ikke anvendt |
Safe-ændringer er hurtige og reversible i ånden, så de gennemføres sammen. Hver advarsel er en bevidst, individuel bekræftelse — der er ingen “anvend alt” for destruktive ændringer.
Manuelle ændringer kommer med en grund og en foreslået handling. migrate slutter med en opsummering: hvor mange ændringer blev anvendt, sprunget over, afventer manuel rettelse eller ikke i dit skema.
Se
migratefor flagene (--dry-run,--safe,--yes,--local).
Fjernelse af ting
Sletning af en tabel eller kolonne fra schema.js dropper den ikke — det ville gøre en utilsigtet sletning katastrofal. For at fjerne noget, markér det som deprecated:
// drop en kolonne
text('email').deprecated('erstattet af login')
// drop en hel tabel
export const oldSessions = table('old_sessions', { /* … */ }).deprecated('ubrugt');
Ved næste migrate dukker det deprecated objekt op som en warning-status sletning, som du bekræfter individuelt. Når det er væk, fjern deklarationen.
Ændring af en kolonnetype
Typeændringer er manuelle — SQLite kan ikke altid gøre dem på stedet, og tvingning af eksisterende værdier er en skønsmæssig vurdering. migrate vil vise ændringen og dens begrundelse; udfør den selv med rå SQL (db.run(...)), typisk ved at oprette en ny kolonne eller tabel, kopiere data og bytte rundt.
SDK’et
Ved runtime har et modul ét bibliotek: sdk. Det samler de tre ting, en bot-backend har brug for — en database, Telegram Bot API’et og udgående HTTP — uden noget der skal installeres og ingen legitimationsoplysninger der skal konfigureres. Databasen (db) er dækket i
Databasen
; denne sektion dækker api, fetch og den globale console.
import { db, api, fetch, InputFile, BotApiError, EndpointError } from 'sdk'; // hele overfladen
// eller fra undermoduler:
import { table, integer, text, eq, sql } from 'sdk/db';
import { api } from 'sdk/api';
import { fetch } from 'sdk/fetch';
| Import | Hvad det er |
|---|---|
db |
Database — query-builder og skema-DSL → Databasen |
api |
Telegram Bot API — api.sendMessage(...) →
nedenfor
|
fetch |
Udgående HTTP → nedenfor |
InputFile |
En fil (bytes + navn) at uploade — virker med både api og fetch →
Filer
|
EndpointError |
Afvis et Mini App-kald med en besked den kan vise → Endpoints |
Importér dine egne projektmoduler ved relativ sti med .js-udvidelsen (from '../schema.js', from '../lib/cart.js'). Se
Modulsystemet
.
Bot API’et
api giver dig hele
Telegram Bot API’et
. Kald enhver metode som api.<metode>(params). Enhver nuværende — og fremtidig — Bot API-metode virker uden behov for SDK-opdatering.
import { api } from 'sdk';
const me = await api.getMe(); // → det udpakkede resultat
await api.sendMessage({ chat_id: id, text: 'Hej!' });
await api.editMessageText({ chat_id, message_id, text: 'Opdateret' });
await api.answerCallbackQuery({ callback_query_id, text: 'Udført' });
Svar-konvolutten er udpakket. Bot API’et pakker normalt resultater ind i { ok: true, result: … }. api returnerer result direkte — getMe() løser til brugerobjektet, ikke til en wrapper. Parametre bruger Bot API’ets egne snake_case-navne (chat_id, message_id, reply_markup, …).
Fejl kaster BotApiError. Når Bot API’et returnerer { ok: false }, kaster kaldet en BotApiError i stedet for at returnere en falsy værdi, så du ikke ved et uheld kan ignorere den. Fejlen bærer .code (Bot API error_code), .description (den menneskelige læsbare besked), .method (hvilken metode fejlede) og .parameters (ekstra data såsom retry_after på en 429, eller migrate_to_chat_id). Catch den for at håndtere en forventet fejl og genkast resten:
import { api, BotApiError } from 'sdk';
try {
await api.deleteMessage({ chat_id, message_id });
} catch (e) {
if (e instanceof BotApiError && e.code === 400) {
// 400 = beskeden er allerede væk; det er fint her.
} else {
throw e;
}
}
Filer
Arbejd med fil-bytes, ikke kun file_ider — upload nye filer og download eksisterende.
Upload — InputFile. Pak rå bytes ind i en InputFile (en simpel holder: bytes, et filnavn og en valgfri MIME-type) og send den, hvor en Bot API-metode forventer en fil:
import { api, InputFile } from 'sdk';
const doc = new InputFile(bytes, 'rapport.pdf', { type: 'application/pdf' });
await api.sendDocument({ chat_id, document: doc });
// filer indlejret i params virker også — f.eks. et album:
await api.sendMediaGroup({ chat_id, media: [
{ type: 'photo', media: new InputFile(a, 'a.jpg', { type: 'image/jpeg' }) },
{ type: 'photo', media: new InputFile(b, 'b.jpg', { type: 'image/jpeg' }) },
]});
Bytes streames til Telegram som en rigtig multipart-upload — filen bufferes ikke som helhed i ét kald til platformen — men Telegrams Bot API-grænser gælder stadig: en bot kan i øjeblikket uploade op til 50 MB pr. fil (og 10 MB for et billede).
Download — via file_id. To hjælpere trækker en fils bytes tilbage. Platformen løser file_id for dig (den kører getFile og henter indholdet bag kulisserne), så du rører aldrig ved bot-tokenet eller en download-URL:
const bytes = await api.getFileContent(file_id); // → Uint8Array (hele filen)
const { file, body } = await api.getFileStream(file_id); // stream den i stedet
file.file_unique_id; // getFile-info
for await (const chunk of body) { /* Uint8Array */ } // eller buffer den: await stream.bytes()
Telegrams Bot API begrænser
getFile-downloads til 20 MB, så det er loftet for begge hjælpere.
HTTP
fetch er en fetch-lignende klient til at kalde omverdenen — tredjeparts-API’er, webhooks, alt over HTTP.
import { fetch } from 'sdk';
const res = await fetch('https://api.example.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Pavel' }) ,
});
if (!res.ok) throw new Error(res.statusText);
const data = await res.json();
Svaret spejler web-platformen: res.status, res.statusText, res.ok (sand for 200–299), res.url, res.headers (.get(), .has(), .keys(), .entries()), og body-læsere await res.json() / await res.text().
Du kan også læse bodyen inkrementelt som en stream —
for await (const chunk of res.body) { … }— hvilket er måden, du forbruger server-sent events eller token-for-token output fra AI API’er.
Body-hjælpere sætter den matchende Content-Type for dig:
await fetch(url, { method: 'POST', body: fetch.body.json({ a: 1 }) }); // application/json
await fetch(url, { method: 'POST', body: fetch.body.form({ a: 1 }) }); // x-www-form-urlencoded
await fetch(url, { method: 'POST', body: fetch.body.text('hi') }); // text/plain
Request-bodies kan også bære filer og rå bytes: send en Uint8Array, en InputFile eller en FormData (til multipart/form-data) som body, og bytes streames ud — så du kan uploade til et tredjeparts-API på samme måde, som du sender filer til Telegram:
import { fetch, FormData, InputFile } from 'sdk';
const form = new FormData();
form.append('file', new InputFile(bytes, 'photo.jpg', { type: 'image/jpeg' }));
form.append('caption', 'fra min bot');
const res = await fetch('https://api.example.com/upload', { method: 'POST', body: form });
Den opfører sig ellers som den standard fetch, du allerede kender, med to begrænsninger:
- Svaret læses som tekst —
await res.json()/await res.text(), eller streames chunk-for-chunk medres.body. - Det samlede svar er begrænset til 30 MB. Den grænse dækker hele svaret — streaming med
res.bodylader dig behandle en stor body inkrementelt, men det hæver ikke grænsen.
Logning
Den globale console er tilgængelig — intet skal importeres, den er der bare som i ethvert JavaScript. Dens output fanges og vises af npx tgcloud run, hvilket gør den til dit primære debug-værktøj under udvikling.
console.log('behandler', { chatId: id }); // log / debug — almindelig
console.info('startet'); // info — blå
console.warn('rate limited'); // warn — gul
console.error(err); // error — rød, med stack trace
Hver linje er tagget med [fil:linje], den kom fra. console.error og console.trace tilføjer en fuld stack, mens console.warn ikke gør. Når du npx tgcloud run et modul, udskrives disse linjer med et farvet præfiks pr. niveau, tiden siden kørslen startede, og oprindelsen — se
run
.
Kommandolinjegrænseflade
tgcloud er broen mellem din projektmappe og skyen. Den opretter projekter, viser hvad der er ændret, udruller, kører moduler og anvender databasedatabase-migreringer. Den kræver
Node.js
18 eller nyere. To måder at få den på:
# Anbefalet — opret et projekt med CLI'en installeret i det:
npm create @tgcloud/bot example_bot
# Eller installer CLI'en globalt og init en tom mappe:
npm install -g @tgcloud/cli
tgcloud init
Npm-pakken er @tgcloud/cli, og kommandoen den installerer er tgcloud. Hver kommando virker fra enhver undermappe i dit projekt.
| Kommando | Formål |
|---|---|
init
|
Opret et nyt projekt i den aktuelle mappe |
add
|
Opret et nyt modul (en handler, et endpoint eller et lib-modul) |
login
|
Forbind projektet til en bot (gemmer tokenet) |
status
|
Vis hvad der er ændret lokalt vs. skyen |
diff
|
Vis linje-for-linje ændringerne |
push
|
Udrul ændrede moduler og Mini App’ens build til skyen |
migrate
|
Anvend skema-ændringer på databasen |
run
|
Kør et modul på platformen uden udrulning |
fetch
|
Tjek hvad der er udrullet, uden at ændre lokale filer |
pull
|
Bring lokale filer på linje med skyen |
reset
|
Kassér lokale ændringer; gendan fra skyens tilstand |
webhook
|
Inspicér og gen-synkronisér den platform-styrede webhook |
completion
|
Udskriv et shell-fuldførelsesscript (bash/zsh/fish) |
upgrade
|
Flyt et pre-0.2.0 projekt-layout ind i tgcloud/ |
Autentificering
Et projekt er bundet til én bot af dets token, som har formatet app<id>:<secret>. app<id>-delen er offentlig og kan udskrives; secret-delen optræder aldrig i logs eller fejl.
Tokenet løses i denne rækkefølge:
TGCLOUD_TOKENmiljøvariabel — til CI; skrives aldrig til disk..tgcloud/credentials— skrevet afnpx tgcloud login.- Ingen af delene → en fejl der peger på
npx tgcloud login.
CLI’en spørger aldrig efter et token midt i en kommando. Login er altid det eksplicitte login-trin. Hvis et gemt token bliver ugyldigt, fjerner CLI’en det og beder dig logge ind igen.
init
npx tgcloud init
Opretter et nyt projekt i den aktuelle mappe: tgcloud/-mappen med schema.js, lib/, handlers/ og en start-handler, plus tgcloud.jsonc, AGENTS.md, docs/ og .tgcloud/-tilstandsmappen. Kør den også i roden af et eksisterende front-end-projekt — den tilføjer tgcloud/-mappen og dokumentationen og overskriver aldrig dine filer. Hvis den genkender front-enden, peger den også tgcloud.jsonc mod build-mappen og tilføjer et deploy-script (byg, derefter push) til din package.json — kun hvis du ikke allerede har et deploy-script. Se
Mini App front-end
.
init nægter at oprette et projekt inde i et andet projekt. At gen-køre den i et projekts egen rod er fint og tilføjer bare alt manglende.
add
npx tgcloud add <target>
Opretter ét nyt modul, forbundet og klar til redigering — en handler, et endpoint eller et lib/-modul.
npx tgcloud add handlers/callback_query # en ny opdaterings-handler
npx tgcloud add endpoints/getProfile # et nyt endpoint til Mini App'en
npx tgcloud add lib/cart # et nyt delt modul
Bemærk at
addaldrig overskriver en eksisterende fil.
<target> er modulets sti (en efterfølgende .js er valgfri). For handlers/ skal navnet være en Telegram-opdateringstype. Platformen reklamerer med det gyldige sæt, så et ugyldigt navn afvises med det samme. handlers/ og endpoints/ er flade, mens lib/ kan være indlejret (lib/payments/stripe).
Modulnavnet er påkrævet. At give kun mappen er en fejl — men en nyttig én: for handlers/ lister den de opdateringstyper, du ikke allerede har, så du kan kopiere én.
$ npx tgcloud add handlers
Fejl: Angiv et navn, f.eks. "handlers/callback_query". Tilgængelige handlers/-typer: callback_query, inline_query, chat_member, …
<Tab>-fuldførelse tilbyder det samme sæt — secompletion.
Den genererede fil har en live export default, så handleren er aktiv så snart du udruller — der er intet der skal uncommentes. Udrul det nye modul med
push
.
login
npx tgcloud login
Spørger efter dit CLI-adgangstoken — fra
@BotFather
→ din bot → Serverless → CLI Access → Access token, et separat token fra din
bots API-token
— validerer det mod platformen og gemmer det i .tgcloud/credentials.
loginer den eneste kommando der spørger efter et token, og den kræver en interaktiv terminal. I CI, sætTGCLOUD_TOKENi stedet.
status
npx tgcloud status
Viser, pr. fil, hvad der har ændret sig mellem din arbejdskatalog og den udrullede kopi: modificeret, ny, slettet, uændret. Den virker offline, sammenlignet mod tilstanden i skyen fra din sidste push, pull eller fetch. Den advarer også om vildfarne .js-filer inde i tgcloud/, som ikke bliver udrullet.
For et projekt med en Mini App viser status også hvad der har ændret sig i dens build-mappe siden sidste udrulning.
diff
npx tgcloud diff
Ligesom status, men viser de faktiske linje-for-linje forskelle for ændrede moduler. Også offline.
push
npx tgcloud push [files...]
Udruller dit projekt til skyen i én atomar batch.
Med ingen argumenter, udruller den hele projektet, og den udrullede tilstand bringes til at afspejle din mappe præcis — moduler du slettede lokalt fjernes i skyen.
Med fil- eller mappeargumenter (npx tgcloud push tgcloud/handlers/message.js, npx tgcloud push handlers/), udruller den kun ændringerne i de filer. En målrettet push sletter aldrig andre moduler. Et mål kan staves som filstien (tgcloud/handlers/message.js), stien inde i tgcloud/ (handlers/message.js) eller modulnavnet (handlers/message) — de betyder alle det samme modul.
Dens ene mulighed er --force — for at springe concurrency-tjekket over og overskrive hvad end der er i skyen. Brug den kun når du er sikker (se
Staying in sync
).
Hvis tgcloud.jsonc navngiver en statisk mappe, udruller en fuld push den i samme batch — kun de filer der ændrede sig uploades — og udskriver sitets adresse. Byg først, fordi push aldrig kører dit build (npm run deploy gør begge). npx tgcloud push dist/ udruller de statiske filer alene, og en push der kun navngiver moduler lader dem være urørte.
Efter en udrulning, hvis schema.js ændrede sig og databasen ikke er synkroniseret, udskriver push en opsummering af de afventende ændringer og foreslår npx tgcloud migrate. Den anvender dem aldrig selv.
migrate
npx tgcloud migrate
Anvender dine skema-ændringer på databasen. Den beregner forskellen mellem schema.js og den live database, og guider dig derefter gennem den ét trin ad gangen med en løbende [N/M] tæller:
- En kort opsummering af alle afventende ændringer.
- Safe-ændringer, anvendt sammen i ét trin ved din bekræftelse.
- Advarsler (sletninger, langsomme operationer), én ad gangen, hver bekræftet separat.
- Manuelle ændringer, vist med en grund og foreslået handling, ikke anvendt automatisk.
- Undokumenterede objekter (i databasen men ikke dit skema), vist for opmærksomhed.
Den slutter med en opsummering: anvendt, sprunget over, afventer manuel rettelse, ikke i skema.
Se Migreringer for modellen.
Muligheder: --dry-run (udskriv alt, anvend intet), --safe (anvend automatisk safe-ændringer, spring advarsler over), --yes (anvend automatisk safe-ændringer og enhver advarsel, spring manuel over — brug med forsigtighed), --local (sammenlign mod din lokale schema.js i stedet for den udrullede). Uden et flag kræver migrate en terminal og fejler i et ikke-interaktivt miljø frem for at gætte.
run
npx tgcloud run <module> [args] [--ctx <json5>]
Kører en handler eller et endpoint på platformen uden udrulning, ved hjælp af dine aktuelle lokale filer. Dette er det hurtige indre loop til test af logik.
<module>— et simpelt navn (søgt underhandlers/ogendpoints/) eller en sti somhandlers/message. Et simpelt navn der eksisterer i begge mapper skal staves med sin mappe.[args]— payloaden sendt til handleren, skrevet i JSON5 så du kan undlade at citere nøgler. Det er det opdateringstype-objekt, din handler modtager (f.eks. en Besked tilhandlers/message), ellerinputtil et endpoint, og det skal være et objekt — ikke et array eller en simpel streng.--ctx <json5>— modulets kontekst-objekt (dens andet argument), også JSON5. Brug det til at levere hvad din kode læser fractx— den rå opdatering for en handler,--ctx '{ update: { update_id: 1 } }', eller init-data for et endpoint,--ctx '{ initData: { user: { id: 1 } } }'.runverificerer den ikke, da du tester din logik, ikke platformens tjek.
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hi" }'
Platformen kører modulet mod det modulrum, der er samlet fra dit lokale projekt (så lokalt ændret lib/-kode bruges også) og returnerer returværdien, alt hvad der er logget med console.*, og den medgåede tid. Læs store argumenter fra en fil med npx tgcloud run handlers/message "$(cat message.json5)".
fetch
npx tgcloud fetch
Tjekker hvad der er udrullet, uden at røre dine arbejdsfiler. Derefter sammenligner status og diff mod den seneste sky-tilstand. Nyttigt til at se en konflikt før du beslutter dig for, hvordan den skal løses.
pull
npx tgcloud pull
Bring dit lokale projekt på linje med skyen ved at opdatere dine arbejdsfiler til den udrullede tilstand.
pull skriver aldrig din Mini App’s build-mappe.
reset
npx tgcloud reset
Kassér dine lokale ændringer og gendan modulerne fra den sidst kendte sky-tilstand. Brug den til at smide et eksperiment væk. Mini App’ens build-mappe røres ikke.
webhook
npx tgcloud webhook
npx tgcloud webhook sync [--drop-pending]
Telegram leverer opdateringer til din bot gennem en webhook, som platformen styrer for dig — du peger aldrig på noget manuelt. npx tgcloud webhook viser dens aktuelle tilstand: URL’en, allowed_updates-listen, hvor mange opdateringer der afventer, den sidste leveringsfejl (hvis nogen), og om den er synkroniseret med dine udrullede handlere.
“Synkroniseret” betyder, at webhook’en peger på platformen, og dens allowed_updates matcher de handlere, du har udrullet — så Telegram leverer præcis de opdateringstyper, du håndterer, og intet andet. Udrulning af en ny handler (eller fjernelse af én) kan efterlade webhook’en ude af synk, indtil den er opdateret; npx tgcloud status flagger også dette.
npx tgcloud webhook sync retter det — den peger webhook’en mod platformen igen og genopbygger allowed_updates fra dine udrullede handlere. Tilføj --drop-pending for at kassere opdateringer Telegram allerede havde køet før synkroniseringen (ellers leveres de når webhook’en er sund igen).
completion
Bemærk: tab-fuldførelse virker kun når en simpel
tgclouder på dinPATH— så installer den globalt (npm install -g @tgcloud/cli) eller placer på anden vis bin-filen på dinPATH. Den kan ikke hooke sig ind inpx.
tgcloud completion <bash|zsh|fish>
Udskriver et shell-fuldførelsesscript til stdout. Aktivér den én gang, derefter fuldfører <Tab> kommandoer, flag, modulmapper, de handler-opdateringstyper du ikke har endnu, og dine lokale kørbare moduler.
# bash — kræver bash-completion pakken:
echo 'eval "$(tgcloud completion bash)"' >> ~/.bashrc
# zsh — sikr at `autoload -U compinit && compinit` kører i din ~/.zshrc:
echo 'eval "$(tgcloud completion zsh)"' >> ~/.zshrc
# fish:
tgcloud completion fish > ~/.config/fish/completions/tgcloud.fish
Genstart din shell (eller re-source filen) bagefter. Kørsel af tgcloud completion uden shell udskriver disse instruktioner igen.
upgrade
npx tgcloud upgrade [--dry-run]
Engangs-migrering for projekter oprettet før CLI 0.2.0, som beholdt schema.js, lib/ og handlers/ i projektroden. Den flytter dem ind i tgcloud/. Din forbindelse til botten bevares, og intet behøver at blive gen-udrullet. --dry-run udskriver de planlagte flytninger uden at røre noget. Andre kommandoer kører ikke på det gamle layout, før du opgraderer.
Staying in sync
Hvert projekt har en monotont stigende revision i skyen, der stiger ved hver udrulning. CLI’en husker den revision, den sidst synkroniserede med, og sender den ved hver push. Hvis skyen er kommet foran — fordi en anden maskine eller teammedlem udrullede — afvises pushet i stedet for lydløst at overskrive deres arbejde, og CLI’en tilbyder tre veje frem:
npx tgcloud fetch # se hvad der ændrede sig i skyen, tjek derefter igen
npx tgcloud pull # bring det seneste ind i dine arbejdsfiler
npx tgcloud push --force # overskriv skyens tilstand (farligt)
Dette optimistiske-concurrency tjek er grunden til, at du kan dele en bot på tværs af et team uden en lockstep-udrulningsproces. Kommandoer afsluttes ikke-nul ved fejl — en afvist udrulning, en fejlet migrering, en autentificeringsfejl, et modul der kastede en fejl under run — så de kan kombineres rent i scripts og CI-pipelines.