Telegram Serverless låter dig köra backend-kod för din bot och Mini App direkt på Telegrams infrastruktur — inga servrar att provisionera, inga containrar att hålla igång, ingen skalning att tänka på. Du skriver vanlig JavaScript-kod, distribuerar den med ett enda kommando, och Telegram kör den i en snabb, isolerad V8-sandbox som ligger precis intill Bot API:et och en inbyggd databas.
Om du någonsin har kopplat en bot till en VPS, en cloud function eller en hosting-panel bara för att svara på ett /start, så är det här den delen du inte längre behöver göra.
Varför serverless
En Telegram-bot är i grunden ett program som reagerar på uppdateringar. Traditionellt var du tvungen att hosta programmet någonstans som alltid är igång, nåbart och säkert — och sedan hålla det så. Telegram Serverless tar bort det lagret helt:
- Ingen infrastruktur. Det finns ingen maskin att hyra, patcha eller övervaka. Din kod körs vid behov och skalas automatiskt med din bot.
- Autentiseringsuppgifter inkluderade. Telegram Bot API, en SQLite-baserad databas och utgående HTTP är tillgängliga för varje modul direkt — inget att installera, inga autentiseringsuppgifter att konfigurera.
- Snabb, isolerad körning. Varje anrop körs i en lättviktig V8-isoleringsmiljö, nära Telegrams egna system, så anrop till Bot API:et och din databas är snabba och pålitliga.
- Ett riktigt utvecklarflöde. Ett projekt lever i en mapp på din maskin under versionshantering. Du redigerar filer, ser exakt vad som ändrats, distribuerar atomärt och rullar din databasschema framåt med granskade migreringar — precis som du redan arbetar med allt annat.
Den mentala modellen: Hur projektet fungerar
Du arbetar på tre ställen, och de mappar tydligt mot varandra:
| Var | Vad finns där |
|---|---|
| Din projektmapp | JavaScript-moduler — schema, delad kod, uppdateringshanterare, Mini App-endpoints — och, om du har en, din Mini Apps frontend |
| Molnet | Den distribuerade kopian av dessa moduler, din bots databas och din Mini Apps filer |
tgcloud CLI:et |
Bryggan — den visar dig skillnader och synkroniserar dem |
Du loggar aldrig in via SSH på något. Du redigerar filer lokalt, kör npx tgcloud push, och plattformen tar hand om resten. Din bots trafik hanteras av de distribuerade modulerna; din databas kvarstår mellan anrop.
Ett projekt har bara fyra sorters kod, allt inuti en tgcloud/-mapp:
tgcloud/
├─ endpoints/ # funktioner som din Mini App anropar
├─ handlers/ # funktioner som reagerar på Telegrams uppdateringstyper
├─ lib/ # delad kod som du importerar varifrån som helst
└─ schema.js # dina databastabeller
När en uppdatering kommer — ett meddelande, en knapptryckning, en inline-fråga — dirigerar Telegram den till matchande hanterare (handlers/message.js, handlers/callback_query.js, …) och anropar dess standardexport. Den funktionen pratar med Bot API:et och databasen via SDK:n och returnerar. Det är hela loopen. En uppdatering utan matchande hanterare ignoreras helt enkelt, så du lägger bara till de hanterare du behöver.
En Mini App är valfri. Om din bot har en, lever dess frontend i samma projekt, bredvid tgcloud/. Serverless hostar dess byggfiler och kör de endpoints/ den anropar. Se
Mini App-frontend
.
Snabb demo
Här är en komplett, fungerande demobot. Den svarar på varje meddelande och kommer ihåg hur många den har sett från varje chatt.
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;
// Infoga räknaren, eller öka den om chatten redan har en — och få tillbaka
// den resulterande raden i samma uttryck 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! Jag har sett ${row.seen} meddelande(n) från dig.`,
});
}
Distribuera:
npx tgcloud push # ladda upp modulerna
npx tgcloud migrate # skapa `counters`-tabellen
Det där är en live-bot med persistent tillstånd och ingen server. Allt i den — api, db, table() DSL:en — beskrivs i avsnitten nedan.
Serverless är en generell backend för Telegram-botar och Mini Apps, inte en mall för en specifik typ av app. Den är idealisk för:
- Konversationella AI-botar som behöver lagra tillstånd per användare i en databas.
- Mini App-backends som lagrar användardata och serverar dynamiskt innehåll.
- Spel och verktyg — inklusive topplistor, frågesporter och mer.
- Automatiseringar och integrationer som anropar tredjeparts HTTP-API:er och skickar resultat till chattar.
Komma igång
Den här genomgången tar dig från en tom mapp till en live-bot som svarar på meddelanden och lagrar data. Den förutsätter att du har
Node.js
18 eller nyare installerat och en bot registrerad hos
@BotFather
. I slutet kommer du att ha använt varje kommando du behöver dagligen: push, migrate, run och status.
Innan något annat, slå på Serverless. I @BotFather , öppna din bot → Serverless och slå på den. Det aktiverar funktionen för denna bot och låser upp dess CLI-åtkomsttoken, hanterare, bibliotek och databas.
1. Skapa ett projekt
Det snabbaste sättet att börja är projekt-skaparen, som sätter upp ett projekt och installerar CLI:et i det:
npm create @tgcloud/bot example_bot
cd example_bot
Argumentet är målmappen: använd . för att skapa i den aktuella mappen, eller valfri sökväg. Det fungerar även i en befintlig mapp och skriver aldrig över filer du redan har.
Detta ger dig ett projekt redo att redigeras:
example_bot/
├─ tgcloud/ # allt som plattformen kör
│ ├─ handlers/
│ │ └─ message.js # en start-meddelandehanterare (echo)
│ ├─ lib/ # dina delade moduler hamnar här (tom från början)
│ └─ schema.js # dina databastabeller
├─ docs/
│ └─ tgcloud-sdk.md # SDK-referens (för dig och dina AI-verktyg)
├─ AGENTS.md # vägledning för AI-kodassistenter
├─ package.json
└─ tgcloud.jsonc # projektkonfiguration
De skapade filerna är självförklarande — var och en innehåller kommenterade exempel på vad du kan göra härnäst.
CLI:et installeras i projektet som ett lokalt dev-beroende, så du kör det med npx tgcloud <kommando> (npx hittar kopian i ditt projekts node_modules), eller genom npm run-genvägarna som skapas i package.json (npm run deploy, npm run status). Som standard finns ingen global tgcloud på din PATH.
Du kan också installera det globalt — npm install -g @tgcloud/cli — om du hellre skriver ett rent tgcloud varifrån som helst. Det är smidigt för att köra tgcloud init i vilken tom mapp som helst, och det är vad shell-tab-komplettering kräver. Hur som helst får du samma projekt.
Bygger du en Mini App? Börja från dess frontend istället. Skapa den med ditt vanliga verktyg, kör sedan projekt-skaparen i den mappen. Se Mini App-frontend .
2. Koppla din bot
Varje projekt är kopplat till en bot. Anslut dem med login, som efterfrågar din CLI-åtkomsttoken (
@BotFather
→ din bot → Serverless → CLI Access → Access token — en separat token från din
bots API-token
) och lagrar den lokalt:
npx tgcloud login
Token har formen app<id>:<secret>. CLI:et sparar den i .tgcloud/, som är git-ignorerad, och skriver aldrig ut hemligheten. Login är enda gången du blir ombedd att ange den — se
Autentisering
för hur tokens hanteras i CI.
3. Se efter
Två kommandon talar om hur läget är när som helst, båda helt offline:
npx tgcloud status # vad har ändrats lokalt jämfört med den distribuerade kopian
npx tgcloud diff # ändringarna rad för rad
Direkt efter init är allt nytt och inget distribuerat ännu. status visar startfilerna som väntar på att laddas upp.
4. Distribuera
Skicka dina moduler till molnet:
npx tgcloud push
push laddar upp varje ändrad modul i en atomär batch och uppdaterar ditt lokala register över vad molnet innehåller. Din bot är live: öppna den i Telegram och skicka ett meddelande — start-hanteraren skickar tillbaka det.
Att distribuera rör aldrig din databas. Att skicka kod och ändra ditt databasschema är medvetet separata steg, så en kod-distribution kan aldrig överraska dig med en datamigrering. Det är vad nästa steg är till för.
5. Lägg till en databastabell
Låt oss få boten att komma ihåg något. Öppna tgcloud/schema.js och deklarera en tabell:
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())`) ,
});
Distribuera schemat, applicera det sedan på databasen:
npx tgcloud push # laddar upp det nya schemat
npx tgcloud migrate # skapar `messages`-tabellen
push rapporterar att schemat är osynkroniserat och visar ändringen, men applicerar inget. migrate guidar dig genom ändringen och skapar tabellen efter din bekräftelse. Denna tvåstegsmodell — och vad som händer med riskabla ändringar som raderingar — täcks i
Migreringar
.
6. Lagra och läs data
Använd nu tabellen från din hanterare. Redigera tgcloud/handlers/message.js:
import { api, db } from 'sdk';
import { eq } from 'sdk/db';
import { messages } from '../schema.js';
export default async function (message) {
// Spara detta meddelande.
await db.insert(messages)
.values({ chatId: message.chat.id, text: message.text })
.run();
// Räkna hur många vi lagrat för den här chatten.
const count = await db.$count(messages, eq(messages.chatId, message.chat.id));
await api.sendMessage({
chat_id: message.chat.id,
text: `Sparat. Det är ${count} meddelande(n) från denna chatt hittills.`,
});
}
Distribuera den uppdaterade hanteraren med npx tgcloud push, skicka sedan några meddelanden till din bot och se räknaren stiga. Databasen kvarstår mellan anrop — det är din bots minne.
7. Testa utan att distribuera
Du behöver inte distribuera för att prova en ändring. npx tgcloud run kör en hanterare på plattformen med dina lokala filer, utan att publicera dem:
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hello" }'
Argumentet är payloaden din hanterare tar emot — för handlers/message, ett
Message
— skrivet i
JSON5
(så att du kan hoppa över att citera nycklar). Kommandot skriver ut allt hanteraren loggade med console.*, returvärdet och hur lång tid det tog. Detta är den snabbaste loopen för att iterera logik — ingen distribution, ingen väntan på ett riktigt meddelande.
8. Håll dig synkroniserad
Medan du arbetar håller ett antal kommandon ditt lokala projekt och molnet synkroniserade: npx tgcloud status visar vad som ändrats, npx tgcloud push distribuerar, npx tgcloud pull synkar ditt lokala projekt med molnet, npx tgcloud fetch kollar vad som är distribuerat utan att röra dina filer, och npx tgcloud reset kastar bort lokala ändringar.
Om två personer (eller två maskiner) distribuerar till samma bot, upptäcker plattformen konflikten och
pushstoppar så att du kanpullförst — du kan aldrig tyst skriva över någon annans arbete. Se Hålla sig synkroniserad .
Bygga med AI
Föredrar du att bygga med en AI-assistent — eller är den enda kodaren i ditt team en AI? Du kan fortfarande skeppa en bot. Vi har tagit ett första steg för att göra att en AI-agent känner sig hemma i ett projekt: varje nytt projekt skapas med en AGENTS.md och en docs/tgcloud-sdk.md-referens som agentiska kodverktyg läser automatiskt.
Tillsammans med en liten, självförsörjande runtime — ett SDK, inga npm-paket att hantera — ger det assistenten en flygande start på konventionerna som generisk kodgenerering tenderar att missa: relativa importer med .js, inga foreign keys, varje db-anrop är asynkront, en hanterare per uppdateringstyp och tvåstegsflödet push/migrate.
Prova det:
npm create @tgcloud/bot my-bot
cd my-bot
opencode # eller Claude Code, Cursor, … — vilken agent som helst som läser AGENTS.md
Fråga sedan, på vanligt språk:
Skriv en bot som kommer ihåg varje persons att-göra-lista — lägg till ett objekt när de skickar text, och visa hela listan när de skickar /list.
Assistenten redigerar schema.js och dina hanterare åt dig; du granskar, testar en ändring direkt med npx tgcloud run, och går sedan live med npx tgcloud push och npx tgcloud migrate. AGENTS.md är en del av ditt projekt — redigera den i takt med att boten växer så att vägledningen förblir korrekt.
På språng med BotFather
Bara din telefon tillgänglig? Hela projektet lever i @BotFather också — öppna din bot → Serverless och du får allt som CLI:et hanterar, på en pekskärm:
- Hanterare — skapa, redigera och testkör uppdateringshanterare; BotFather håller webhooken synkroniserad med de hanterare du har (samma In sync / Out of sync som CLI:et rapporterar).
- Endpoints — funktionerna din Mini App anropar.
- Bibliotek — dina delade
lib/-moduler. - Databas — redigera
schema.jsi samma Drizzle-liknande syntax, granska väntande ändringar och applicera dem; Spara distribuerar. - Mini App-filer — frontenden dina användare öppnar i Telegram.
- CLI-åtkomst — hämta CLI-åtkomsttoken här när du är tillbaka vid ett tangentbord.
Det är ett och samma molnprojekt, så du kan starta en hanterare på din telefon och npx tgcloud pull till din laptop senare — inget är låst till en enskild klient. Att köra en hanterare visar till och med dess Konsol-utdata direkt i chatten, precis som
npx tgcloud run
.
Projekt och moduler
Ett serverless-projekt är en vanlig mapp under versionshantering. Allt plattformen kör sitter i en mapp, tgcloud/; resten av projektet är ditt — en mini-app-frontend, konfiguration, dokumentation. Det finns inget byggsteg för boten, inga node_modules vid körning och ingen server-ingångspunkt.
Projektets anatomi
example_bot/
├─ tgcloud/ # den distribuerade delen — inget annat är det
│ ├─ handlers/ # uppdateringshanterare — platt, endast en nivå
│ │ ├─ message.js
│ │ └─ callback_query.js
│ ├─ endpoints/ # funktioner din Mini App anropar — platt, endast en nivå
│ │ └─ getProfile.js
│ ├─ lib/ # delade moduler; underkataloger tillåtna
│ │ ├─ reply.js
│ │ └─ internal/util.js
│ └─ schema.js # databasschema — en fil
└─ .tgcloud/ # CLI-status, git-ignorerad
Endast .js-filer inuti tgcloud/ — schema.js, lib/, handlers/, endpoints/ — distribueras. Allt annat — Markdown, konfigurationsfiler, .tgcloud/-mappen, allt utanför tgcloud/ — stannar på din maskin. Modulnamn är relativa till tgcloud/. Filen tgcloud/handlers/message.js är modulen handlers/message, och det är namnet npx tgcloud run använder.
Uppgradera från ett äldre projekt? Projekt skapade före CLI 0.2.0 behöll
schema.js,lib/ochhandlers/i projektroten. Körnpx tgcloud upgradeen gång för att flytta dem tilltgcloud/.
schema.js — din databas. Den deklarerar tabeller som namngivna exporter med hjälp av schema-DSL:en och ligger i tgcloud/ som en enskild fil. Den distribueras som vilken annan modul som helst, men att distribuera den ändrar aldrig databasen — schemaändringar appliceras separat med npx tgcloud migrate. Se
Databasen
.
lib/ — delad kod, allt du vill återanvända över hanterare: rena hjälpare, databasåtkomstlager, formatering, integrationer med externa tjänster. lib/ är den enda katalogen som får innehålla underkataloger (lib/internal/util.js, lib/payments/stripe.js), så du kan organisera en större kodbas hur du vill. Moduler i lib/ anropas aldrig direkt av plattformen; de finns för att importeras av hanterare och av varandra.
handlers/ — din bots ingångspunkter. Varje fil motsvarar en Telegram-uppdateringstyp, och plattformen dirigerar varje inkommande uppdatering till matchande hanterare:
| Fil | Hanterar |
|---|---|
handlers/message.js |
Nya inkommande meddelanden |
handlers/inline_query.js |
Nya inkommande inline frågor |
handlers/callback_query.js |
Nya inkommande callback-frågor |
| … | varje annan Bot API-uppdateringstyp |
handlers/ är platt — inga underkataloger. En hanterares export default är funktionen plattformen anropar (se
Hanterare
).
En uppdateringstyp hanteras endast om dess hanterarfil finns och inte är tom. Om det inte finns någon handlers/<typ>.js — eller om filen är tom — ignoreras uppdateringar av den typen och plattformen kör inget för dem. Så behåll bara de hanterare du faktiskt behöver: varje du lägger till är en annan uppdateringstyp din bot vaknar upp för att bearbeta, och att utelämna resten innebär att Telegram inte startar din kod för uppdateringar du ändå bara skulle kassera.
För att skapa en ny hanterare, kör
npx tgcloud add handlers/<typ>.
endpoints/ — serversidan av din Mini App. Varje fil är en funktion som Mini Appen anropar vid namn, så endpoints/getProfile.js svarar på POST /api/getProfile. Plattformen kontrollerar att anropet kommer från en Telegram-användare inuti din Mini App innan din kod körs, och ger dig den användaren. endpoints/ är platt, precis som handlers/. Se
Endpoints
.
.tgcloud/ — maskinlokal status som hanteras helt av CLI:et, inklusive dina sparade autentiseringsuppgifter. Den är git-ignorerad, och du ska aldrig läsa från eller skriva till den för hand — använd CLI-kommandona istället.
Modulsystemet
Vid körning kan en modul se exakt två saker: plattformens SDK och de andra modulerna i ditt projekt. Det finns inga npm-paket, inget filsystem och inget nätverk förutom genom SDK:ns fetch.
Importera dina egna moduler som du skulle göra i vilket JavaScript-projekt som helst — genom relativ sökväg, med .js-tillägget — och SDK:n med dess namn:
// tgcloud/handlers/message.js
import { users } from '../schema.js'; // schema-modulen
import { addItem } from '../lib/cart.js'; // en lib-modul
import { format } from '../lib/internal/fmt.js'; // nästlad lib-modul
import { db, api, fetch } from 'sdk'; // plattformens SDK
// tgcloud/lib/cart.js
import { format } from './internal/fmt.js'; // en granne
import { users } from '../schema.js';
En relativ import måste sluta på .js och får inte leda utanför tgcloud/:
import { users } from '../schema'; // lägg till .js
import config from '../../config.js'; // utanför tgcloud/
import { z } from 'zod'; // inga npm-paket
Varje modul har också ett namn — dess sökväg inuti tgcloud/ utan tillägg, såsom schema, lib/cart eller handlers/message. Det namnet är vad npx tgcloud run och CLI-utdatan använder, och en modul kan importeras med det också (from 'lib/cart', inget tillägg). Föredra relativa importer i din kod. Det är vad din editor förstår — jump-to-definition, autokomplettering, byt namn.
Vad som är tillgängligt vid körning är exakt två saker: sdk och dess undermoduler (sdk/db, sdk/api, sdk/fetch) — hela plattformens yta, se
SDK:n
— och dina egna moduler i tgcloud/. Det är den kompletta listan. Om din kod importar något annat, kommer det inte att lösas. Denna begränsning är vad som håller moduler snabba att ladda och säkra att köra.
Hanterare
En hanterare är en modul i handlers/ vars standardexport plattformen anropar när en matchande uppdatering kommer.
// handlers/message.js
import { api } from 'sdk';
export default async function (message) {
await api.sendMessage({
chat_id: message.chat.id,
text: `Du sa: ${message.text ?? '(ingen text)'}`,
});
}
En hanterare tar emot uppdateringens payload som sitt argument — plattformen packar upp Telegram
Update
åt dig. handlers/message.js får
Message
(dvs update.message); handlers/callback_query.js får
CallbackQuery
; och så vidare. Hanterarens andra argument är ett kontextobjekt per anrop, ctx. Det bär på den råa Update som ctx.update — använd det när du behöver något utanför payloaden, som update_id.
En hanterare kan vara async (är det oftast) och kan returnera ett värde. Den når Bot API:et, databasen och utgående HTTP genom SDK:n.
Du behöver inte en riktig uppdatering för att testa en hanterare. npx tgcloud run kör den på plattformen med payloaden du anger och din nuvarande lokala kod:
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hi" }'
Argumentet är payloaden — samma objekt din hanterare tar emot — i JSON5. För att tillhandahålla hanterarens ctx (dess andra argument), lägg till --ctx, t.ex. --ctx '{ update: { update_id: 1 } }'. Detta körs mot dina lokala filer, så du kan prova ändringar innan du distribuerar dem. Se
run
.
Endpoints
En endpoint är en modul i endpoints/ som din Mini App anropar — platsen för allt frontenden inte kan göra på egen hand, som att läsa och skriva till databasen, anropa Bot API:et eller prata med externa tjänster med dina hemligheter.
// 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 hittades inte', { userId: user.id });
return profile;
}
Det första argumentet, input, är JSON-objektet som Mini Appen skickade. Det andra, ctx, bär på ctx.initData — Mini Appens
init data
(user, chat, start_param, auth_date, …), redan verifierad av plattformen. Den har samma form som Telegram.WebApp.initDataUnsafe på klienten, men här kan du lita på den. Ett anrop utan giltig init data avvisas med 401 innan din kod körs, så ctx.initData.user är verkligen personen som öppnade din Mini App. Endpoints kan bara anropas på detta sätt — från din bots Mini App, av en Telegram-användare — så det finns inget offentligt API att skydda och ingen inloggning att bygga.
Vad endpointen returnerar skickas tillbaka som JSON. För att neka ett anrop, throw new EndpointError(beskrivning, parametrar?). Mini Appen får beskrivningen och parametrar-objektet som de är. Om du behöver förgrena logiken baserat på typen av fel i frontenden, lägg in en kod i parametrar — { code: 'OUT_OF_STOCK' }. Varje annat undantag rapporteras som ett generiskt internt fel, och det riktiga meddelandet går bara till dina loggar, så interna detaljer läcker aldrig till klienten.
Anropa en endpoint från Mini Appen
Mini Appen anropar en endpoint vid namn med Telegram.WebApp.Serverless.call från det officiella
Mini Apps SDK
, telegram-web-app.js — skriptet din Mini App redan inkluderar. Den skickar init data åt dig och anropar tillbaka med ett fel eller endpointens resultat:
Telegram.WebApp.Serverless.call('getProfile', { lang: 'en' }, function (err, profile) {
if (err) {
// err.message — beskrivningen; err.parameters — vad din EndpointError bar på
if (err.type === 'ENDPOINT_ERROR') showMessage(err.message);
return;
}
render(profile);
});
Input är valfritt och måste vara ett vanligt objekt. Utan det är anropet Serverless.call('getProfile', callback).
err.type är 'ENDPOINT_ERROR' när din endpoint kastade ett EndpointError. Varje annat värde betyder att plattformen avvisade anropet eller att endpointen misslyckades oväntat, till exempel 'UNAUTHORIZED' när init data saknas eller är ogiltig.
Endpoint-namn är som JavaScript-identifierare — bokstäver, siffror och _, som inte börjar med en siffra (getProfile, place_order). Skapa en med
npx tgcloud add endpoints/<namn>
, och prova den utan Mini Appen med
run
, där du skickar init data själv:
npx tgcloud run endpoints/getProfile '{}' --ctx '{ initData: { user: { id: 1, first_name: "Ann" } } }'
Vad distribueras
När du npx tgcloud push, samlar CLI:et varje .js-fil under schema.js, lib/, handlers/ och endpoints/, och skickar exakt den uppsättningen som ditt projekts modulrymd — tillsammans med Mini Appens byggmapp, om tgcloud.jsonc namnger en (se nedan). Allt som finns i molnet men saknas i ditt projekt tas bort, så det distribuerade tillståndet speglar alltid din mapp — inklusive raderingar. Inget utanför tgcloud/ distribueras som kod — resten av projektroten är din. Inuti tgcloud/ flaggas en .js-fil som inte finns på någon av dessa platser (tgcloud/util.js, tgcloud/helpers/x.js), så att den inte tyst förblir odistribuerad. Markdown, dotfiles och .tgcloud/ distribueras aldrig.
Mini App-frontend
Projektroten är din, och det är den naturliga platsen för din Mini Apps frontend — byggd med React, Vue, Svelte eller vanlig HTML, av Vite eller någon annan bundler. Serverless hostar dess byggfiler bredvid din bot. En push distribuerar koden och de statiska filerna tillsammans, och appen serveras på https://app<app_id>.tgcloud.ai/ (push skriver ut den exakta adressen — använd den som din Mini Apps URL i
@BotFather
).
Rapportera missbruk. Mini Apps på tgcloud.ai publiceras av oberoende utvecklare. För att rapportera olagligt innehåll eller missbruk på en
app<app_id>.tgcloud.ai-subdomän, mejla [email protected] .
För att börja, skapa frontenden först, med vilket verktyg du vill, lägg sedan till Serverless ovanpå:
npm create vite@latest example_app # eller create-vue, …
cd example_app
npm create @tgcloud/bot .
Projekt-skaparen skriver aldrig över dina filer. Den lägger till tgcloud/-mappen bredvid frontenden, känner igen byggmappen och lägger till ett npm run deploy-skript som bygger appen och distribuerar den tillsammans med boten.
Vad som hostas ställs in i tgcloud.jsonc, projektets konfigurationsfil i projektroten:
{
"$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 }]
}
}
Endast "source" krävs. Det är byggmappen, relativ till projektroten. De andra nycklarna i "static" konfigurerar hur den serveras (nedan). init skapar tgcloud.jsonc åt dig. När den känner igen frontenden fyller den också i "source":
| Frontend | init sätter "source" till |
|---|---|
| Vite — React, Vue, Svelte, Solid, … | dist |
| Next.js, Nuxt, SvelteKit, React Router, TanStack Start, Angular | inget — konfigurera en statisk export först, sätt sedan "source" till dess mapp själv |
Med någon annan konfiguration, sätt "source" till din byggmapp själv.
$schema-raden ger din editor autokomplettering och validering för varje nyckel. Filen är JSON med kommentarer och avslutande kommatecken tillåtna. Om ett annat verktyg du har behöver läsa den som vanlig JSON, döp den till tgcloud.json istället — då måste det vara strikt JSON.
Bygg innan du pushar. push laddar upp mappen exakt som den är på disken — förutom filer och mappar vars namn börjar med en punkt, vilka hoppas över — och kör aldrig ditt bygge, så en ren npx tgcloud push efter att ha redigerat frontenden skulle distribuera den tidigare versionen. Använd npm run deploy (den kör ditt build-skript, sedan tgcloud push), eller bygg själv först.
Alternativen för servering:
spa— serveraindex.htmlför sökvägar som inte finns och saknar filändelse, så att en klient-side router fungerar vid omladdning. Sökvägar med ändelse får fortfarande404.immutable— mönster för innehållshashade filer (Vites/assets/*) som webbläsare kan cacha i ett år. Använd det bara för innehållshashade filer. Alla andra filer som matchas här skulle förbli inaktuella i webbläsare i ett år, och det finns inget sätt att återkalla dem.headers— svarshuvuden för sökvägarna en regel matchar (en exakt sökväg, eller ett prefix som slutar på/*). Ettnull-värde tar bort ett huvud.redirects— omdirigeringsregler, där den första matchningen vinner. En befintlig fil slår alltid en regel.statusär301om du inte ställer in den."static": "dist"— en genväg för{ "source": "dist" }när du inte behöver något annat."static": false— ta bort den distribuerade sajten vid nästa push. Att ta bort nyckeln istället slutar bara med att detta projekt hanterar statiska filer, och det som är distribuerat stannar kvar.
Sajten serveras från roten av sin egen host, så din bundlers standard base (/) är korrekt. Telegram Web öppnar Mini Apps i en iframe, så lägg inte till X-Frame-Options eller en restriktiv frame-ancestors-policy.
Samma host svarar på dina
endpoints
under /api/ — det är dit Telegram.WebApp.Serverless.call('getProfile', …) går. Lägg inte egna filer under /api/ i byggmappen.
Databasen
Varje bot får sin egen databas — ett SQLite-baserat lager som kvarstår mellan anrop och är tillgängligt för varje modul genom db. Du beskriver dina tabeller i schema.js med en liten, typad DSL; du läser och skriver dem med en flytande frågebyggare; och du utvecklar dem med granskade migreringar.
Kan du Drizzle ORM ? Då vet du redan hur du pratar med databasen här. Schema-DSL:en och frågebyggaren följer Drizzle — kolumnbyggarna,
select().from().where(), operatorerna,onConflictDoUpdate,.returning(), ochsql-taggen beter sig alla som du förväntar dig, så att läsa och skriva data är det bekanta API du redan använder. Du importerar det bara frånsdk/db, och ett par plattformsspecifika saker (främst inga foreign keys ) pekas ut där de dyker upp.
Deklarera tabeller
Tabeller är namngivna exporter i schema.js. Att anropa table() bygger en beskrivning vid inläsning (den rör inte databasen); plattformen upptäcker de exporterade tabellerna när du distribuerar schema.js och migrerar databasen för att matcha.
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(namn, kolumner, extras?):
namnär SQL-tabellnamnet;kolumnerär en karta över JS-egenskap → kolumndefinition;extrasär ett valfritt callback(t) => ({ … })därtexponerar kolumnerna (t.createdär en referens till den kolumnen) — deklarera index och tabellnivåbegränsningar här.
Kolumntyper
| Fabrik | SQLite-typ | Anteckningar |
|---|---|---|
text() |
TEXT | |
integer() |
INTEGER | |
real() |
REAL | alias float() |
numeric() |
NUMERIC | |
blob() |
BLOB | läser/skriver Uint8Array |
boolean() |
INTEGER | lagras som 0/1, läses som true/false |
json() |
TEXT | auto JSON.stringify / JSON.parse |
Kolumnnamnsargumentet är valfritt — utelämna det så används JS-nyckeln.
mode-alternativet styr hur värden konverteras mellan SQLite och JavaScript.
mode |
Lagras som | JS-värde |
|---|---|---|
boolean |
INTEGER 0/1 | boolean |
json |
TEXT (JSON) | vilket objekt/array som helst |
timestamp |
INTEGER (unix-sekunder) | Date |
timestamp_ms |
INTEGER (unix-ms) | Date |
bytes |
BLOB | Uint8Array |
boolean() och json() är genvägar för integer(name, { mode: 'boolean' }) och text(name, { mode: 'json' }).
En blob() läser och skriver en Uint8Array — runtime har ingen Node Buffer (och en Buffer är en Uint8Array-subklass, så detta är den portabla bastypen). mode ovan styr endast hur ett värde kodas, oberoende av kolumnens lagringstyp: så blob('col', { mode: 'json' }) är BLOB-motsvarigheten till json() — samma JSON-kodning, lagrad i en BLOB-kolumn istället för TEXT.
TLDR:
blob()använderUint8Array.modestyr kodning, inte lagring, såblob(..., { mode: 'json' })lagrar JSON som en BLOB, medanjson()lagrar det som TEXT.
Kolumnmodifierare
Kolumnmodifierare kedjas på en kolumn.
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('ersatt av login') // markerar kolumnen för borttagning
.default()på enjson()-kolumn kodar värdet åt dig; ensql`…`-default skickas igenom ord för ord..deprecated()är terminal — se Migreringar .
Index och begränsningar
Index och begränsningar deklareras i extras-callbacken, där kolumnerna finns 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})`) , // uttrycksindex
active: index('idx_active').on(t.userId).where(sql`done = 0`) , // partiellt index
}));
Tabellnivåmodifierare kedjas efter table(...): .strict(), .withoutRowid(), .deprecated('anledning').
Inga foreign keys
Runtime körs med PRAGMA foreign_keys av. En deklarerad foreign key skulle tyst vara inert — inga kaskader, inget skydd mot föräldralösa rader — vilket är sämre än att inte ha någon alls, så DSL:en gör det omöjligt. Närmare bestämt kastar .references() och foreignKey() på tabellnivå fel när de deklareras, och ett schema som använder dem distribueras inte.
Du bör modellera relationer med vanliga kolumner (userId: integer('user_id')) och upprätthålla integritet i din applikationskod: infoga föräldrar före barn, ta bort barn före föräldrar, hantera fel korrekt och städa upp föräldralösa rader med en LEFT JOIN … WHERE parent.id IS NULL när du behöver.
Frånvaron av foreign keys är en medveten begränsning, inte ett förbiseende. Ta hänsyn till det tidigt när du planerar din bot.
Frågor
db är en flytande frågebyggare. Varje fråga är asynkron — terminalmetoderna (.all(), .get(), .values(), .run()) returnerar Promises, så alltid 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(); // alla rader
await db.select().from(todos).where(eq(todos.id, 1)).get(); // första raden eller null
await db.select().from(todos).values(); // rader som värdearrayer
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();
// anpassad 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();
// radräkning — en hjälpare, inte en byggarterminal:
await db.$count(todos); // alla rader
await db.$count(todos, eq(todos.done, false)); // med ett filter
- Kedjbar:
.where(),.orderBy(),.limit(),.offset(),.groupBy(),.having(),.distinct(). - Terminaler:
.all(),.get(),.values(). - Räkna rader med
db.$count(tabell, where?)(ellercount()i en projektion).
Infoga, uppdatera, radera:
await db.insert(todos).values({ userId: 1, text: 'Köp mjö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();
Notera att en batch-insert är ett uttryck, så den begränsas av SQLite:s variabelgräns (
rader × kolumner); om du går över den, blir det fel på inserten — dela upp den själv.
Operatorer importeras från 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 flera argument är samma som and(a, b). Ett jämförelsearguments andra argument är ett värde som standard, men kan vara en annan kolumn eller sql`…` — t.ex. eq(a.x, b.y). Aggregat (count/sum/avg/min/max) är SQL-fragment för en .select({ … })-projektion.
Rå SQL — när byggaren inte räcker till, använd rå SQL. Läget väljs efter metod: db.run för skrivningar, db.all för många rader, db.get för en rad, db.values för rader som positionella arrayer.
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öp mjölk']]
En skrivning resulterar i ett run result — rowsAffected, lastInsertRowid och rows, som bär på en RETURNING-klausul om SQL:en hade en. Använd db.values när en fråga projicerar två kolumner med samma namn (en join som SELECT u.id, o.id): db.all nycklar rader efter kolumnnamn, så det andra idt skulle skriva över det första.
sql`…` -taggen gör ${value} till en bunden parameter och ${table.column} till en identifierare, och skarvar in nästlade sql-fragment. Använd sql.raw('…') för en literal utan parametrar.
Råa frågor är inte bundna till en tabell, så deras rader kommer tillbaka utan lägeskonvertering — en boolean är
0/1, en JSON-kolumn är en sträng, en timestamp är ett nummer. Endast den tabellbundna byggaren konverterar värden.
Migreringar
Din databas ändras i takt med att din bot växer. Plattformen håller det säkert genom att separera distribution av kod från ändring av data, och genom att klassificera varje schemaändring efter hur riskabel den är.
Distribution rör aldrig databasen. När du npx tgcloud push en ändrad schema.js, registrerar plattformen det nya schemat och berättar vad databasen skulle ändra — men applicerar inget:
npx tgcloud push # distribuera schema.js; rapporterar väntande DB-ändringar
npx tgcloud migrate # granska och applicera dem
npx tgcloud migrate beräknar skillnaden mellan ditt schema och den live-databasen och guidar dig genom den. Du blir alltid tillfrågad innan något appliceras. Det betyder att en rutinmässig kod-distribution aldrig kan utlösa en datamigrering av misstag.
Varje väntande ändring har en status som avgör hur migrate behandlar den:
| Status | Betydelse | I migrate |
|---|---|---|
| safe | Additiv och icke-blockerande — en ny tabell, kolumn eller index | Appliceras tillsammans i ett steg, vid bekräftelse |
| warning | Potentiellt destruktiv eller långsam — ta bort något, eller ett index på en stor tabell | Presenteras en i taget, var och en bekräftas separat |
| manual | Kan inte göras automatiskt — t.ex. ändra typ på en kolumn | Visas med vägledning; du utför det för hand |
| undocumented | Finns i databasen men inte i ditt schema | Visas för medvetenhet; appliceras ej |
Säkra ändringar är snabba och reversibla till sin natur, så de körs igenom tillsammans. Varje varning är en medveten, individuell bekräftelse — det finns inget “applicera alla” för destruktiva ändringar.
Manuella ändringar kommer med en anledning och en föreslagen åtgärd. migrate avslutas med en sammanfattning: hur många ändringar som applicerades, hoppades över, väntar på manuell fix eller inte finns i ditt schema.
Se
migrateför flaggorna (--dry-run,--safe,--yes,--local).
Ta bort saker
Att ta bort en tabell eller kolumn från schema.js raderar den inte — det skulle göra en oavsiktlig radering katastrofal. För att ta bort något, markera det som deprecated:
// ta bort en kolumn
text('email').deprecated('ersatt av login')
// ta bort en hel tabell
export const oldSessions = table('old_sessions', { /* … */ }).deprecated('oanvänd');
Vid nästa migrate dyker det deprecated objektet upp som en varning-status radering som du bekräftar individuellt. När det är borta, ta bort deklarationen.
Ändra en kolumns typ
Typändringar är manuella — SQLite kan inte alltid göra dem på plats, och att tvinga befintliga värden är en bedömningsfråga. migrate visar ändringen och dess resonemang; utför det själv med rå SQL (db.run(...)), vanligtvis genom att skapa en ny kolumn eller tabell, kopiera data och byta ut.
SDK:n
Vid körning har en modul ett bibliotek: sdk. Det buntar ihop de tre saker en bot-backend behöver — en databas, Telegram Bot API och utgående HTTP — med inget att installera och inga autentiseringsuppgifter att konfigurera. Databasen (db) täcks i
Databasen
; detta avsnitt täcker api, fetch och den globala console.
import { db, api, fetch, InputFile, BotApiError, EndpointError } from 'sdk'; // hela ytan
// eller från undermoduler:
import { table, integer, text, eq, sql } from 'sdk/db';
import { api } from 'sdk/api';
import { fetch } from 'sdk/fetch';
| Import | Vad det är |
|---|---|
db |
Databas — frågebyggare och schema-DSL → Databasen |
api |
Telegram Bot API — api.sendMessage(...) →
nedan
|
fetch |
Utgående HTTP → nedan |
InputFile |
En fil (bytes + namn) att ladda upp — fungerar med både api och fetch →
Filer
|
EndpointError |
Neka ett Mini App-anrop med ett meddelande det kan visa → Endpoints |
Importera dina egna projektmoduler med relativ sökväg och .js-tillägget (from '../schema.js', from '../lib/cart.js'). Se
Modulsystemet
.
Bot API:et
api ger dig hela
Telegram Bot API:et
. Anropa vilken metod som helst som api.<metod>(params). Varje nuvarande — och framtida — Bot API-metod fungerar utan att någon SDK-uppdatering krävs.
import { api } from 'sdk';
const me = await api.getMe(); // → det uppackade resultatet
await api.sendMessage({ chat_id: id, text: 'Hej!' });
await api.editMessageText({ chat_id, message_id, text: 'Uppdaterat' });
await api.answerCallbackQuery({ callback_query_id, text: 'Klar' });
Svarskuvertet är uppackat. Bot API:et packar normalt in resultat i { ok: true, result: … }. api returnerar result direkt — getMe() löses till användarobjektet, inte till en wrapper. Parametrar använder Bot API:ets egna snake_case-namn (chat_id, message_id, reply_markup, …).
Fel kastar BotApiError. När Bot API:et returnerar { ok: false }, kastar anropet en BotApiError istället för att returnera ett falskt värde, så du kan inte oavsiktligt ignorera det. Felet bär på .code (Bot API:ets error_code), .description (det mänskligt läsbara meddelandet), .method (vilken metod som misslyckades) och .parameters (extra data såsom retry_after vid en 429, eller migrate_to_chat_id). Fånga det för att hantera ett förväntat fel och kasta om resten:
import { api, BotApiError } from 'sdk';
try {
await api.deleteMessage({ chat_id, message_id });
} catch (e) {
if (e instanceof BotApiError && e.code === 400) {
// 400 = meddelandet är redan borta; det är okej här.
} else {
throw e;
}
}
Filer
Arbeta med fil-bytes, inte bara file_id:er — ladda upp nya filer och ladda ner befintliga.
Uppladdning — InputFile. Packa in råa bytes i en InputFile (en enkel hållare: bytes, ett filnamn och en valfri MIME-type) och skicka den varhelst en Bot API-metod förväntar sig 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 nästlade i parametrar fungerar också — t.ex. ett 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' }) },
]});
Bytesen streamas till Telegram som en riktig multipart-uppladdning — filen buffras inte helt i ett enda anrop till plattformen — men Telegrams Bot API-gränser gäller fortfarande: en bot kan för närvarande ladda upp upp till 50 MB per fil (och 10 MB för ett foto).
Nedladdning — med file_id. Två hjälpare drar tillbaka en fils bytes. Plattformen löser file_id åt dig (den kör getFile och hämtar innehållet bakom kulisserna), så du rör aldrig bot-token eller en nedladdnings-URL:
const bytes = await api.getFileContent(file_id); // → Uint8Array (hela filen)
const { file, body } = await api.getFileStream(file_id); // streama den istället
file.file_unique_id; // getFile-informationen
for await (const chunk of body) { /* Uint8Array */ } // eller buffra den: await stream.bytes()
Telegrams Bot API begränsar
getFile-nedladdningar till 20 MB, så det är taket för båda hjälparna.
HTTP
fetch är en fetch-liknande klient för att anropa omvärlden — tredjeparts API:er, webhooks, allt över 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 speglar webbplattformen: res.status, res.statusText, res.ok (sant för 200–299), res.url, res.headers (.get(), .has(), .keys(), .entries()), och kroppsläsare await res.json() / await res.text().
Du kan också läsa kroppen inkrementellt som en ström —
for await (const chunk of res.body) { … }— vilket är hur du konsumerar server-sent events eller token-för-token utdata från AI API:er.
Kroppshjälpare sätter matchande Content-Type åt 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
Anropskroppar kan också bära filer och råa bytes: skicka en Uint8Array, en InputFile eller en FormData (för multipart/form-data) som body, så streamas bytesen ut — så att du kan ladda upp till ett tredjeparts API på samma sätt som du skickar filer till Telegram:
import { fetch, FormData, InputFile } from 'sdk';
const form = new FormData();
form.append('file', new InputFile(bytes, 'foto.jpg', { type: 'image/jpeg' }));
form.append('caption', 'från min bot');
const res = await fetch('https://api.example.com/upload', { method: 'POST', body: form });
Det beter sig annars som standard fetch du redan känner till, med två begränsningar:
- Svaret läses som text —
await res.json()/await res.text(), eller streamas chunk-för-chunk medres.body. - Det totala svaret är begränsat till 30 MB. Det taket täcker hela svaret — streaming med
res.bodylåter dig bearbeta en stor kropp inkrementellt, men det höjer inte gränsen.
Loggning
Den globala standarden console är tillgänglig — inget att importera, den finns bara där som i vilken JavaScript som helst. Dess utdata fångas upp och visas av npx tgcloud run, vilket gör den till ditt främsta felsökningsverktyg under utveckling.
console.log('bearbetar', { chatId: id }); // log / debug — vanlig
console.info('startad'); // info — blå
console.warn('rate limited'); // warn — gul
console.error(err); // error — röd, med stack trace
Varje rad är taggad med [fil:rad] den kom ifrån. console.error och console.trace lägger till en full stack, medan console.warn inte gör det. När du npx tgcloud run en modul skrivs dessa rader ut med ett färgat prefix per nivå, tiden sedan körningen startade och ursprunget — se
run
.
Kommandoradsgränssnitt (CLI)
tgcloud är bryggan mellan din projektmapp och molnet. Det skapar projekt, visar vad som ändrats, distribuerar, kör moduler och applicerar databasmigreringar. Det kräver
Node.js
18 eller nyare. Två sätt att få det:
# Rekommenderas — skapa ett projekt med CLI:et installerat i det:
npm create @tgcloud/bot example_bot
# Eller installera CLI:et globalt och initiera en tom mapp:
npm install -g @tgcloud/cli
tgcloud init
NPM-paketet är @tgcloud/cli, och kommandot det installerar är tgcloud. Varje kommando fungerar från vilken underkatalog som helst i ditt projekt.
| Kommando | Syfte |
|---|---|
init
|
Skapa ett nytt projekt i den aktuella mappen |
add
|
Skapa en ny modul (en hanterare, en endpoint eller en lib-modul) |
login
|
Koppla projektet till en bot (sparar token) |
status
|
Visa vad som ändrats lokalt vs. molnet |
diff
|
Visa ändringarna rad för rad |
push
|
Distribuera ändrade moduler och Mini App-bygget till molnet |
migrate
|
Applicera schemaändringar på databasen |
run
|
Kör en modul på plattformen utan att distribuera |
fetch
|
Kontrollera vad som är distribuerat, utan att ändra lokala filer |
pull
|
Synka lokala filer med molnet |
reset
|
Kasta bort lokala ändringar; återställ från moln-status |
webhook
|
Inspektera och omsynka den plattformshanterade webhooken |
completion
|
Skriv ut ett shell-kompletteringsskript (bash/zsh/fish) |
upgrade
|
Flytta en layout från före 0.2.0 till tgcloud/ |
Autentisering
Ett projekt är låst till en bot genom sin token, som har formen app<id>:<secret>. app<id>-delen är publik och kan skrivas ut; hemligheten visas aldrig i loggar eller fel.
Token löses i denna ordning:
TGCLOUD_TOKENmiljövariabel — för CI; skrivs aldrig till disk..tgcloud/credentials— skrivs avnpx tgcloud login.- Ingetdera → ett fel som pekar dig mot
npx tgcloud login.
CLI:et efterfrågar aldrig en token mitt i ett kommando. Att logga in är alltid det explicita login-steget. Om en sparad token blir ogiltig, tar CLI:et bort den och ber dig login igen.
init
npx tgcloud init
Skapar ett nytt projekt i den aktuella katalogen: tgcloud/-mappen med schema.js, lib/, handlers/ och en starthanterare, plus tgcloud.jsonc, AGENTS.md, docs/, och .tgcloud/-statusmappen. Kör den i roten av ett befintligt frontend-projekt också — den lägger till tgcloud/-mappen och dokumentationen, och skriver aldrig över dina filer. Om den känner igen frontenden, pekar den även tgcloud.jsonc mot byggmappen och lägger till ett deploy-skript (bygg, sedan push) till din package.json — endast om du inte har ett deploy-skript redan. Se
Mini App-frontend
.
init vägrar skapa ett projekt inuti ett annat projekt. Att köra den igen i ett projekts egen rot går bra och lägger bara till det som saknas.
add
npx tgcloud add <target>
Skapar en enskild ny modul, konfigurerad och redo att redigeras — en hanterare, en endpoint eller en lib/-modul.
npx tgcloud add handlers/callback_query # en ny uppdateringshanterare
npx tgcloud add endpoints/getProfile # en ny endpoint för Mini Appen
npx tgcloud add lib/cart # en ny delad modul
Notera att
addaldrig skriver över en befintlig fil.
<target> är modulens sökväg (ett efterföljande .js är valfritt). För handlers/ måste namnet vara en Telegram-uppdateringstyp. Plattformen annonserar den giltiga uppsättningen, så ett ogiltigt namn avvisas direkt. handlers/ och endpoints/ är platta, medan lib/ kan vara nästlad (lib/payments/stripe).
Modulnamnet krävs. Att bara ge katalogen är ett fel — men ett hjälpsamt sådant: för handlers/ listar den de uppdateringstyper du inte redan har, så att du kan kopiera en.
$ npx tgcloud add handlers
Fel: Ange ett namn, t.ex. "handlers/callback_query". Tillgängliga handlers/-typer: callback_query, inline_query, chat_member, …
<Tab>-komplettering erbjuder samma uppsättning — secompletion.
Den genererade filen har en live-export default, så hanteraren är aktiv så fort du distribuerar — det finns inget att avkommentera. Distribuera den nya modulen med
push
.
login
npx tgcloud login
Frågar efter din CLI-åtkomsttoken — från
@BotFather
→ din bot → Serverless → CLI Access → Access token, en separat token från din
bots API-token
— validerar den mot plattformen och sparar den i .tgcloud/credentials.
loginär det enda kommandot som frågar efter en token, och det kräver en interaktiv terminal. I CI, sättTGCLOUD_TOKENistället.
status
npx tgcloud status
Visar, per fil, vad som har ändrats mellan din arbetskatalog och den distribuerade kopian: modifierad, ny, raderad, oförändrad. Den fungerar offline, genom att jämföra med status för molnet vid din senaste push, pull eller fetch. Den varnar också för lösa .js-filer inuti tgcloud/ som inte kommer att distribueras.
För ett projekt med en Mini App visar status även vad som ändrats i dess byggmapp sedan den senaste distributionen.
diff
npx tgcloud diff
Som status, men visar de faktiska ändringarna rad för rad för ändrade moduler. Också offline.
push
npx tgcloud push [files...]
Distribuerar ditt projekt till molnet i en atomär batch.
Med inga argument distribueras hela projektet, och det distribuerade tillståndet görs för att spegla din mapp exakt — moduler du raderat lokalt tas bort i molnet.
Med fil- eller katalogargument (npx tgcloud push tgcloud/handlers/message.js, npx tgcloud push handlers/), distribueras endast ändringarna i de filerna. En målinriktad push tar aldrig bort andra moduler. Ett mål kan skrivas som filsökvägen (tgcloud/handlers/message.js), sökvägen inuti tgcloud/ (handlers/message.js) eller modulnamnet (handlers/message) — de betyder alla samma modul.
Dess enda alternativ är --force — för att hoppa över concurrency-kontrollen och skriva över vad som finns i molnet. Använd det bara när du är säker (se
Hålla sig synkroniserad
).
Om tgcloud.jsonc namnger en statisk mapp, distribuerar en fullständig push den i samma batch — endast filerna som ändrades laddas upp — och skriver ut sajtens adress. Bygg först, eftersom push aldrig kör ditt bygge (npm run deploy gör båda). npx tgcloud push dist/ distribuerar bara de statiska filerna, och en push som bara namnger moduler lämnar dem orörda.
Efter en distribution, om schema.js ändrades och databasen är osynkroniserad, skriver push ut en sammanfattning av väntande ändringar och föreslår npx tgcloud migrate. Den applicerar dem aldrig själv.
migrate
npx tgcloud migrate
Applicerar dina schemaändringar på databasen. Den beräknar skillnaden mellan schema.js och live-databasen, och guidar dig sedan genom den ett steg i taget med en löpande [N/M]-räknare:
- En kort sammanfattning av alla väntande ändringar.
- Säkra ändringar, applicerade tillsammans i ett enda steg vid din bekräftelse.
- Varningar (raderingar, långsamma operationer), en i taget, var och en bekräftas separat.
- Manuella ändringar, visas med en anledning och föreslagen åtgärd, appliceras inte automatiskt.
- Odokumenterade objekt (i databasen men inte i ditt schema), visas för medvetenhet.
Den avslutas med en sammanfattning: applicerade, hoppades över, väntar på manuell fix, inte i schema.
Se Migreringar för modellen.
Alternativ: --dry-run (skriv ut allt, applicera inget), --safe (auto-applicera säkra ändringar, hoppa över varningar), --yes (auto-applicera säkra ändringar och varje varning, hoppa över manuella — använd med försiktighet), --local (jämför mot din lokala schema.js istället för den distribuerade). Utan flagga kräver migrate en terminal och ger fel i en icke-interaktiv miljö istället för att gissa.
run
npx tgcloud run <module> [args] [--ctx <json5>]
Kör en hanterare eller en endpoint på plattformen utan att distribuera, med dina nuvarande lokala filer. Detta är den snabba loopen för att testa logik.
<module>— ett namn (söks underhandlers/ochendpoints/) eller en sökväg somhandlers/message. Ett namn som finns i båda katalogerna måste skrivas med sin katalog.[args]— payloaden som skickas till hanteraren, skriven i JSON5 så att du kan hoppa över att citera nycklar. Det är uppdateringstyp-objektet din hanterare tar emot (t.ex. ett meddelande förhandlers/message), ellerinputför en endpoint, och det måste vara ett objekt — inte en array eller en rå sträng.--ctx <json5>— modulens kontextobjekt (dess andra argument), också JSON5. Använd det för att ange vad din kod läser frånctx— den råa uppdateringen för en hanterare,--ctx '{ update: { update_id: 1 } }', eller init data för en endpoint,--ctx '{ initData: { user: { id: 1 } } }'.runverifierar den inte, eftersom du testar din logik, inte plattformens kontroller.
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hi" }'
Plattformen kör modulen mot modulrymden som är sammansatt av ditt lokala projekt (så lokalt ändrad lib/-kod används också) och returnerar returvärdet, allt loggat med console.* och den förflutna tiden. Läs stora argument från en fil med npx tgcloud run handlers/message "$(cat message.json5)".
fetch
npx tgcloud fetch
Kontrollerar vad som för närvarande är distribuerat, utan att röra dina arbetsfiler. Efter den jämför status och diff mot den senaste moln-statusen. Användbart för att titta på en konflikt innan du bestämmer hur den ska lösas.
pull
npx tgcloud pull
Synkar ditt lokala projekt med molnet genom att uppdatera dina arbetsfiler till det distribuerade tillståndet.
pull skriver aldrig din Mini Apps byggmapp.
reset
npx tgcloud reset
Kastar bort dina lokala ändringar och återställer modulerna från det senast kända moln-tillståndet. Använd den för att kasta bort ett experiment. Mini Appens byggmapp rörs inte.
webhook
npx tgcloud webhook
npx tgcloud webhook sync [--drop-pending]
Telegram levererar uppdateringar till din bot genom en webhook, som plattformen hanterar åt dig — du pekar aldrig någonstans för hand. npx tgcloud webhook visar dess nuvarande tillstånd: URL:en, allowed_updates-listan, hur många uppdateringar som väntar, det senaste leveransfelet (om något) och om den är synkroniserad med dina distribuerade hanterare.
“Synkroniserad” betyder att webhooken pekar på plattformen och dess allowed_updates matchar hanterarna du har distribuerat — så Telegram levererar exakt de uppdateringstyper du hanterar, och inget annat. Att distribuera en ny hanterare (eller ta bort en) kan lämna webhooken osynkroniserad tills den uppdateras; npx tgcloud status flaggar även detta.
npx tgcloud webhook sync fixar det — den pekar om webhooken till plattformen och bygger om allowed_updates från dina distribuerade hanterare. Lägg till --drop-pending för att kasta bort uppdateringar Telegram redan köat före synkningen (annars levereras de så fort webhooken är hälsosam igen).
completion
Notera: tab-komplettering fungerar bara när ett rent
tgcloudfinns på dinPATH— så installera det globalt (npm install -g @tgcloud/cli) eller lägg binären på dinPATH. Den kan inte hooka in inpx.
tgcloud completion <bash|zsh|fish>
Skriver ut ett shell-kompletteringsskript till stdout. Aktivera det en gång, sedan <Tab>-kompletterar kommandon, flaggor, modulkataloger, hanteraruppdateringstyperna du inte har ännu och dina lokala körbara moduler.
# bash — behöver bash-completion-paketet:
echo 'eval "$(tgcloud completion bash)"' >> ~/.bashrc
# zsh — se till att `autoload -U compinit && compinit` körs i din ~/.zshrc:
echo 'eval "$(tgcloud completion zsh)"' >> ~/.zshrc
# fish:
tgcloud completion fish > ~/.config/fish/completions/tgcloud.fish
Starta om ditt shell (eller re-source filen) efteråt. Att köra tgcloud completion utan shell skriver ut dessa instruktioner igen.
upgrade
npx tgcloud upgrade [--dry-run]
Engångsmigrering för projekt skapade före CLI 0.2.0, som behöll schema.js, lib/ och handlers/ i projektroten. Den flyttar dem till tgcloud/. Din koppling till boten behålls, och inget behöver distribueras om. --dry-run skriver ut de planerade flyttarna utan att röra något. Andra kommandon körs inte på den gamla layouten förrän du uppgraderar.
Hålla sig synkroniserad
Varje projekt har en monotoniskt ökande revision i molnet, som ökar vid varje distribution. CLI:et kommer ihåg revisionen det senast synkade med och skickar den vid varje push. Om molnet har rört sig framåt — för att en annan maskin eller teammedlem distribuerade — avvisas din push istället för att tyst skriva över deras arbete, och CLI:et erbjuder tre vägar framåt:
npx tgcloud fetch # se vad som ändrades i molnet, kontrollera sedan igen
npx tgcloud pull # ta in det senaste i dina arbetsfiler
npx tgcloud push --force # skriv över moln-statusen (farligt)
Denna optimistiska-concurrency-kontroll är anledningen till att du kan dela en bot över ett team utan en låst distributionsprocess. Kommandon avslutas med icke-noll vid fel — en avvisad distribution, en misslyckad migrering, ett autentiseringsfel, en modul som kastade fel under run — så de komponerar rent i skript och CI-pipelines.