Telegram Serverless îți permite să rulezi cod backend pentru botul și Mini Aplicația ta direct pe infrastructura Telegram — fără servere de configurat, fără containere de menținut, fără bătăi de cap cu scalarea. Scrii module JavaScript simple, le desfășori cu o singură comandă, iar Telegram le rulează într-un sandbox V8 rapid și izolat, care se află chiar lângă Bot API și o bază de date integrată.
Dacă a trebuit vreodată să conectezi un bot la un VPS, o funcție cloud sau un panou de găzduire doar pentru a răspunde la o comandă /start, aceasta este partea de care nu mai trebuie să te ocupi.
De ce serverless
Un bot Telegram este, în esență, un program care reacționează la actualizări. În mod tradițional, trebuia să găzduiești acel program undeva unde să fie mereu pornit, accesibil și securizat — și apoi să îl menții astfel. Telegram Serverless elimină complet acest strat:
- Fără infrastructură. Nu există nicio mașină de închiriat, actualizat sau monitorizat. Codul tău rulează la cerere și se scalează automat odată cu botul tău.
- Credențiale incluse. Telegram Bot API, o bază de date bazată pe SQLite și HTTP outbound sunt disponibile pentru fiecare modul fără nicio configurare suplimentară — nimic de instalat, nicio credențială de conectat.
- Execuție rapidă și izolată. Fiecare invocare rulează într-un izolat V8 ușor, aproape de sistemele proprii ale Telegram, astfel încât apelurile către Bot API și baza ta de date sunt rapide și fiabile.
- Un flux de lucru real pentru dezvoltatori. Un proiect trăiește într-un folder pe mașina ta sub controlul versiunilor. Editezi fișierele, vezi exact ce s-a schimbat, desfășori atomic și actualizezi schema bazei de date cu migrări revizuite — exact așa cum lucrezi deja cu orice altceva.
Modelul mental: Cum funcționează proiectul
Lucrezi în trei locuri, care se mapează clar unul pe celălalt:
| Unde | Ce se află acolo |
|---|---|
| Folderul proiectului | Module JavaScript — schemă, cod partajat, handlere de actualizare, endpoint-uri Mini App — și, dacă ai unul, front‑end-ul Mini App-ului |
| Cloud | Copia desfășurată a acelor module, baza de date a botului tău și fișierele Mini App-ului |
CLI-ul tgcloud |
Puntea — îți arată diferențele și le sincronizează |
Nu te conectezi niciodată prin SSH la nimic. Editezi fișierele local, rulezi npx tgcloud push, iar platforma se ocupă de restul. Traficul botului tău este gestionat de modulele desfășurate; baza de date persistă între invocări.
Un proiect are doar patru tipuri de cod, toate în interiorul unui folder tgcloud/:
tgcloud/
├─ endpoints/ # funcții pe care le apelează Mini App-ul tău
├─ handlers/ # funcții care reacționează la tipurile de actualizări Telegram
├─ lib/ # cod partajat pe care îl imporți de oriunde
└─ schema.js # tabelele bazei tale de date
Când sosește o actualizare — un mesaj, o apăsare de buton, o interogare inline — Telegram o direcționează către handlerul corespunzător (handlers/message.js, handlers/callback_query.js, …) și îi apelează exportul implicit. Acea funcție comunică cu Bot API și baza de date prin SDK și returnează. Acesta este întregul ciclu. O actualizare fără un handler corespunzător este pur și simplu ignorată, deci adaugi doar handlerele de care ai nevoie.
Un Mini App este opțional. Dacă botul tău are unul, front-end-ul său trăiește în același proiect, lângă tgcloud/. Serverless găzduiește rezultatul construcției sale și rulează endpoints/ pe care le apelează. Vezi
Mini App front‑end
.
Demo rapid
Iată un bot demo complet și funcțional. Acesta răspunde la fiecare mesaj și reține câte a văzut din fiecare 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;
// Inserează contorul sau actualizează-l dacă acest chat are deja unul — și primește
// rândul rezultat înapoi în aceeași instrucțiune prin .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: `Salut! Am văzut ${row.seen} mesaj(e) de la tine.`,
});
}
Desfășoară-l:
npx tgcloud push # încarcă modulele
npx tgcloud migrate # creează tabelul `counters`
Acesta este un bot live cu stare persistentă și fără server. Totul în el — api, db, DSL-ul table() — este descris în secțiunile de mai jos.
Serverless este un backend general pentru boții Telegram și Mini Apps, nu un șablon pentru un singur tip de aplicație. Este ideal pentru:
- Boți cu AI conversațional care trebuie să stocheze starea per‑utilizator într-o bază de date.
- Backend-uri pentru Mini Apps care stochează datele utilizatorilor și servesc conținut dinamic.
- Jocuri și instrumente — inclusiv clasamente, chestionare și multe altele.
- Automatizări și integrări care apelează API-uri HTTP terțe și trimit rezultatele în chaturi.
Noțiuni de bază
Acest ghid te poartă de la un folder gol la un bot live care răspunde la mesaje și stochează date. Presupune că ai
Node.js
18 sau mai nou instalat și un bot înregistrat la
@BotFather
. Până la final, vei fi folosit fiecare comandă de care ai nevoie zi de zi: push, migrate, run și status.
Înainte de orice, pornește Serverless. În @BotFather , deschide botul tău → Serverless și pornește-l. Aceasta activează funcția pentru acest bot și deblochează tokenul de acces CLI, handlerele, biblioteca și baza de date.
1. Crearea unui proiect
Cea mai rapidă metodă de a începe este creatorul de proiecte, care configurează un proiect și instalează CLI-ul în el:
npm create @tgcloud/bot example_bot
cd example_bot
Argumentul este folderul țintă: transmite . pentru a configura în folderul curent sau orice cale. Funcționează și într-un folder existent și nu suprascrie niciodată fișierele pe care le ai deja.
Acest lucru îți oferă un proiect gata de editat:
example_bot/
├─ tgcloud/ # tot ce rulează platforma
│ ├─ handlers/
│ │ └─ message.js # un handler de mesaje de pornire (redă textul înapoi)
│ ├─ lib/ # modulele tale partajate merg aici (gol la început)
│ └─ schema.js # tabelele bazei tale de date
├─ docs/
│ └─ tgcloud-sdk.md # referință SDK (pentru tine și instrumentele tale AI)
├─ AGENTS.md # orientare pentru asistenții de programare AI
├─ package.json
└─ tgcloud.jsonc # configurarea proiectului
Fișierele generate sunt auto-documentate — fiecare conține exemple comentate despre ce poți face în continuare.
CLI-ul se instalează în proiect ca o dev-dependență locală, deci îl rulezi cu npx tgcloud <comandă> (npx găsește copia din node_modules-ul proiectului tău) sau prin scurtăturile npm run pe care generatorul le adaugă în package.json (npm run deploy, npm run status). În mod implicit, nu există niciun tgcloud global pe PATH-ul tău.
Poți, de asemenea, să îl instalezi global — npm install -g @tgcloud/cli — dacă preferi să tastezi tgcloud direct de oriunde. Este util pentru a rula tgcloud init în orice folder gol și este ceea ce are nevoie funcția de auto-completare din shell. Oricum, obții același proiect.
Construiești un Mini App? Începe cu front‑end-ul acestuia. Creează-l cu instrumentul tău obișnuit, apoi rulează creatorul de proiect în acel folder. Vezi Mini App front‑end .
2. Conectarea botului
Fiecare proiect este legat de un singur bot. Conectează-le cu login, care cere tokenul tău de acces CLI (
@BotFather
→ botul tău → Serverless → CLI Access → Token de acces — un token separat de
tokenul API al botului tău
) și îl stochează local:
npx tgcloud login
Tokenul are forma app<id>:<secret>. CLI-ul îl păstrează în .tgcloud/, care este ignorat de git, și nu afișează niciodată partea secretă. Login este singura dată când ți se cere — vezi
Autentificare
pentru modul în care tokenurile sunt rezolvate în CI.
3. Verificări
Două comenzi îți spun care este starea în orice moment, ambele complet offline:
npx tgcloud status # ce s-a schimbat local față de copia desfășurată
npx tgcloud diff # modificările linie cu linie
Imediat după init, totul este nou și nimic nu este încă desfășurat. status arată fișierele de pornire care așteaptă să fie încărcate.
4. Desfășurare (Deploy)
Trimite modulele tale în cloud:
npx tgcloud push
push încarcă fiecare modul schimbat într-o singură tranșă atomică și actualizează înregistrarea locală a ceea ce conține cloud-ul acum. Botul tău este live: deschide-l în Telegram și trimite-i un mesaj — handlerul de pornire îl va reda înapoi.
Desfășurarea nu atinge niciodată baza de date. Încărcarea codului și schimbarea schemei bazei de date sunt pași separați în mod deliberat, astfel încât o desfășurare de cod nu te poate surprinde niciodată cu o migrare de date. Pentru asta este pasul următor.
5. Adăugarea unui tabel în baza de date
Hai să facem botul să rețină ceva. Deschide tgcloud/schema.js și declară un 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())`) ,
});
Desfășoară schema, apoi aplic-o în baza de date:
npx tgcloud push # încarcă noua schemă
npx tgcloud migrate # creează tabelul `messages`
push raportează că schema nu este sincronizată și îți arată modificarea în așteptare, dar nu aplică nimic. migrate te ghidează prin modificare și, la confirmarea ta, creează tabelul. Acest model în doi pași — și ce se întâmplă cu modificările mai riscante, precum ștergerile — este tratat în
Migrări
.
6. Stocarea și citirea datelor
Acum folosește tabelul din handlerul tău. Editează tgcloud/handlers/message.js:
import { api, db } from 'sdk';
import { eq } from 'sdk/db';
import { messages } from '../schema.js';
export default async function (message) {
// Salvează acest mesaj.
await db.insert(messages)
.values({ chatId: message.chat.id, text: message.text })
.run();
// Numără câte am stocat pentru acest chat.
const count = await db.$count(messages, eq(messages.chatId, message.chat.id));
await api.sendMessage({
chat_id: message.chat.id,
text: `Salvat. Până acum, sunt ${count} mesaj(e) din acest chat.`,
});
}
Desfășoară handlerul actualizat cu npx tgcloud push, apoi trimite botului câteva mesaje și urmărește cum crește numărul. Baza de date persistă între invocări — aceasta este memoria botului tău.
7. Testare fără desfășurare
Nu trebuie să desfășori pentru a încerca o modificare. npx tgcloud run execută un handler pe platformă folosind fișierele tale locale, fără a le publica:
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hello" }'
Argumentul este payload-ul pe care îl primește handlerul tău — pentru handlers/message, un
Mesaj
— scris în
JSON5
(deci poți omite ghilimelele pentru chei). Comanda tipărește tot ce a logat handlerul cu console.*, valoarea returnată și cât timp a durat. Acesta este cel mai rapid mod de a itera logica — fără desfășurare, fără așteptarea unui mesaj real.
8. Sincronizarea
Pe măsură ce lucrezi, câteva comenzi mențin proiectul local și cloud-ul aliniate: npx tgcloud status arată ce s-a schimbat, npx tgcloud push desfășoară, npx tgcloud pull aduce proiectul local la nivelul cloud-ului, npx tgcloud fetch verifică ce este desfășurat fără a atinge fișierele, iar npx tgcloud reset elimină modificările locale.
Dacă două persoane (sau două mașini) desfășoară pe același bot, platforma detectează conflictul și
pushse oprește pentru a-ți permite să facipullmai întâi — nu poți suprascrie niciodată silențios munca altcuiva. Vezi Sincronizarea .
Construirea cu AI
Preferi să construiești cu un asistent AI — sau singurul programator din echipa ta este un AI? Poți totuși să livrezi un bot. Am făcut un prim pas pentru ca un agent AI să se simtă ca acasă într-un proiect: fiecare proiect nou este configurat cu un AGENTS.md și o referință docs/tgcloud-sdk.md pe care instrumentele de codare agențiale le citesc automat.
Împreună cu un runtime mic și autonom — un singur SDK, fără pachete npm de gestionat — aceasta oferă asistentului un start rapid asupra convențiilor pe care generarea generică de cod tinde să le rateze: importuri relative cu .js, fără chei externe, fiecare apel db este asincron, un handler per tip de actualizare și fluxul în doi pași push/migrate.
Încearcă:
npm create @tgcloud/bot my-bot
cd my-bot
opencode # sau Claude Code, Cursor, … — orice agent care citește AGENTS.md
Apoi întreabă, în limbaj natural:
Scrie un bot care reține lista de sarcini a fiecărei persoane — adaugă un element când trimit text și arată toată lista când trimit /list.
Asistentul editează schema.js și handlerele pentru tine; tu revizuiești, testezi instantaneu o modificare cu npx tgcloud run, apoi mergi live cu npx tgcloud push și npx tgcloud migrate. AGENTS.md face parte din proiectul tău — editează-l pe măsură ce botul crește, astfel încât ghidarea să rămână corectă.
În mișcare cu BotFather
Ești doar cu telefonul? Întregul proiect trăiește și în @BotFather — deschide botul tău → Serverless și primești tot ce gestionează CLI-ul, pe un ecran tactil:
- Handlere — creează, editează și testează handlerele de actualizare; BotFather menține webhook-ul sincronizat cu handlerele pe care le ai (același In sync / Out of sync pe care îl raportează CLI-ul).
- Endpoint-uri — funcțiile pe care le apelează Mini App-ul tău.
- Bibliotecă — modulele tale
lib/partajate. - Bază de date — editează
schema.jsîn aceeași sintaxă de tip Drizzle, revizuiește modificările în așteptare și aplică-le; Save (Salvare) desfășoară modificările. - Fișiere Mini App — front‑end-ul pe care utilizatorii tăi îl deschid în Telegram.
- CLI Access — obține tokenul de acces CLI de aici când te întorci la o tastatură.
Este același proiect cloud, deci poți începe un handler pe telefon și să-l aduci cu npx tgcloud pull pe laptop mai târziu — nimic nu este legat de un singur client. Rularea unui handler arată chiar și ieșirea Consolei direct în chat, exact ca
npx tgcloud run
.
Proiecte și module
Un proiect serverless este un folder obișnuit sub controlul versiunilor. Tot ce rulează platforma se află într-un folder, tgcloud/; restul proiectului este al tău — front-end de mini-app, configurări, documentație. Nu există niciun pas de construcție pentru bot, niciun node_modules la runtime și niciun punct de intrare pe server.
Anatomia unui proiect
example_bot/
├─ tgcloud/ # partea desfășurată — nimic altceva nu este
│ ├─ handlers/ # handlere de actualizare — plate, un singur nivel
│ │ ├─ message.js
│ │ └─ callback_query.js
│ ├─ endpoints/ # funcții pe care le apelează Mini App-ul tău — plate, un singur nivel
│ │ └─ getProfile.js
│ ├─ lib/ # module partajate; subdirectoare permise
│ │ ├─ reply.js
│ │ └─ internal/util.js
│ └─ schema.js # schema bazei de date — un singur fișier
└─ .tgcloud/ # starea CLI, ignorat de git
Doar fișierele .js din interiorul tgcloud/ — schema.js, lib/, handlers/, endpoints/ — sunt desfășurate. Orice altceva — Markdown, fișiere de configurare, folderul .tgcloud/, orice în afara tgcloud/ — rămâne pe mașina ta. Numele modulelor sunt relative la tgcloud/. Fișierul tgcloud/handlers/message.js este modulul handlers/message, iar acesta este numele pe care îl folosește npx tgcloud run.
Faci upgrade de la un proiect mai vechi? Proiectele create înainte de CLI 0.2.0 păstrau
schema.js,lib/șihandlers/la rădăcina proiectului. Ruleazănpx tgcloud upgradeo dată pentru a le muta întgcloud/.
schema.js — baza ta de date. Declară tabelele ca exporturi numite folosind DSL-ul schemei și trăiește în tgcloud/ ca un singur fișier. Este desfășurat ca orice alt modul, dar desfășurarea sa nu schimbă niciodată baza de date — modificările schemei sunt aplicate separat cu npx tgcloud migrate. Vezi
Baza de date
.
lib/ — cod partajat, orice vrei să refolosești în handlere: ajutoare pure, straturi de acces la baza de date, formatare, integrări cu servicii externe. lib/ este singurul director care poate conține subdirectoare (lib/internal/util.js, lib/payments/stripe.js), deci poți organiza o bază de cod mai mare cum dorești. Modulele din lib/ nu sunt invocate niciodată direct de platformă; ele există pentru a fi importate de handlere și între ele.
handlers/ — punctele de intrare ale botului tău. Fiecare fișier corespunde unui tip de actualizare Telegram, iar platforma direcționează fiecare actualizare primită către handlerul corespunzător:
| Fișier | Gestionează |
|---|---|
handlers/message.js |
Mesaje noi primite |
handlers/inline_query.js |
Interogări noi inline |
handlers/callback_query.js |
Interogări noi de tip callback |
| … | orice alt tip de actualizare Bot API |
handlers/ este plat — fără subdirectoare. export default al unui handler este funcția pe care o apelează platforma (vezi
Handlere
).
Un tip de actualizare este gestionat doar dacă fișierul său handler există și nu este gol. Dacă nu există handlers/<tip>.js — sau fișierul este gol — actualizările de acel tip sunt ignorate, iar platforma nu rulează nimic pentru ele. Deci, păstrează doar handlerele de care ai nevoie: fiecare pe care îl adaugi este încă un tip de actualizare la care botul tău trebuie să se trezească pentru a procesa, iar ignorarea restului înseamnă că Telegram nu pornește codul tău pentru actualizări pe care oricum le-ai ignora.
Pentru a configura un nou handler, rulează
npx tgcloud add handlers/<tip>.
endpoints/ — partea de server a Mini App-ului tău. Fiecare fișier este o funcție pe care Mini App-ul o apelează după nume, deci endpoints/getProfile.js răspunde la POST /api/getProfile. Platforma verifică dacă apelul provine de la un utilizator Telegram din interiorul Mini App-ului tău înainte ca codul tău să ruleze și îți oferă acel utilizator. endpoints/ este plat, ca handlers/. Vezi
Endpoint-uri
.
.tgcloud/ — stare locală gestionată în întregime de CLI, inclusiv credențialele tale salvate. Este ignorat de git și nu ar trebui niciodată să citești sau să scrii în el manual — folosește comenzile CLI în schimb.
Sistemul de module
La runtime, un modul poate vedea exact două lucruri: SDK-ul platformei și celelalte module din proiectul tău. Nu există pachete npm, nici sistem de fișiere și nicio rețea, cu excepția prin fetch-ul SDK-ului.
Importă propriile module așa cum ai face-o în orice proiect JavaScript — prin cale relativă, cu extensia .js — și SDK-ul prin numele său:
// tgcloud/handlers/message.js
import { users } from '../schema.js'; // modulul schemei
import { addItem } from '../lib/cart.js'; // un modul lib
import { format } from '../lib/internal/fmt.js'; // modul lib imbricat
import { db, api, fetch } from 'sdk'; // SDK-ul platformei
// tgcloud/lib/cart.js
import { format } from './internal/fmt.js'; // un vecin
import { users } from '../schema.js';
Un import relativ trebuie să se termine în .js și nu poate duce în afara tgcloud/:
import { users } from '../schema'; // adaugă .js
import config from '../../config.js'; // în afara tgcloud/
import { z } from 'zod'; // fără pachete npm
Fiecare modul are și un nume — calea sa în interiorul tgcloud/ fără extensie, cum ar fi schema, lib/cart sau handlers/message. Acel nume este ceea ce folosesc npx tgcloud run și ieșirea CLI-ului, iar un modul poate fi importat și prin el (from 'lib/cart', fără extensie). Preferă importurile relative în codul tău. Sunt ceea ce înțelege editorul tău — salt la definiție, auto-completare, redenumire.
Ce este disponibil la runtime sunt exact două lucruri: sdk și submodulele sale (sdk/db, sdk/api, sdk/fetch) — întreaga suprafață a platformei, vezi
SDK-ul
— și propriile tale module în tgcloud/. Aceasta este lista completă. Dacă codul tău import-ează altceva, nu se va rezolva. Această constrângere este ceea ce menține modulele rapide la încărcare și sigure la rulare.
Handlere
Un handler este un modul în handlers/ al cărui export implicit platforma îl invocă atunci când sosește o actualizare corespunzătoare.
// handlers/message.js
import { api } from 'sdk';
export default async function (message) {
await api.sendMessage({
chat_id: message.chat.id,
text: `Ai spus: ${message.text ?? '(fără text)'}`,
});
}
Un handler primește payload-ul actualizării ca argument — platforma despachetează
Actualizarea
Telegram pentru tine. handlers/message.js primește
Mesajul
(adică update.message); handlers/callback_query.js primește
CallbackQuery
; și așa mai departe. Al doilea argument al handlerului este un obiect de context per invocare, ctx. Acesta transportă Update-ul brut ca ctx.update — accesează-l când ai nevoie de ceva din afara payload-ului, cum ar fi update_id.
Un handler poate fi async (de obicei este) și poate returna o valoare. Acesta accesează Bot API, baza de date și HTTP outbound prin SDK.
Nu ai nevoie de o actualizare reală pentru a testa un handler. npx tgcloud run îl execută pe platformă cu payload-ul pe care îl furnizezi și codul tău local curent:
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hi" }'
Argumentul este payload-ul — același obiect pe care îl primește handlerul tău — în JSON5. Pentru a furniza ctx-ul handlerului (al doilea argument), adaugă --ctx, de ex. --ctx '{ update: { update_id: 1 } }'. Aceasta rulează împotriva fișierelor tale locale, deci poți încerca modificări înainte de a le desfășura. Vezi
run
.
Endpoint-uri
Un endpoint este un modul în endpoints/ pe care îl apelează Mini App-ul tău — locul pentru orice nu poate face front-end-ul singur, cum ar fi citirea și scrierea în baza de date, apelarea Bot API sau comunicarea cu servicii externe folosind secretele tale.
// 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 negăsit', { userId: user.id });
return profile;
}
Primul argument, input, este obiectul JSON trimis de Mini App. Al doilea, ctx, transportă ctx.initData —
datele de inițializare
ale Mini App-ului (user, chat, start_param, auth_date, …), deja verificate de platformă. Are aceeași formă ca Telegram.WebApp.initDataUnsafe pe client, dar aici poți avea încredere în el. Un apel fără date de inițializare valide este respins cu 401 înainte ca codul tău să ruleze, deci ctx.initData.user este într-adevăr persoana care a deschis Mini App-ul tău. Endpoint-urile pot fi apelate doar în acest mod — de la Mini App-ul botului tău, de către un utilizator Telegram — deci nu există niciun API public de protejat și nicio autentificare de construit.
Orice returnează endpoint-ul este trimis înapoi sub formă de JSON. Pentru a refuza un apel, throw new EndpointError(descriere, parametri?). Mini App-ul primește descrierea și obiectul parametri așa cum sunt. Dacă trebuie să ramifici logica pe tipul de eroare în front-end, pune un cod în parametri — { cod: 'OUT_OF_STOCK' }. Orice altă excepție este raportată ca o eroare internă generică, iar mesajul real merge doar în jurnalele (log-urile) tale, astfel încât elementele interne să nu ajungă niciodată la client.
Apelarea unui endpoint din Mini App
Mini App-ul apelează un endpoint după nume cu Telegram.WebApp.Serverless.call din SDK-ul oficial
Mini Apps
, telegram-web-app.js — scriptul pe care Mini App-ul tău deja îl include. Acesta trimite datele de inițializare pentru tine și apelează înapoi cu o eroare sau cu rezultatul endpoint-ului:
Telegram.WebApp.Serverless.call('getProfile', { lang: 'en' }, function (err, profile) {
if (err) {
// err.message — descrierea; err.parameters — ce a transportat EndpointError-ul tău
if (err.type === 'ENDPOINT_ERROR') showMessage(err.message);
return;
}
render(profile);
});
Input-ul este opțional și trebuie să fie un obiect simplu. Fără el, apelul este Serverless.call('getProfile', callback).
err.type este 'ENDPOINT_ERROR' atunci când endpoint-ul tău a aruncat o EndpointError. Orice altă valoare înseamnă că platforma a refuzat apelul sau endpoint-ul a eșuat neașteptat, de exemplu 'UNAUTHORIZED' când datele de inițializare lipsesc sau sunt invalide.
Numele endpoint-urilor sunt ca identificatorii JavaScript — litere, cifre și _, fără să înceapă cu o cifră (getProfile, place_order). Configurează unul cu
npx tgcloud add endpoints/<nume>
și testează-l fără Mini App cu
run
, transmițând manual datele de inițializare:
npx tgcloud run endpoints/getProfile '{}' --ctx '{ initData: { user: { id: 1, first_name: "Ann" } } }'
Ce se desfășoară
Când rulezi npx tgcloud push, CLI-ul colectează fiecare fișier .js de sub schema.js, lib/, handlers/ și endpoints/ și trimite exact acel set ca spațiu de module al proiectului tău — împreună cu folderul de build al Mini App-ului, dacă tgcloud.jsonc specifică unul (vezi mai jos). Orice este prezent în cloud, dar absent din proiectul tău, este eliminat, deci starea desfășurată oglindește întotdeauna folderul tău — inclusiv ștergerile. Nimic din afara tgcloud/ nu este desfășurat ca cod — restul rădăcinii proiectului este al tău. În interiorul tgcloud/, un fișier .js care nu se află în una dintre acele locații (tgcloud/util.js, tgcloud/helpers/x.js) este marcat, deci nu rămâne nedesfășurat în tăcere. Markdown-ul, fișierele cu punct (dotfiles) și .tgcloud/ nu sunt niciodată desfășurate.
Mini App front-end
Rădăcina proiectului este a ta și este locul natural pentru front‑end-ul Mini App-ului tău — construit cu React, Vue, Svelte sau HTML simplu, de Vite sau orice alt bundler. Serverless găzduiește rezultatul construcției (build output) lângă botul tău. Un singur push desfășoară codul și fișierele statice împreună, iar aplicația este servită la https://app<app_id>.tgcloud.ai/ (push tipărește adresa exactă — folosește-o ca URL al Mini App-ului tău în
@BotFather
).
Raportarea abuzurilor. Mini App-urile de pe tgcloud.ai sunt publicate de dezvoltatori independenți. Pentru a raporta conținut ilegal sau abuzuri pe un subdomeniu
app<app_id>.tgcloud.ai, trimite un email la [email protected] .
Pentru a începe, creează mai întâi front‑end-ul, cu orice instrument dorești, apoi adaugă Serverless peste el:
npm create vite@latest example_app # sau create-vue, …
cd example_app
npm create @tgcloud/bot .
Creatorul de proiecte nu suprascrie niciodată fișierele tale. Adaugă folderul tgcloud/ lângă front‑end, recunoaște folderul de build și adaugă un script npm run deploy care construiește aplicația și o desfășoară împreună cu botul.
Ce este găzduit este setat în tgcloud.jsonc, fișierul de configurare al proiectului la rădăcina acestuia:
{
"$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 }]
}
}
Doar "source" este necesar. Este folderul de build, relativ la rădăcina proiectului. Celelalte chei ale "static" configurează modul în care este servit (mai jos). init creează tgcloud.jsonc pentru tine. Când recunoaște front‑end-ul, completează și "source":
| Front‑end | init setează "source" la |
|---|---|
| Vite — React, Vue, Svelte, Solid, … | dist |
| Next.js, Nuxt, SvelteKit, React Router, TanStack Start, Angular | nimic — configurează mai întâi un export static, apoi setează "source" la folderul respectiv |
Cu orice altă configurare, setează "source" la folderul tău de build manual.
Linia $schema oferă editorului tău auto-completare și validare pentru fiecare cheie. Fișierul este JSON cu comentarii și virgule finale permise. Dacă un alt instrument de-al tău trebuie să-l citească drept JSON simplu, redenumește-l tgcloud.json — atunci trebuie să fie JSON strict.
Construiește (Build) înainte de a desfășura. push încarcă folderul exact așa cum este pe disc — cu excepția fișierelor și folderelor al căror nume începe cu un punct, care sunt omise — și nu rulează niciodată construcția (build-ul) tău, deci un simplu npx tgcloud push după editarea front‑end-ului ar desfășura construcția anterioară. Folosește npm run deploy (rulează scriptul tău build, apoi tgcloud push) sau construiește tu mai întâi.
Opțiunile de servire:
spa— serveșteindex.htmlpentru căile care nu există și nu au extensie de fișier, astfel încât un router client-side să funcționeze la reîncărcare. Căile cu extensie primesc în continuare404.immutable— tipare pentru fișiere cu conținut hash (precum/assets/*de la Vite) pe care browserele le pot stoca în cache timp de un an. Folosește-l doar pentru fișierele cu conținut hash. Orice alt fișier care se potrivește aici ar rămâne învechit în browsere timp de un an și nu există nicio modalitate de a-l retrage.headers— anteturi de răspuns pentru căile care se potrivesc unei reguli (o cale exactă sau un prefix care se termină în/*). O valoarenullelimină un antet.redirects— reguli de redirecționare, unde prima potrivire câștigă. Un fișier existent bate întotdeauna o regulă.statuseste301dacă nu îl setezi."static": "dist"— o scurtătură pentru{ "source": "dist" }când nu ai nevoie de nimic altceva."static": false— elimină site-ul desfășurat la următoarea desfășurare. Ștergerea cheii oprește pur și simplu acest proiect din a gestiona fișierele statice, iar ce este desfășurat rămâne acolo.
Site-ul este servit de la rădăcina propriului său host, deci base (/) implicit al bundler-ului tău este corect. Telegram Web deschide Mini Apps într-un iframe, deci nu adăuga X-Frame-Options sau o politică restrictivă frame-ancestors.
Același host răspunde la
endpoint-urile
tale sub /api/ — acolo merge Telegram.WebApp.Serverless.call('getProfile', …). Nu pune propriile tale fișiere sub /api/ în folderul de build.
Baza de date
Fiecare bot primește propria bază de date — un magazin bazat pe SQLite care persistă între invocări și este disponibil pentru fiecare modul prin db. Îți descrii tabelele în schema.js cu un DSL mic și tastat (typed); le citești și le scrii cu un generator de interogări fluent; și le evoluezi cu migrări revizuite.
Cunoști Drizzle ORM ? Atunci știi deja cum să comunici cu baza de date de aici. DSL-ul schemei și generatorul de interogări urmează Drizzle — constructorii de coloane,
select().from().where(), operatorii,onConflictDoUpdate,.returning()și tag-ulsqlse comportă exact cum te-ai aștepta, deci citirea și scrierea datelor este API-ul familiar pe care îl folosești deja. Pur și simplu îl imporți dinsdk/db, iar câteva specificități ale platformei (în special lipsa cheilor externe ) sunt punctate acolo unde apar.
Declararea tabelelor
Tabelele sunt exporturi numite în schema.js. Apelarea table() construiește o descriere la momentul încărcării (nu atinge baza de date); platforma descoperă tabelele exportate când desfășori schema.js și migrează baza de date pentru a se potrivi.
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(nume, coloane, extras?):
numeeste numele tabelului SQL;coloaneeste o hartă a proprietății JS → definiția coloanei;extraseste un callback opțional(t) => ({ … })undetexpune coloanele (t.createdeste o referință la acea coloană) — declară aici indecși și constrângeri la nivel de tabel.
Tipuri de coloane
| Factory | Tip SQLite | Note |
|---|---|---|
text() |
TEXT | |
integer() |
INTEGER | |
real() |
REAL | alias float() |
numeric() |
NUMERIC | |
blob() |
BLOB | citește/scrie Uint8Array |
boolean() |
INTEGER | stocat ca 0/1, citit ca true/false |
json() |
TEXT | auto JSON.stringify / JSON.parse |
Argumentul numelui coloanei este opțional — omite-l și va fi folosită cheia JS. Opțiunea
modecontrolează modul în care valorile se convertesc între SQLite și JavaScript.
mode |
Stocat ca | Valoare JS |
|---|---|---|
boolean |
INTEGER 0/1 | boolean |
json |
TEXT (JSON) | orice obiect/array |
timestamp |
INTEGER (secunde unix) | Date |
timestamp_ms |
INTEGER (ms unix) | Date |
bytes |
BLOB | Uint8Array |
boolean() și json() sunt scurtături pentru integer(nume, { mode: 'boolean' }) și text(nume, { mode: 'json' }).
Un blob() citește și scrie un Uint8Array — runtime-ul nu are Node Buffer (iar un Buffer este o subclasă a Uint8Array, deci acesta este tipul de bază portabil). mode-ul de mai sus guvernează doar cum este codificată o valoare, independent de tipul de stocare al coloanei: deci blob('col', { mode: 'json' }) este echivalentul BLOB al json() — aceeași codificare JSON, păstrată într-o coloană BLOB în loc de TEXT.
TLDR:
blob()foloseșteUint8Array.modecontrolează codificarea, nu stocarea, deciblob(..., { mode: 'json' })stochează JSON ca un BLOB, în timp cejson()îl stochează ca TEXT.
Modificatori de coloană
Modificatorii de coloană se înlănțuie pe o coloană.
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' }) // sau 'virtual'
text('email').deprecated('înlocuit de login') // marchează coloana pentru eliminare
Un
.default()pe o coloanăjson()codifică valoarea pentru tine; un defaultsql`…`este transmis textual..deprecated()este terminal — vezi Migrări .
Indecși și constrângeri
Indecșii și constrângerile sunt declarate în callback-ul extras, unde coloanele sunt în 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})`) , // index de expresie
active: index('idx_active').on(t.userId).where(sql`done = 0`) , // index parțial
}));
Modificatorii la nivel de tabel se înlănțuie după table(...): .strict(), .withoutRowid(), .deprecated('motiv').
Fără chei externe
Runtime-ul rulează cu PRAGMA foreign_keys dezactivat. O cheie externă declarată ar fi silențios inertă — fără cascade, fără protecție împotriva orfanilor — ceea ce este mai rău decât să nu ai niciuna, deci DSL-ul face imposibil acest lucru. Anume, .references() și foreignKey() la nivel de tabel aruncă eroare când sunt declarate, iar o schemă care le folosește nu se va desfășura.
Ar trebui să modelezi relațiile cu coloane simple (userId: integer('user_id')) și să impui integritatea în codul aplicației tale: inserează părinții înainte de copii, șterge copiii înainte de părinți, gestionează erorile corect și curăță orfanii cu un LEFT JOIN … WHERE parent.id IS NULL când ai nevoie.
Absența cheilor externe este o constrângere deliberată, nu o omisiune. Ține cont de acest lucru din timp când îți planifici botul.
Interogare
db este un generator de interogări fluent. Fiecare interogare este asincronă — metodele terminale (.all(), .get(), .values(), .run()) returnează Promises, deci întotdeauna folosește 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(); // toate rândurile
await db.select().from(todos).where(eq(todos.id, 1)).get(); // primul rând sau null
await db.select().from(todos).values(); // rânduri ca array-uri de valori
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();
// proiecție personalizată: { 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();
// număr de rânduri — un helper, nu un terminal de generator:
await db.$count(todos); // toate rândurile
await db.$count(todos, eq(todos.done, false)); // cu un filtru
- Înlanțuibile:
.where(),.orderBy(),.limit(),.offset(),.groupBy(),.having(),.distinct(). - Terminale:
.all(),.get(),.values(). - Numără rândurile cu
db.$count(table, where?)(saucount()într-o proiecție).
Inserare, actualizare, ștergere:
await db.insert(todos).values({ userId: 1, text: 'Cumpără lapte' }).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();
Reține că o inserare de tip batch este o singură instrucțiune, deci este limitată de limita de variabile a SQLite (
rânduri × coloane); dacă depășești limita, inserarea dă eroare — împarte-le tu.
Operatorii sunt importați din 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) cu argumente multiple este la fel ca and(a, b). Al doilea argument al unei comparații este o valoare implicit, dar poate fi o altă coloană sau sql`…` — de ex. eq(a.x, b.y). Agregatele (count/sum/avg/min/max) sunt fragmente sql pentru o proiecție .select({ … }).
SQL brut — când generatorul nu este suficient, coboară la SQL brut. Modul este ales prin metodă: db.run pentru scrieri, db.all pentru multe rânduri, db.get pentru un rând, db.values pentru rânduri ca array-uri poziționale.
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, 'cumpără lapte']]
O scriere se rezolvă într-un rezultat de rulare — rowsAffected, lastInsertRowid și rows, care transportă o clauză RETURNING dacă SQL-ul a avut una. Folosește db.values când o interogare proiectează două coloane cu același nume (un join precum SELECT u.id, o.id): db.all cheiează rândurile după numele coloanei, deci al doilea id l-ar suprascrie pe primul.
Tag-ul sql`…` transformă ${valoare} într-un parametru legat (bound parameter) și ${tabel.coloană} într-un identificator, și îmbină fragmentele sql imbricate. Folosește sql.raw('…') pentru un literal fără parametri.
Interogările brute nu sunt legate de un tabel, deci rândurile lor vin înapoi fără conversie de mod — un boolean este
0/1, o coloană JSON este un șir de caractere (string), un timestamp este un număr. Doar generatorul legat de tabel convertește valorile.
Migrări
Baza ta de date se schimbă pe măsură ce botul tău crește. Platforma menține acest lucru sigur separând desfășurarea codului de schimbarea datelor și clasificând fiecare modificare a schemei după cât de riscantă este.
Desfășurarea nu atinge niciodată baza de date. Când rulezi npx tgcloud push pentru un schema.js modificat, platforma înregistrează noua schemă și îți spune ce s-ar modifica în baza de date — dar nu aplică nimic:
npx tgcloud push # desfășoară schema.js; raportează modificările DB în așteptare
npx tgcloud migrate # revizuiește și aplică-le
npx tgcloud migrate calculează diferența dintre schema ta și baza de date live și te ghidează prin ea. Ți se cere întotdeauna confirmarea înainte de a aplica ceva. Aceasta înseamnă că o desfășurare de cod de rutină nu poate declanșa niciodată o migrare de date accidental.
Fiecare modificare în așteptare transportă o stare care determină modul în care migrate o tratează:
| Stare | Semnificație | În migrate |
|---|---|---|
| safe | Aditivă și fără blocaje — un tabel, coloană sau index nou | Aplicată împreună într-un singur pas, la confirmare |
| warning | Potențial distructivă sau lentă — ștergerea a ceva sau un index pe un tabel uriaș | Prezentată una câte una, fiecare confirmată separat |
| manual | Nu poate fi făcută automat — de ex. schimbarea tipului unei coloane | Arătată cu îndrumare; o efectuezi manual |
| undocumented | Există în baza de date, dar nu în schema ta | Arătată pentru conștientizare; nu este aplicată |
Modificările sigure sunt rapide și reversibile ca intenție, deci se aplică împreună. Fiecare avertisment este o confirmare deliberată și individuală — nu există o opțiune de „aplică tot” pentru modificările distructive.
Modificările manuale vin cu un motiv și o acțiune sugerată. migrate se încheie cu un sumar: câte modificări au fost aplicate, sărite, care așteaptă o remediere manuală sau care nu sunt în schema ta.
Vezi
migratepentru flag-uri (--dry-run,--safe,--yes,--local).
Eliminarea lucrurilor
Ștergerea unui tabel sau a unei coloane din schema.js nu le șterge — acest lucru ar face o ștergere accidentală catastrofală. Pentru a elimina ceva, marchează-l ca depreciat:
// șterge o coloană
text('email').deprecated('înlocuit de login')
// șterge un tabel întreg
export const oldSessions = table('old_sessions', { /* … */ }).deprecated('nefolosit');
La următoarea migrate, obiectul depreciat apare ca o ștergere cu stare de avertisment pe care o confirmi individual. Odată ce a dispărut, elimină declarația.
Schimbarea tipului unei coloane
Schimbările de tip sunt manuale — SQLite nu le poate face întotdeauna pe loc, iar constrângerea valorilor existente este o decizie de judecată. migrate va arăta modificarea și raționamentul ei; efectueaz-o singur cu SQL brut (db.run(...)), de obicei prin crearea unei noi coloane sau tabel, copierea datelor și schimbarea lor.
SDK-ul
La runtime, un modul are o singură bibliotecă: sdk. Aceasta grupează cele trei lucruri de care are nevoie un backend de bot — o bază de date, Telegram Bot API și HTTP outbound — fără nimic de instalat și nicio credențială de configurat. Baza de date (db) este tratată în
Baza de date
; această secțiune acoperă api, fetch și globalul console.
import { db, api, fetch, InputFile, BotApiError, EndpointError } from 'sdk'; // întreaga suprafață
// sau din submodule:
import { table, integer, text, eq, sql } from 'sdk/db';
import { api } from 'sdk/api';
import { fetch } from 'sdk/fetch';
| Import | Ce este |
|---|---|
db |
Bază de date — generator de interogări și DSL schemă → Baza de date |
api |
Telegram Bot API — api.sendMessage(...) →
mai jos
|
fetch |
HTTP outbound → mai jos |
InputFile |
Un fișier (bytes + nume) de încărcat — funcționează cu api și fetch →
Fișiere
|
EndpointError |
Refuză apelul unui Mini App cu un mesaj pe care îl poate arăta → Endpoint-uri |
Importă propriile module ale proiectului prin cale relativă cu extensia .js (from '../schema.js', from '../lib/cart.js'). Vezi
Sistemul de module
.
The Bot API
api îți oferă întregul
Telegram Bot API
. Apelează orice metodă ca api.<metodă>(parametri). Fiecare metodă Bot API curentă — și viitoare — funcționează fără a fi nevoie de o actualizare SDK.
import { api } from 'sdk';
const me = await api.getMe(); // → rezultatul despachetat
await api.sendMessage({ chat_id: id, text: 'Salut!' });
await api.editMessageText({ chat_id, message_id, text: 'Actualizat' });
await api.answerCallbackQuery({ callback_query_id, text: 'Gata' });
Plicul de răspuns este despachetat. Bot API în mod normal înfășoară rezultatele în { ok: true, result: … }. api returnează result direct — getMe() se rezolvă la obiectul utilizator, nu la un wrapper. Parametrii folosesc numele snake_case proprii ale Bot API (chat_id, message_id, reply_markup, …).
Eșecurile aruncă BotApiError. Când Bot API returnează { ok: false }, apelul aruncă o BotApiError în loc să returneze o valoare falsă, astfel încât să nu o poți ignora accidental. Eroarea transportă .code (codul de eroare Bot API), .description (mesajul lizibil de către om), .method (ce metodă a eșuat) și .parameters (date extra cum ar fi retry_after pe un 429 sau migrate_to_chat_id). Prinde-o pentru a gestiona un eșec așteptat și aruncă din nou restul:
import { api, BotApiError } from 'sdk';
try {
await api.deleteMessage({ chat_id, message_id });
} catch (e) {
if (e instanceof BotApiError && e.code === 400) {
// 400 = mesajul a dispărut deja; asta e în regulă aici.
} else {
throw e;
}
}
Fișiere
Lucrează cu bytes de fișier, nu doar cu file_id-uri — încarcă fișiere noi și descarcă-le pe cele existente.
Încărcare — InputFile. Înfășoară bytes-ii bruti într-un InputFile (un container simplu: bytes, un nume de fișier și un type MIME opțional) și transmite-l oriunde o metodă Bot API așteaptă un fișier:
import { api, InputFile } from 'sdk';
const doc = new InputFile(bytes, 'raport.pdf', { type: 'application/pdf' });
await api.sendDocument({ chat_id, document: doc });
// fișierele imbricate în parametri funcționează și ele — de ex. un 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-ii sunt trimiși către Telegram ca o încărcare multipart reală — fișierul nu este buffered în întregime într-un singur apel către platformă — dar limitele Bot API ale Telegram se aplică în continuare: un bot poate încărca în prezent până la 50 MB per fișier (și 10 MB pentru o fotografie).
Descărcare — prin file_id. Doi helpers descarcă bytes-ii unui fișier înapoi. Platforma rezolvă file_id pentru tine (rulează getFile și descarcă conținutul în fundal), astfel încât nu atingi niciodată tokenul botului sau un URL de descărcare:
const bytes = await api.getFileContent(file_id); // → Uint8Array (întreg fișierul)
const { file, body } = await api.getFileStream(file_id); // descarcă prin stream în schimb
file.file_unique_id; // informațiile getFile
for await (const chunk of body) { /* Uint8Array */ } // sau buffer-ează-l: await stream.bytes()
Descărcările
getFileale Bot API Telegram sunt limitate la 20 MB, deci acesta este plafonul pentru ambele helpers.
HTTP
fetch este un client de tip fetch pentru a apela lumea exterioară — API-uri terțe, webhooks, orice prin 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();
Răspunsul oglindește platforma web: res.status, res.statusText, res.ok (true pentru 200–299), res.url, res.headers (.get(), .has(), .keys(), .entries()) și cititorii de body await res.json() / await res.text().
Poți de asemenea să citești body-ul incremental ca un stream —
for await (const chunk of res.body) { … }— care este modul în care consumi server-sent events sau ieșirea token-cu-token de la API-urile AI.
Helpers pentru body setează Content-Type corespunzător pentru tine:
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
Body-urile cererilor pot transporta și fișiere și bytes bruti: transmite un Uint8Array, un InputFile sau un FormData (pentru multipart/form-data) ca body, iar bytes-ii sunt trimiși prin stream — deci poți încărca către un API terț la fel cum trimiți fișiere către 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', 'de la botul meu');
const res = await fetch('https://api.example.com/upload', { method: 'POST', body: form });
În rest, se comportă ca fetch-ul standard pe care îl cunoști deja, cu două constrângeri:
- Răspunsul este citit ca text —
await res.json()/await res.text()sau transmis chunk-cu-chunk cures.body. - Răspunsul total este plafonat la 30 MB. Acel plafon acoperă întregul răspuns — streaming-ul cu
res.bodyîți permite să procesezi un body mare incremental, dar nu ridică limita.
Logging
Globalul standard console este disponibil — nimic de importat, este pur și simplu acolo, ca în orice JavaScript. Ieșirea sa este capturată și afișată de npx tgcloud run, ceea ce îl face instrumentul tău principal de depanare (debugging) în timpul dezvoltării.
console.log('procesare', { chatId: id }); // log / debug — simplu
console.info('pornit'); // info — albastru
console.warn('rată limitată'); // warn — galben
console.error(err); // error — roșu, cu stack trace
Fiecare linie este etichetată cu [fișier:linie] din care provine. console.error și console.trace adaugă un stack complet, în timp ce console.warn nu. Când rulezi npx tgcloud run pentru un modul, aceste linii sunt tipărite cu un prefix colorat per nivel, timpul scurs de la începerea rulării și originea — vezi
run
.
Command-line interface
tgcloud este puntea dintre folderul proiectului tău și cloud. Configurează proiecte, îți arată ce s-a schimbat, desfășoară, rulează module și aplică migrări ale bazei de date. Are nevoie de
Node.js
18 sau mai nou. Două moduri de a-l obține:
# Recomandat — creează un proiect cu CLI-ul instalat în el:
npm create @tgcloud/bot example_bot
# Sau instalează CLI-ul global și inițializează un folder gol:
npm install -g @tgcloud/cli
tgcloud init
Pachetul npm este @tgcloud/cli, iar comanda pe care o instalează este tgcloud. Fiecare comandă funcționează din orice subfolder al proiectului tău.
| Comandă | Scop |
|---|---|
init
|
Configurează un proiect nou în folderul curent |
add
|
Configurează un modul nou (un handler, un endpoint sau un modul lib) |
login
|
Conectează proiectul la un bot (salvează tokenul) |
status
|
Arată ce s-a schimbat local față de cloud |
diff
|
Arată modificările linie cu linie |
push
|
Desfășoară modulele schimbate și build-ul Mini App-ului în cloud |
migrate
|
Aplică modificările schemei bazei de date |
run
|
Execută un modul pe platformă fără desfășurare |
fetch
|
Verifică ce este desfășurat, fără a schimba fișierele locale |
pull
|
Aduce fișierele locale la nivelul cloud-ului |
reset
|
Elimină modificările locale; restaurează din starea cloud |
webhook
|
Inspectează și resincronizează webhook-ul gestionat de platformă |
completion
|
Tipărește un script de completare shell (bash/zsh/fish) |
upgrade
|
Mută un layout de proiect pre-0.2.0 în tgcloud/ |
Autentificare
Un proiect este legat de un bot prin tokenul său, care are forma app<id>:<secret>. Partea app<id> este publică și poate fi tipărită; secretul nu apare niciodată în log-uri sau erori.
Tokenul este rezolvat în această ordine:
TGCLOUD_TOKENvariabilă de mediu — pentru CI; nu este niciodată scrisă pe disc..tgcloud/credentials— scrisă denpx tgcloud login.- Niciuna → o eroare care te direcționează către
npx tgcloud login.
CLI-ul nu cere niciodată un token în mijlocul unei comenzi. Autentificarea este întotdeauna pasul explicit login. Dacă un token salvat devine invalid, CLI-ul îl elimină și îți cere să faci din nou login.
init
npx tgcloud init
Configurează un proiect nou în directorul curent: folderul tgcloud/ cu schema.js, lib/, handlers/ și un handler de pornire, plus tgcloud.jsonc, AGENTS.md, docs/ și folderul de stare .tgcloud/. Rulează-l și în rădăcina unui proiect front‑end existent — adaugă folderul tgcloud/ și documentația, și nu suprascrie niciodată fișierele tale. Dacă recunoaște front‑end-ul, indică și tgcloud.jsonc către folderul de build și adaugă un script deploy (build, apoi push) în package.json — doar dacă nu ai deja un script deploy. Vezi
Mini App front‑end
.
init refuză să creeze un proiect în interiorul altui proiect. Rularea lui din nou în rădăcina unui proiect propriu este în regulă și adaugă doar ce lipsește.
add
npx tgcloud add <țintă>
Configurează un singur modul nou, conectat și gata de editat — un handler, un endpoint sau un modul lib/.
npx tgcloud add handlers/callback_query # un nou handler de actualizare
npx tgcloud add endpoints/getProfile # un nou endpoint pentru Mini App
npx tgcloud add lib/cart # un nou modul partajat
Reține că
addnu suprascrie niciodată un fișier existent.
<țintă> este calea modulului (un .js final este opțional). Pentru handlers/, numele trebuie să fie un tip de actualizare Telegram. Platforma publică setul valid, deci un nume invalid este respins de la început. handlers/ și endpoints/ sunt plate, în timp ce lib/ poate fi imbricat (lib/payments/stripe).
Numele modulului este necesar. Specificarea doar a directorului este o eroare — dar una utilă: pentru handlers/ listează tipurile de actualizare pe care nu le ai deja, deci poți copia unul.
$ npx tgcloud add handlers
Eroare: Specifică un nume, de ex. "handlers/callback_query". Tipuri de handlers/ disponibile: callback_query, inline_query, chat_member, …
Completarea prin
<Tab>oferă același set — vezicompletion.
Fișierul generat are un export default live, deci handlerul este activ imediat ce desfășori — nu e nimic de decomentat. Desfășoară noul modul cu
push
.
login
npx tgcloud login
Cere tokenul tău de acces CLI — de la
@BotFather
→ botul tău → Serverless → CLI Access → Token de acces — un token separat de
tokenul API al botului tău
— îl validează cu platforma și îl salvează în .tgcloud/credentials.
logineste singura comandă care cere un token și necesită un terminal interactiv. În CI, seteazăTGCLOUD_TOKENîn schimb.
status
npx tgcloud status
Arată, per fișier, ce s-a schimbat între directorul tău de lucru și copia desfășurată: modificat, nou, șters, neschimbat. Funcționează offline, comparând cu starea cloud-ului de la ultimul tău push, pull sau fetch. De asemenea, avertizează despre fișierele .js rătăcite în tgcloud/ care nu vor fi desfășurate.
Pentru un proiect cu un Mini App, status arată și ce s-a schimbat în folderul său de build de la ultima desfășurare.
diff
npx tgcloud diff
La fel ca status, dar arată diferențele reale linie cu linie pentru modulele schimbate. De asemenea, offline.
push
npx tgcloud push [fișiere...]
Desfășoară proiectul tău în cloud într-o singură tranșă atomică.
Fără argumente, desfășoară întregul proiect, iar starea desfășurată este făcută să oglindească folderul tău exact — modulele pe care le-ai șters local sunt eliminate în cloud.
Cu argumente de fișier sau director (npx tgcloud push tgcloud/handlers/message.js, npx tgcloud push handlers/), desfășoară doar modificările din acele fișiere. Un push vizat nu șterge niciodată alte module. O țintă poate fi scrisă ca calea fișierului (tgcloud/handlers/message.js), calea din interiorul tgcloud/ (handlers/message.js) sau numele modulului (handlers/message) — toate înseamnă același modul.
O opțiune este --force — pentru a sări peste verificarea de concurență și a suprascrie orice se află în cloud. Folosește-o doar când ești sigur (vezi
Sincronizarea
).
Dacă tgcloud.jsonc indică un folder static, un push complet îl desfășoară în aceeași tranșă — doar fișierele care s-au schimbat sunt încărcate — și tipărește adresa site-ului. Construiește mai întâi, deoarece push nu rulează niciodată construcția ta (npm run deploy face ambele). npx tgcloud push dist/ desfășoară doar fișierele statice, iar un push care numește doar module le lasă intacte.
După o desfășurare, dacă schema.js s-a schimbat și baza de date nu este sincronizată, push tipărește un sumar al modificărilor în așteptare și sugerează npx tgcloud migrate. Nu le aplică niciodată singur.
migrate
npx tgcloud migrate
Aplică modificările schemei tale în baza de date. Calculează diferența dintre schema.js și baza de date live, apoi te ghidează prin ea pas cu pas cu un contor [N/M] care rulează:
- Un sumar scurt al tuturor modificărilor în așteptare.
- Modificări sigure, aplicate împreună într-un singur pas la confirmarea ta.
- Avertismente (ștergeri, operațiuni lente), una câte una, fiecare confirmată separat.
- Modificări manuale, arătate cu un motiv și o acțiune sugerată, nu aplicate automat.
- Obiecte nedocumentate (în baza de date, dar nu în schema ta), arătate pentru conștientizare.
Se încheie cu un sumar: aplicate, sărite, care așteaptă remediere manuală, nu în schemă.
Vezi Migrări pentru model.
Opțiuni: --dry-run (tipărește tot, nu aplica nimic), --safe (auto-aplică modificările sigure, sari peste avertismente), --yes (auto-aplică modificările sigure și fiecare avertisment, sari peste cele manuale — folosește cu grijă), --local (diferență față de schema.js local în loc de cel desfășurat). Fără un flag, migrate necesită un terminal și dă eroare într-un mediu non-interactiv în loc să ghicească.
run
npx tgcloud run <modul> [argumente] [--ctx <json5>]
Execută un handler sau un endpoint pe platformă fără a desfășura, folosind fișierele tale locale curente. Acesta este bucla internă rapidă pentru testarea logicii.
<modul>— un nume simplu (căutat înhandlers/șiendpoints/) sau o cale precumhandlers/message. Un nume simplu care există în ambele directoare trebuie scris cu directorul său.[argumente]— payload-ul transmis handlerului, scris în JSON5 pentru a omite ghilimelele pentru chei. Este obiectul de tip actualizare pe care îl primește handlerul tău (de ex. un Mesaj pentruhandlers/message) sauinput-ul unui endpoint și trebuie să fie un obiect — nu un array sau un șir de caractere simplu.--ctx <json5>— obiectul de context al modulului (al doilea argument), de asemenea JSON5. Folosește-l pentru a furniza ceea ce codul tău citește dinctx— actualizarea brută pentru un handler,--ctx '{ update: { update_id: 1 } }'sau datele de inițializare pentru un endpoint,--ctx '{ initData: { user: { id: 1 } } }'.runnu le verifică, deoarece testezi logica ta, nu verificările platformei.
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hi" }'
Platforma rulează modulul împotriva spațiului de module asamblat din proiectul tău local (deci codul lib/ modificat local este folosit și el) și returnează valoarea returnată, orice logat cu console.* și timpul scurs. Citește argumentele mari dintr-un fișier cu npx tgcloud run handlers/message "$(cat message.json5)".
fetch
npx tgcloud fetch
Verifică ce este desfășurat în prezent, fără a atinge fișierele tale de lucru. După aceasta, status și diff compară cu ultima stare din cloud. Util pentru a privi un conflict înainte de a decide cum să-l rezolvi.
pull
npx tgcloud pull
Aduce proiectul tău local la nivelul cloud-ului actualizând fișierele tale de lucru la starea desfășurată.
pull nu scrie niciodată folderul de build al Mini App-ului tău.
reset
npx tgcloud reset
Elimină modificările locale și restaurează modulele de la ultima stare cunoscută din cloud. Folosește-l pentru a arunca un experiment. Folderul de build al Mini App-ului nu este atins.
webhook
npx tgcloud webhook
npx tgcloud webhook sync [--drop-pending]
Telegram livrează actualizări către botul tău printr-un webhook, pe care platforma îl gestionează pentru tine — nu îl indici niciodată manual. npx tgcloud webhook arată starea curentă: URL-ul, lista allowed_updates, câte actualizări sunt în așteptare, ultima eroare de livrare (dacă există) și dacă este sincronizat cu handlerele tale desfășurate.
„Sincronizat” înseamnă că webhook-ul indică platforma și lista sa allowed_updates se potrivește cu handlerele pe care le-ai desfășurat — deci Telegram livrează exact tipurile de actualizare pe care le gestionezi și nimic altceva. Desfășurarea unui nou handler (sau eliminarea unuia) poate lăsa webhook-ul nesincronizat până când este reîmprospătat; npx tgcloud status semnalează și acest lucru.
npx tgcloud webhook sync îl repară — indică webhook-ul către platformă și reconstruiește allowed_updates din handlerele tale desfășurate. Adaugă --drop-pending pentru a elimina actualizările pe care Telegram le-a pus deja în coadă înainte de sincronizare (altfel sunt livrate odată ce webhook-ul este din nou sănătos).
completion
Notă: completarea prin tab funcționează doar când un
tgcloudsimplu este pePATH-ul tău — deci instalează-l global (npm install -g @tgcloud/cli) sau pune binarul pePATH. Nu se poate conecta lanpx.
tgcloud completion <bash|zsh|fish>
Tipărește un script de completare shell la stdout. Activează-l o dată, apoi <Tab> completează comenzi, flag-uri, directoare de module, tipurile de actualizare de handler pe care nu le ai încă și modulele tale locale rulabile.
# bash — are nevoie de pachetul bash-completion:
echo 'eval "$(tgcloud completion bash)"' >> ~/.bashrc
# zsh — asigură-te că `autoload -U compinit && compinit` rulează în ~/.zshrc:
echo 'eval "$(tgcloud completion zsh)"' >> ~/.zshrc
# fish:
tgcloud completion fish > ~/.config/fish/completions/tgcloud.fish
Repornește shell-ul (sau re-încarcă fișierul) ulterior. Rularea tgcloud completion fără shell tipărește din nou aceste instrucțiuni.
upgrade
npx tgcloud upgrade [--dry-run]
Migrare unică pentru proiectele create înainte de CLI 0.2.0, care păstrau schema.js, lib/ și handlers/ la rădăcina proiectului. Le mută în tgcloud/. Legătura ta cu botul este păstrată și nimic nu trebuie desfășurat din nou. --dry-run tipărește mutările planificate fără a atinge nimic. Alte comenzi nu rulează pe vechiul layout până când nu faci upgrade.
Sincronizarea
Fiecare proiect are o revizie care crește monoton în cloud, incrementată la fiecare desfășurare. CLI-ul își amintește revizia cu care s-a sincronizat ultima dată și o trimite la fiecare push. Dacă cloud-ul a avansat — deoarece o altă mașină sau un coechipier a desfășurat — push-ul este respins în loc să suprascrie silențios munca lor, iar CLI-ul oferă trei căi de urmat:
npx tgcloud fetch # vezi ce s-a schimbat în cloud, apoi reverifică
npx tgcloud pull # adu ultima versiune în fișierele tale de lucru
npx tgcloud push --force # suprascrie starea cloud (periculos)
Această verificare de concurență optimistă este motivul pentru care poți partaja un bot în cadrul unei echipe fără un proces de desfășurare blocat. Comenzile ies cu cod non-zero la eșec — o desfășurare respinsă, o migrare eșuată, o eroare de autentificare, un modul care a dat eroare în timpul run — deci pot fi compuse curat în scripturi și pipeline-uri CI.