Telegram Serverless ermöglicht es Ihnen, Backend-Code für Ihren Bot und Ihre Mini-App direkt auf der Infrastruktur von Telegram auszuführen – keine Server müssen bereitgestellt, keine Container am Laufen gehalten und keine Skalierung bedacht werden. Sie schreiben einfache JavaScript-Module, stellen sie mit einem einzigen Befehl bereit, und Telegram führt sie in einer schnellen, isolierten V8-Sandbox aus, die sich direkt neben der Bot-API und einer integrierten Datenbank befindet.

Wenn Sie jemals einen Bot mit einem VPS, einer Cloud-Funktion oder einem Hosting-Panel verbunden haben, nur um auf einen /start zu antworten, ist dies der Teil, den Sie nicht mehr tun müssen.

Warum serverlos

Ein Telegram-Bot ist im Grunde ein Programm, das auf Updates reagiert. Traditionell mussten Sie dieses Programm irgendwo hosten, das immer erreichbar, erreichbar und sicher war – und es dann auch so halten. Telegram Serverless entfernt diese Schicht vollständig:

  • Keine Infrastruktur. Es gibt keine Maschine, die gemietet, gepatcht oder überwacht werden muss. Ihr Code wird nach Bedarf ausgeführt und skaliert automatisch mit Ihrem Bot.
  • Anmeldedaten inklusive. Die Telegram Bot API, eine SQLite-gestützte Datenbank und ausgehende HTTP-Anfragen sind für jedes Modul sofort verfügbar – nichts muss installiert, keine Anmeldedaten müssen verdrahtet werden.
  • Schnelle, isolierte Ausführung. Jeder Aufruf läuft in einem leichtgewichtigen V8-Isolat, nahe den eigenen Systemen von Telegram, sodass Aufrufe an die Bot-API und Ihre Datenbank schnell und zuverlässig sind.
  • Ein echtes Entwickler-Workflow. Ein Projekt lebt in einem Ordner auf Ihrem Rechner unter Versionskontrolle. Sie bearbeiten Dateien, sehen genau, was sich geändert hat, stellen atomar bereit und rollen Ihr Datenbankschema mit geprüften Migrationen nach vorne – so, wie Sie es bereits mit allem anderen tun.

Das mentale Modell: So funktioniert das Projekt

Sie arbeiten an drei Orten, und diese bilden saubere Entsprechungen zueinander:

Wo Was befindet sich dort
Ihr Projektordner JavaScript-Module – Schema, gemeinsam genutzter Code, Update-Handler, Mini-App-Endpunkte – und, falls vorhanden, das Frontend der Mini-App
Die Cloud Die bereitgestellte Kopie dieser Module, die Datenbank Ihres Bots und die Dateien der Mini-App
Die tgcloud-CLI Die Brücke – sie zeigt Ihnen Unterschiede und synchronisiert sie

Sie SSHen sich nie irgendwo ein. Sie bearbeiten Dateien lokal, führen npx tgcloud push aus, und die Plattform kümmert sich um den Rest. Der Traffic Ihres Bots wird von den bereitgestellten Modulen verarbeitet; Ihre Datenbank bleibt zwischen den Aufrufen bestehen.

Ein Projekt besteht aus nur vier Arten von Code, die alle innerhalb eines tgcloud/-Ordners liegen:

tgcloud/
├─ endpoints/  # Funktionen, die Ihre Mini-App aufruft
├─ handlers/   # Funktionen, die auf Telegram-Update-Typen reagieren
├─ lib/        # gemeinsam genutzter Code, den Sie von überall importieren
└─ schema.js   # Ihre Datenbanktabellen

Wenn ein Update eintrifft – eine Nachricht, ein Tastendruck, eine Inline-Abfrage –, leitet Telegram es an den passenden Handler (handlers/message.js, handlers/callback_query.js, …) weiter und ruft dessen Standardexport auf. Diese Funktion spricht über das SDK mit der Bot-API und der Datenbank und gibt zurück. Das ist der gesamte Loop. Ein Update ohne passenden Handler wird einfach ignoriert, sodass Sie nur die Handler hinzufügen, die Sie benötigen.

Eine Mini-App ist optional. Wenn Ihr Bot eine hat, befindet sich ihr Frontend im selben Projekt, neben tgcloud/. Serverless hostet die Build-Ausgabe und führt die Endpunkte aus, die sie aufruft. Siehe Mini-App Frontend .

Kurze Demo

Hier ist eine vollständige, funktionierende Demo-Bot. Er antwortet auf jede Nachricht und merkt sich, wie viele er von jedem Chat gesehen hat.

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;

  // Fügt den Zähler ein oder inkrementiert ihn, wenn dieser Chat bereits einen hat – und holt die
  // resultierende Zeile im selben Statement über .returning() zurück.
  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: `Hallo! Ich habe ${row.seen} Nachricht(en) von dir gesehen.`,
  });
}

Stellen Sie es bereit:

npx tgcloud push       # lädt die Module hoch
npx tgcloud migrate    # erstellt die `counters`-Tabelle

Das ist ein Live-Bot mit persistentem Zustand und ohne Server. Alles darin – api, db, die table()-DSL – wird in den folgenden Abschnitten beschrieben.

Serverless ist ein allgemeines Backend für Telegram-Bots und Mini-Apps, kein Vorlagen-System für eine bestimmte Art von App. Es ist ideal für:

  • Konversationelle KI-Bots, die benutzerspezifischen Zustand in einer Datenbank speichern müssen.
  • Mini-App-Backends, die Benutzerdaten speichern und dynamische Inhalte bereitstellen.
  • Spiele und Tools – einschließlich Bestenlisten, Quizfragen und mehr.
  • Automatisierungen und Integrationen, die externe HTTP-APIs aufrufen und Ergebnisse in Chats pushen.

Erste Schritte

Diese Anleitung führt Sie von einem leeren Ordner zu einem Live-Bot, der auf Nachrichten antwortet und Daten speichert. Sie setzt voraus, dass Sie Node.js 18 oder neuer installiert haben und einen mit @BotFather registrierten Bot besitzen. Am Ende werden Sie jeden Befehl verwendet haben, den Sie täglich benötigen: push, migrate, run und status.

Schalten Sie Serverless zuerst ein. Öffnen Sie in @BotFather Ihren Bot → Serverless und schalten Sie ihn ein. Das aktiviert die Funktion für diesen Bot und schaltet seinen CLI-Zugriffstoken, Handler, die Bibliothek und die Datenbank frei.

1. Ein Projekt erstellen

Der schnellste Weg zum Start ist der Projekt-Ersteller, der ein Projektgerüst erstellt und die CLI darin installiert:

npm create @tgcloud/bot example_bot
cd example_bot

Das Argument ist der Zielordner: übergeben Sie . zum Erstellen im aktuellen Ordner oder einen beliebigen Pfad. Es funktioniert auch in einem bestehenden Ordner und überschreibt niemals Dateien, die Sie bereits haben.

Dies gibt Ihnen ein fertig zu bearbeitendes Projekt:

example_bot/
├─ tgcloud/             # alles, was die Plattform ausführt
│  ├─ handlers/
│  │  └─ message.js     # ein Starter-Nachrichten-Handler (gibt Text zurück)
│  ├─ lib/              # Ihre gemeinsam genutzten Module kommen hierher (leer zu Beginn)
│  └─ schema.js         # Ihre Datenbanktabellen
├─ docs/
│  └─ tgcloud-sdk.md    # SDK-Referenz (für Sie und Ihre KI-Tools)
├─ AGENTS.md            # Orientierung für KI-Coding-Assistenten
├─ package.json
└─ tgcloud.jsonc        # Projektkonfiguration

Die generierten Dateien sind selbsterklärend – jede enthält kommentierte Beispiele für das, was Sie als Nächstes tun können.

Die CLI wird als lokale Entwicklungsabhängigkeit im Projekt installiert, sodass Sie sie mit npx tgcloud <command> ausführen (npx findet die Kopie in den node_modules Ihres Projekts) oder über die npm run-Verknüpfungen, die der Generator zu package.json hinzufügt (npm run deploy, npm run status). Standardmäßig gibt es kein globales tgcloud in Ihrem PATH.

Sie können es auch global installieren – npm install -g @tgcloud/cli –, wenn Sie lieber ein nacktes tgcloud von überall aus tippen möchten. Das ist praktisch, um tgcloud init in jedem leeren Ordner auszuführen, und es ist das, was die Shell-Tab-Vervollständigung benötigt. In beiden Fällen erhalten Sie dasselbe Projekt.

Erstellen Sie eine Mini-App? Starten Sie stattdessen mit ihrem Frontend. Erstellen Sie sie mit Ihrem üblichen Werkzeug und führen Sie dann den Projekt-Ersteller in diesem Ordner aus. Siehe Mini-App Frontend .

2. Ihren Bot verknüpfen

Jedes Projekt ist an einen einzigen Bot gebunden. Verknüpfen Sie sie mit login, was Sie nach Ihrem CLI-Zugriffstoken fragt ( @BotFather → Ihr Bot → Serverless → CLI Access → Access token – ein separater Token von Ihrem Bot API Token ) und ihn lokal speichert:

npx tgcloud login

Der Token hat das Format app<id>:<secret>. Die CLI speichert ihn in .tgcloud/, das ignoriert wird, und gibt niemals den geheimen Teil aus. Login ist das einzige Mal, dass Sie danach gefragt werden – siehe Authentifizierung für die Auflösung von Tokens in CI.

3. Umsehen

Zwei Befehle zeigen Ihnen jederzeit, wo die Dinge stehen, beide vollständig offline:

npx tgcloud status     # Was hat sich lokal im Vergleich zur bereitgestellten Kopie geändert
npx tgcloud diff       # Die zeilenweisen Änderungen

Direkt nach init ist alles neu und noch nichts bereitgestellt. status zeigt die Starter-Dateien, die hochgeladen werden müssen.

4. Bereitstellen

Senden Sie Ihre Module in die Cloud:

npx tgcloud push

push lädt jedes geänderte Modul in einem atomaren Stapel hoch und aktualisiert Ihre lokale Aufzeichnung dessen, was die Cloud nun enthält. Ihr Bot ist live: Öffnen Sie ihn in Telegram und senden Sie ihm eine Nachricht – der Starter-Handler gibt sie zurück.

Bereitstellungen berühren niemals Ihre Datenbank. Das Pushen von Code und das Ändern Ihres Datenbankschemas sind bewusst getrennte Schritte, sodass eine Code-Bereitstellung niemals mit einer Datenmigration überraschen kann. Das ist es, was der nächste Schritt ist.

5. Eine Datenbanktabelle hinzufügen

Lassen Sie den Bot etwas speichern. Öffnen Sie tgcloud/schema.js und deklarieren Sie eine Tabelle:

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())`) ,
});

Stellen Sie das Schema bereit und wenden Sie es dann auf die Datenbank an:

npx tgcloud push       # lädt das neue Schema hoch
npx tgcloud migrate    # erstellt die `messages`-Tabelle

push meldet, dass das Schema nicht synchron ist und zeigt die ausstehende Änderung an, wendet aber nichts an. migrate führt Sie durch die Änderung und erstellt auf Ihre Bestätigung hin die Tabelle. Dieses Zwei-Schritt-Modell – und was mit riskanteren Änderungen wie dem Löschen geschieht – wird in Migrationen behandelt.

6. Daten speichern und lesen

Verwenden Sie die Tabelle nun aus Ihrem Handler. Bearbeiten Sie tgcloud/handlers/message.js:

import { api, db } from 'sdk';
import { eq } from 'sdk/db';
import { messages } from '../schema.js';

export default async function (message) {
  // Speichert diese Nachricht.
  await db.insert(messages)
    .values({ chatId: message.chat.id, text: message.text })
    .run();

  // Zählt, wie viele wir für diesen Chat gespeichert haben.
  const count = await db.$count(messages, eq(messages.chatId, message.chat.id));

  await api.sendMessage({
    chat_id: message.chat.id,
    text: `Gespeichert. Das sind ${count} Nachricht(en) aus diesem Chat bisher.`,
  });
}

Stellen Sie den aktualisierten Handler mit npx tgcloud push bereit und senden Sie dann einige Nachrichten an Ihren Bot und beobachten Sie, wie die Zählung steigt. Die Datenbank bleibt zwischen den Aufrufen erhalten – das ist das Gedächtnis Ihres Bots.

7. Ohne Bereitstellung testen

Sie müssen nichts bereitstellen, um eine Änderung auszuprobieren. npx tgcloud run führt einen Handler auf der Plattform mit Ihren lokalen Dateien aus, ohne diese zu veröffentlichen:

npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hallo" }'

Das Argument ist die Nutzlast, die Ihr Handler erhält – für handlers/message eine Nachricht – geschrieben in JSON5 (damit Sie Schlüssel weglassen können) . Der Befehl gibt alles aus, was der Handler mit console.* protokolliert hat, den Rückgabewert und wie lange es gedauert hat. Das ist die engste Schleife, um Logik zu iterieren – keine Bereitstellung, kein Warten auf eine echte Nachricht.

8. Synchron bleiben

Während Sie arbeiten, halten eine Handvoll Befehle Ihr lokales Projekt und die Cloud synchron: npx tgcloud status zeigt, was sich geändert hat, npx tgcloud push stellt bereit, npx tgcloud pull bringt Ihr lokales Projekt mit der Cloud in Einklang, npx tgcloud fetch prüft, was bereitgestellt ist, ohne Ihre Dateien zu berühren, und npx tgcloud reset verwirft lokale Änderungen.

Wenn zwei Personen (oder zwei Maschinen) dasselbe Bot bereitstellen, erkennt die Plattform den Konflikt und push stoppt, damit Sie zuerst pull können – Sie können niemals stillschweigend die Arbeit eines anderen überschreiben. Siehe Synchron bleiben .

Mit KI aufbauen

Bevorzugen Sie den Aufbau mit einem KI-Assistenten – oder ist der einzige Programmierer in Ihrem Team eine KI? Sie können trotzdem einen Bot ausliefern. Wir haben einen ersten Schritt unternommen, um einem KI-Agenten das Gefühl zu geben, sich in einem Projekt zu Hause zu fühlen: Jeder neue wird mit einer AGENTS.md und einer Referenz docs/tgcloud-sdk.md erstellt, die Agenten-Codierungswerkzeuge automatisch lesen.

Zusammen mit einer kleinen, in sich geschlossenen Laufzeit – ein SDK, keine npm-Pakete zum Jonglieren – gibt das dem Assistenten einen schnellen Start bei den Konventionen, die generische Code-Generatoren hier oft übersehen: relative Importe mit .js, keine Fremdschlüssel, jeder db-Aufruf ist asynchron, ein Handler pro Update-Typ und der Zwei-Schritt-push/migrate-Flow.

Versuchen Sie es:

npm create @tgcloud/bot my-bot
cd my-bot
opencode            # oder Claude Code, Cursor, … – jeder Agent, der AGENTS.md liest

Fragen Sie dann einfach in Klartext:

Schreibe einen Bot, der sich die To-Do-Listen jeder Person merkt – füge ein Element hinzu, wenn sie Text sendet, und zeige die gesamte Liste an, wenn sie /list sendet.

Der Assistent bearbeitet schema.js und Ihre Handler für Sie; Sie überprüfen, testen eine Änderung sofort mit npx tgcloud run, und gehen dann mit npx tgcloud push und npx tgcloud migrate live. AGENTS.md ist Teil Ihres Projekts – bearbeiten Sie es, wenn der Bot wächst, damit die Anleitungen korrekt bleiben.

Unterwegs mit BotFather

Nur noch auf dem Handy? Das gesamte Projekt lebt auch in @BotFather – öffnen Sie Ihren Bot → Serverless und Sie erhalten alles, was die CLI verwaltet, auf einem Touchscreen:

  • Handler – Erstellen, bearbeiten und testen Sie Update-Handler; BotFather hält den Webhook mit den von Ihnen vorhandenen Handlern synchron (das gleiche Synchron / Nicht synchron, das die CLI meldet).
  • Endpunkte – Die Funktionen, die Ihre Mini-App aufruft.
  • Bibliothek – Ihre gemeinsam genutzten lib/-Module.
  • Datenbank – Bearbeiten Sie schema.js in der gleichen Drizzle-ähnlichen Syntax, überprüfen Sie ausstehende Änderungen und wenden Sie sie an; Speichern stellt bereit.
  • Mini-App-Dateien – Das Frontend, das Ihre Benutzer in Telegram öffnen.
  • CLI-Zugriff – Holen Sie sich den CLI-Zugriffstoken hier, wenn Sie wieder am Keyboard sind.

Es ist dasselbe Cloud-Projekt, sodass Sie einen Handler auf Ihrem Handy starten und ihn später mit npx tgcloud pull auf Ihren Laptop holen können – nichts ist an einen einzigen Client gebunden. Das Ausführen eines Handlers zeigt sogar seine Konsolenausgabe direkt im Chat an, genau wie npx tgcloud run .

Projekte und Module

Ein serverloses Projekt ist ein gewöhnlicher Ordner unter Versionskontrolle. Alles, was die Plattform ausführt, befindet sich in einem Ordner, tgcloud/; der Rest des Projekts gehört Ihnen – ein Mini-App-Frontend, Konfiguration, Dokumentation. Es gibt keinen Build-Schritt für den Bot, keine node_modules zur Laufzeit und keinen Server-Einstiegspunkt.

Aufbau eines Projekts

example_bot/
├─ tgcloud/             # der bereitgestellte Teil – nichts anderes
│  ├─ handlers/         # Update-Handler – flach, nur eine Ebene
│  │  ├─ message.js
│  │  └─ callback_query.js
│  ├─ endpoints/        # Funktionen, die Ihre Mini-App aufruft – flach, nur eine Ebene
│  │  └─ getProfile.js
│  ├─ lib/              # gemeinsam genutzte Module; Unterverzeichnisse erlaubt
│  │  ├─ reply.js
│  │  └─ internal/util.js
│  └─ schema.js         # Datenbank-Schema – eine Datei
└─ .tgcloud/            # CLI-Status, git-ignoriert

Nur .js-Dateien innerhalb von tgcloud/ – schema.js, lib/, handlers/, endpoints/ – werden bereitgestellt. Alles andere – Markdown, Konfigurationsdateien, der .tgcloud/-Ordner, alles außerhalb von tgcloud/ – bleibt auf Ihrem Rechner. Modulnamen sind relativ zu tgcloud/. Die Datei tgcloud/handlers/message.js ist das Modul handlers/message, und das ist der Name, den npx tgcloud run verwendet.

Upgrade von einem älteren Projekt? Projekte, die vor CLI 0.2.0 erstellt wurden, behielten schema.js, lib/ und handlers/ im Projektstammverzeichnis. Führen Sie npx tgcloud upgrade einmal aus, um sie in tgcloud/ zu verschieben.

schema.js – Ihre Datenbank. Sie deklariert Tabellen als benannte Exporte unter Verwendung der Schema-DSL und liegt als einzelne Datei in tgcloud/. Sie wird wie jedes andere Modul bereitgestellt, aber die Bereitstellung ändert niemals die Datenbank – Schemaänderungen werden separat mit npx tgcloud migrate angewendet. Siehe Die Datenbank .

lib/ – gemeinsam genutzter Code, alles, was Sie über Handler hinweg wiederverwenden möchten: reine Hilfsfunktionen, Datenbankzugriffsschichten, Formatierung, Integrationen mit externen Diensten. lib/ ist das einzige Verzeichnis, das Unterverzeichnisse enthalten darf (lib/internal/util.js, lib/payments/stripe.js), sodass Sie eine größere Codebasis nach Belieben organisieren können. Module in lib/ werden von der Plattform nie direkt aufgerufen; sie existieren, um von Handlern und untereinander importiert zu werden.

handlers/ – die Einstiegspunkte Ihres Bots. Jede Datei entspricht einem Telegram-Update-Typ, und die Plattform leitet jedes eingehende Update an den passenden Handler weiter:

Datei Behandelt
handlers/message.js Neu eingehende Nachrichten
handlers/inline_query.js Neu eingehende Inline -Abfragen
handlers/callback_query.js Neu eingehende Callback-Abfragen
… Jeder andere Bot API Update-Typ

handlers/ ist flach – keine Unterverzeichnisse. Der export default eines Handlers ist die Funktion, die die Plattform aufruft (siehe Handler ).

Ein Update-Typ wird nur dann behandelt, wenn seine Handlerdatei existiert und nicht leer ist. Wenn es kein handlers/<type>.js gibt – oder die Datei leer ist –, werden Updates dieses Typs ignoriert, und die Plattform führt nichts für sie aus. Bewahren Sie also nur die Handler auf, die Sie tatsächlich benötigen: Jeder, den Sie hinzufügen, ist ein weiterer Update-Typ, zu dem Ihr Bot aufwacht, um ihn zu verarbeiten, und wenn Sie die restlichen weglassen, startet Telegram Ihren Code nicht für Updates, die Sie sowieso verwerfen würden.

Um einen neuen Handler zu generieren, führen Sie npx tgcloud add handlers/<type> aus.

endpoints/ – die Serverseite Ihrer Mini-App. Jede Datei ist eine Funktion, die die Mini-App nach Namen aufruft, sodass endpoints/getProfile.js auf POST /api/getProfile antwortet. Die Plattform prüft, ob der Aufruf von einem Telegram-Benutzer innerhalb Ihrer Mini-App stammt, bevor Ihr Code ausgeführt wird, und übergibt Ihnen diesen Benutzer. endpoints/ ist flach, wie handlers/. Siehe Endpunkte .

.tgcloud/ – maschinenlokaler Status, der vollständig von der CLI verwaltet wird, einschließlich Ihrer gespeicherten Anmeldedaten. Er wird von Git ignoriert, und Sie sollten ihn nie von Hand lesen oder schreiben – verwenden Sie stattdessen die CLI-Befehle.

Das Modulsystem

Zur Laufzeit kann ein Modul genau zwei Dinge sehen: das Plattform-SDK und die anderen Module in Ihrem Projekt. Es gibt keine npm-Pakete, kein Dateisystem und kein Netzwerk außer über den fetch des SDK.

Importieren Sie Ihre eigenen Module so, wie Sie es in jedem JavaScript-Projekt tun würden – per relativem Pfad, mit der .js-Erweiterung – und das SDK nach seinem Namen:

// tgcloud/handlers/message.js
import { users } from '../schema.js';              // das Schema-Modul
import { addItem } from '../lib/cart.js';          // ein Lib-Modul
import { format } from '../lib/internal/fmt.js';  // verschachteltes Lib-Modul
import { db, api, fetch } from 'sdk';              // das Plattform-SDK
// tgcloud/lib/cart.js
import { format } from './internal/fmt.js';        // ein Nachbar
import { users } from '../schema.js';

Ein relativer Import muss mit .js enden und darf nicht außerhalb von tgcloud/ führen:

import { users } from '../schema';       // .js hinzufügen
import config from '../../config.js';    // außerhalb von tgcloud/
import { z } from 'zod';                 // keine npm-Pakete

Jedes Modul hat auch einen Namen – seinen Pfad innerhalb von tgcloud/ ohne die Erweiterung, z. B. schema, lib/cart oder handlers/message. Dieser Name ist das, was npx tgcloud run und die Ausgabe der CLI verwenden, und ein Modul kann auch unter diesem Namen importiert werden (from 'lib/cart', keine Erweiterung). Bevorzugen Sie relative Importe in Ihrem Code. Sie sind das, was Ihr Editor versteht – Sprung zur Definition, Autovervollständigung, Umbenennung.

Was zur Laufzeit verfügbar ist, sind genau zwei Dinge: sdk und seine Untermodule (sdk/db, sdk/api, sdk/fetch) – die gesamte Plattformoberfläche, siehe Das SDK – und Ihre eigenen Module in tgcloud/. Das ist die vollständige Liste. Wenn Ihr Code etwas anderes importiert, wird es nicht aufgelöst. Diese Einschränkung hält die Module schnell zu laden und sicher auszuführen.

Handler

Ein Handler ist ein Modul in handlers/, dessen Standardexport die Plattform aufruft, wenn ein passendes Update eintrifft.

// handlers/message.js
import { api } from 'sdk';

export default async function (message) {
  await api.sendMessage({
    chat_id: message.chat.id,
    text: `Du hast gesagt: ${message.text ?? '(kein Text)'}`,
  });
}

Ein Handler erhält die Nutzlast des Updates als Argument – die Plattform entpackt das Telegram Update für Sie. handlers/message.js erhält die Nachricht (d.h. update.message); handlers/callback_query.js erhält die CallbackQuery ; und so weiter. Das zweite Argument des Handlers ist ein pro-Aufruf-Kontextobjekt, ctx. Es trägt das rohe Update als ctx.update – greifen Sie darauf zu, wenn Sie etwas außerhalb der Nutzlast benötigen, wie z.B. update_id.

Ein Handler kann async sein (ist es normalerweise) und einen Wert zurückgeben. Er erreicht die Bot-API, die Datenbank und ausgehende HTTP-Anfragen über das SDK.

Sie benötigen kein echtes Update, um einen Handler zu testen. npx tgcloud run führt ihn auf der Plattform mit der von Ihnen bereitgestellten Nutzlast und Ihrem aktuellen lokalen Code aus:

npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hi" }'

Das Argument ist die Nutzlast – dasselbe Objekt, das Ihr Handler erhält – in JSON5. Um den ctx (sein zweites Argument) des Handlers bereitzustellen, fügen Sie --ctx hinzu, z.B. --ctx '{ update: { update_id: 1 } }'. Dies läuft gegen Ihre lokalen Dateien, sodass Sie Änderungen ausprobieren können, bevor Sie sie bereitstellen. Siehe run .

Endpunkte

Ein Endpunkt ist ein Modul in endpoints/, das Ihre Mini-App aufruft – der Ort für alles, was das Frontend nicht allein tun kann, wie z. B. das Lesen und Schreiben der Datenbank, das Aufrufen der Bot-API oder die Kommunikation mit externen Diensten mit Ihren Geheimnissen.

// 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 nicht gefunden', { userId: user.id });
  return profile;
}

Das erste Argument, input, ist das JSON-Objekt, das die Mini-App gesendet hat. Das zweite, ctx, enthält ctx.initData – die Init-Daten der Mini-App (user, chat, start_param, auth_date, …) , die bereits von der Plattform verifiziert wurden. Sie haben die gleiche Form wie Telegram.WebApp.initDataUnsafe auf dem Client, aber hier können Sie ihr vertrauen. Ein Aufruf ohne gültige Init-Daten wird mit 401 abgelehnt, bevor Ihr Code ausgeführt wird, sodass ctx.initData.user wirklich die Person ist, die Ihre Mini-App geöffnet hat. Endpunkte können nur auf diese Weise aufgerufen werden – von der Mini-App Ihres Bots, durch einen Telegram-Benutzer –, sodass es keine öffentliche API zu schützen und keine Anmeldung zu erstellen gibt.

Was auch immer der Endpunkt zurückgibt, wird als JSON zurückgegeben. Um einen Aufruf abzulehnen, throw new EndpointError(description, parameters?). Die Mini-App erhält die Beschreibung und das parameters-Objekt, wie sie sind. Wenn Sie auf die Art des Fehlers im Frontend verzweigen müssen, legen Sie einen Code in parameters ab – { code: 'OUT_OF_STOCK' }. Jede andere Ausnahme wird als generischer interner Fehler gemeldet, und die eigentliche Nachricht geht nur an Ihre Protokolle, sodass interne Fehler nicht nach außen an den Client dringen.

Einen Endpunkt von der Mini-App aufrufen

Die Mini-App ruft einen Endpunkt mit seinem Namen über Telegram.WebApp.Serverless.call aus dem offiziellen Mini Apps SDK , telegram-web-app.js – dem Skript, das Ihre Mini-App bereits enthält – auf. Es sendet die Init-Daten für Sie und ruft mit einem Fehler oder dem Ergebnis des Endpunkts zurück:

Telegram.WebApp.Serverless.call('getProfile', { lang: 'en' }, function (err, profile) {
  if (err) {
    // err.message – die Beschreibung; err.parameters – was Ihr EndpointError trug
    if (err.type === 'ENDPOINT_ERROR') showMessage(err.message);
    return;
  }
  render(profile);
});

Die Eingabe ist optional und muss ein normales Objekt sein. Ohne sie ist der Aufruf Serverless.call('getProfile', callback).

err.type ist 'ENDPOINT_ERROR', wenn Ihr Endpunkt einen EndpointError ausgelöst hat. Jeder andere Wert bedeutet, dass die Plattform den Aufruf abgelehnt hat oder der Endpunkt unerwartet fehlgeschlagen ist, z.B. 'UNAUTHORIZED', wenn die Init-Daten fehlen oder ungültig sind.

Endpunktnamen sind wie JavaScript-Bezeichner – Buchstaben, Ziffern und _, beginnend nicht mit einer Ziffer (getProfile, place_order). Generieren Sie einen mit npx tgcloud add endpoints/<name> und versuchen Sie ihn ohne die Mini-App mit run aus, indem Sie die Init-Daten selbst übergeben:

npx tgcloud run endpoints/getProfile '{}' --ctx '{ initData: { user: { id: 1, first_name: "Ann" } } }'

Was bereitgestellt wird

Wenn Sie npx tgcloud push ausführen, sammelt die CLI jede .js-Datei unter schema.js, lib/, handlers/ und endpoints/ und sendet diesen exakten Satz als Modulraum Ihres Projekts – zusammen mit dem Build-Ordner der Mini-App, wenn tgcloud.jsonc einen benennt (siehe unten). Alles, was in der Cloud vorhanden, aber nicht in Ihrem Projekt ist, wird entfernt, sodass der bereitgestellte Zustand immer Ihrem Ordner entspricht – auch Löschungen. Nichts außerhalb von tgcloud/ wird als Code bereitgestellt – der Rest des Projektstamms gehört Ihnen. Innerhalb von tgcloud/ wird eine .js-Datei, die sich nicht an diesen Orten befindet (tgcloud/util.js, tgcloud/helpers/x.js), markiert, sodass sie nicht stillschweigend nicht bereitgestellt wird. Markdown, Punktdateien und .tgcloud/ werden niemals bereitgestellt.

Mini-App Frontend

Der Projektstamm gehört Ihnen und ist der natürliche Ort für das Frontend Ihrer Mini-App – erstellt mit React, Vue, Svelte oder einfachem HTML, von Vite oder jedem anderen Bundler. Serverless hostet seine Build-Ausgabe neben Ihrem Bot. Ein push stellt den Code und die statischen Dateien zusammen bereit, und die App wird unter https://app<app_id>.tgcloud.ai/ bereitgestellt ( push gibt die genaue Adresse aus – verwenden Sie sie als URL Ihrer Mini-App in @BotFather ).

Missbrauch melden. Mini-Apps auf tgcloud.ai werden von unabhängigen Entwicklern veröffentlicht. Um illegale Inhalte oder Missbrauch auf einer app<app_id>.tgcloud.ai-Subdomain zu melden, senden Sie eine E-Mail an [email protected] .

Um zu beginnen, erstellen Sie zuerst das Frontend mit dem Werkzeug Ihrer Wahl und fügen Sie dann Serverless darauf auf:

npm create vite@latest example_app   # oder create-vue, …
cd example_app
npm create @tgcloud/bot .

Der Projekt-Ersteller überschreibt Ihre Dateien niemals. Er fügt den tgcloud/-Ordner neben dem Frontend hinzu, erkennt den Build-Ordner und fügt ein npm run deploy-Skript hinzu, das die App erstellt und zusammen mit dem Bot bereitstellt.

Was gehostet wird, ist in tgcloud.jsonc festgelegt, der Konfigurationsdatei des Projekts im Projektstamm:

{
  "$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 }]
  }
}

Nur "source" ist erforderlich. Es ist der Build-Ordner, relativ zum Projektstamm. Die anderen Schlüssel von "static" konfigurieren, wie er bereitgestellt wird (unten). init erstellt tgcloud.jsonc für Sie. Wenn es das Frontend erkennt, füllt es auch "source" aus:

Frontend init setzt "source" auf
Vite – React, Vue, Svelte, Solid, … dist
Next.js, Nuxt, SvelteKit, React Router, TanStack Start, Angular nichts – konfigurieren Sie zuerst einen statischen Export und setzen Sie dann "source" auf dessen Ordner selbst

Mit jeder anderen Einrichtung setzen Sie "source" auf Ihren Build-Ordner selbst.

Die Zeile $schema gibt Ihrem Editor Autovervollständigung und Validierung für jeden Schlüssel. Die Datei ist JSON mit Kommentaren und erlaubten abschließenden Kommas. Wenn ein anderes Werkzeug sie als reines JSON lesen muss, nennen Sie sie stattdessen tgcloud.json – dann muss sie striktes JSON sein.

Vor dem Pushen bauen. push lädt den Ordner exakt so hoch, wie er auf der Festplatte liegt – außer Dateien und Ordnern, deren Namen mit einem Punkt beginnen und die übersprungen werden –, und führt niemals Ihren Build aus, sodass ein einfaches npx tgcloud push nach dem Bearbeiten des Frontends den vorherigen Build bereitstellen würde. Verwenden Sie npm run deploy (es führt Ihr build-Skript aus, dann tgcloud push), oder bauen Sie es zuerst selbst.

Die Bereitstellungsoptionen:

  • spa – liefert index.html für Pfade, die nicht existieren und keine Dateierweiterung haben, sodass ein Client-seitiger Router beim Neuladen funktioniert. Pfade mit einer Erweiterung erhalten immer noch 404.
  • immutable – Muster für inhaltsgehashte Dateien (Vites /assets/*), die Browser ein Jahr lang cachen dürfen. Verwenden Sie es nur für inhaltsgehashte Dateien. Jede andere hier übereinstimmende Datei würde ein Jahr lang in Browsern veraltet bleiben, und es gibt keine Möglichkeit, sie zurückzurufen.
  • headers – Antwort-Header für die Pfade, auf die eine Regel passt (ein exakter Pfad oder ein Präfix, das auf /* endet). Ein null-Wert entfernt einen Header.
  • redirects – Weiterleitungsregeln, bei denen die erste Übereinstimmung gewinnt. Eine vorhandene Datei schlägt immer eine Regel. status ist 301, es sei denn, Sie legen ihn fest.
  • "static": "dist" – eine Abkürzung für { "source": "dist" }, wenn Sie nichts weiter benötigen.
  • "static": false – entfernt die bereitgestellte Website beim nächsten Push. Das Löschen des Schlüssels verhindert stattdessen, dass dieses Projekt statische Dateien verwaltet, und was auch immer bereitgestellt ist, bleibt.

Die Website wird vom Stamm ihres eigenen Hosts bereitgestellt, daher ist die Standardbasis (/) Ihres Bundlers korrekt. Telegram Web öffnet Mini-Apps in einem iFrame, also fügen Sie keine X-Frame-Options oder eine restriktive frame-ancestors-Richtlinie hinzu.

Derselbe Host beantwortet Ihre Endpunkte unter /api/ – dort geht Telegram.WebApp.Serverless.call('getProfile', …) hin. Legen Sie keine eigenen Dateien unter /api/ im Build-Ordner ab.

Die Datenbank

Jeder Bot erhält seine eigene Datenbank – ein SQLite-gestütztes Speichersystem, das zwischen den Aufrufen erhalten bleibt und über db für jedes Modul verfügbar ist. Sie beschreiben Ihre Tabellen in schema.js mit einer kleinen, typisierten DSL; Sie lesen und schreiben sie mit einem flüssigen Query-Builder; und Sie entwickeln sie mit geprüften Migrationen weiter.

Kennen Sie Drizzle ORM ? Dann wissen Sie bereits, wie Sie mit der Datenbank hier sprechen. Die Schema-DSL und der Query-Builder folgen Drizzle – die Spalten-Builder, select().from().where(), die Operatoren, onConflictDoUpdate, .returning() und der sql-Tag verhalten sich alle so, wie Sie es erwarten, sodass das Lesen und Schreiben von Daten die vertraute API ist, die Sie bereits verwenden. Sie importieren es einfach aus sdk/db, und ein paar Plattform-spezifische Elemente (am bemerkenswertesten keine Fremdschlüssel ) werden dort darauf hingewiesen, wo sie auftreten.

Tabellen deklarieren

Tabellen sind benannte Exporte in schema.js. Das Aufrufen von table() erstellt zur Ladezeit eine Beschreibung (es berührt die Datenbank nicht); die Plattform entdeckt die exportierten Tabellen, wenn Sie schema.js bereitstellen und migriert die Datenbank entsprechend.

import { table, integer, text, boolean, json, index, sql } from 'sdk/db';

export const users = table('users', {
  id:      integer('id').primaryKey({ autoIncrement: true }) ,
  tgId:    integer('tg_id').unique() ,
  name:    text('name').notNull() ,
  lang:    text('lang').default('en') ,
  isAdmin: boolean('is_admin').default(false) ,
  prefs:   json('prefs') ,
  created: integer('created_at', { mode: 'timestamp' }).default(sql`(unixepoch())`) ,
}, (t) => ({
  createdIdx: index('idx_users_created').on(t.created) ,
}));

table(name, columns, extras?):

  • name ist der SQL-Tabellenname;
  • columns ist eine Map von JS-Eigenschaft → Spaltendefinition;
  • extras ist ein optionaler Callback (t) => ({ … }), bei dem t die Spalten exponiert (t.created ist ein Verweis auf diese Spalte) – deklarieren Sie hier Indizes und tabellenweite Einschränkungen.

Spaltentypen

Factory SQLite-Typ Hinweise
text() TEXT
integer() INTEGER
real() REAL Alias float()
numeric() NUMERIC
blob() BLOB liest/schreibt Uint8Array
boolean() INTEGER als 0/1 gespeichert, als true/false gelesen
json() TEXT automatisch JSON.stringify / JSON.parse

Der Spaltennamen-Argument ist optional – lassen Sie es weg, und der JS-Schlüssel wird verwendet. Die Option mode steuert, wie Werte zwischen SQLite und JavaScript konvertiert werden.

mode Gespeichert als JS-Wert
boolean INTEGER 0/1 boolean
json TEXT (JSON) beliebiges Objekt/Array
timestamp INTEGER (Unix-Sekunden) Date
timestamp_ms INTEGER (Unix-ms) Date
bytes BLOB Uint8Array

boolean() und json() sind Kurzformen für integer(name, { mode: 'boolean' }) und text(name, { mode: 'json' }).

Ein blob() liest und schreibt ein Uint8Array – die Laufzeit hat keinen Node Buffer (und ein Buffer ist eine Uint8Array-Unterklasse, daher ist dies der portable Basistyp). Der obige mode steuert nur, wie ein Wert kodiert wird, unabhängig vom Speichertyp der Spalte: also ist blob('col', { mode: 'json' }) das BLOB-Gegenstück zu json() – dieselbe JSON-Kodierung, gespeichert in einer BLOB-Spalte anstelle von TEXT.

TL;DR: blob() verwendet Uint8Array. mode steuert die Kodierung, nicht die Speicherung, sodass blob(..., { mode: 'json' }) JSON als BLOB speichert, während json() es als TEXT speichert.

Spaltenmodifikatoren

Spaltenmodifikatoren werden an eine Spalte angehängt.

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' })  // oder 'virtual'
text('email').deprecated('ersetzt durch login')   // markiert die Spalte zur Entfernung

.default() bei einer json()-Spalte kodiert den Wert für Sie; ein sql`…` default wird unverändert durchgereicht. .deprecated() ist terminal – siehe Migrationen .

Indizes und Einschränkungen

Indizes und Einschränkungen werden im extras-Callback deklariert, wo die Spalten im Geltungsbereich sind.

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})`) ,          // Ausdrucksindex
  active: index('idx_active').on(t.userId).where(sql`done = 0`) ,  // partieller Index
}));

Tabellenweite Modifikatoren werden nach table(...) angehängt: .strict(), .withoutRowid(), .deprecated('reason').

Keine Fremdschlüssel

Die Laufzeit wird mit PRAGMA foreign_keys ausgeschaltet. Ein deklarierter Fremdschlüssel wäre stillschweigend inert – keine Kaskaden, kein Waisenkindschutz – was schlechter ist als gar keiner, daher macht die DSL es unmöglich. Insbesondere .references() und tabellenweite foreignKey() werfen eine Ausnahme bei der Deklaration, und ein Schema, das sie verwendet, wird nicht bereitgestellt.

Sie sollten Beziehungen mit einfachen Spalten (userId: integer('user_id')) modellieren und die Integrität in Ihrem Anwendungscode erzwingen: Eltern vor Kindern einfügen, Kinder vor Eltern löschen, Fehler ordnungsgemäß behandeln und Waisen mit einem LEFT JOIN … WHERE parent.id IS NULL aufkehren, wenn Sie müssen.

Das Fehlen von Fremdschlüsseln ist eine bewusste Einschränkung, kein Versehen. Berücksichtigen Sie dies frühzeitig bei der Planung Ihres Bots.

Abfragen

db ist ein flüssiger Query-Builder. Jede Abfrage ist asynchron – die Terminalmethoden (.all(), .get(), .values(), .run()) geben Promises zurück, also immer await.

import { db } from 'sdk';
import { eq, and, desc, asc, count, sql } from 'sdk/db';
import { users, todos } from '../schema.js';

await db.select().from(todos).all();                          // alle Zeilen
await db.select().from(todos).where(eq(todos.id, 1)).get();   // erste Zeile oder null
await db.select().from(todos).values();                       // Zeilen als Wert-Arrays

await db.select().from(todos)
  .where(and(eq(todos.userId, uid) , eq(todos.done, false)))
  .orderBy(desc(todos.priority) , asc(todos.id))
  .limit(10).offset(20)
  .all();

// benutzerdefinierte 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();

// Zeilenanzahl – ein Helfer, kein Builder-Terminal:
await db.$count(todos);                        // alle Zeilen
await db.$count(todos, eq(todos.done, false)); // mit Filter
  • Verkettbar: .where(), .orderBy(), .limit(), .offset(), .groupBy(), .having(), .distinct().
  • Terminals: .all(), .get(), .values().
  • Zeilen mit db.$count(table, where?) zählen (oder count() in einer Projektion).

Einfügen, Aktualisieren, Löschen:

await db.insert(todos).values({ userId: 1, text: 'Milch kaufen' }).run();
await db.insert(todos).values([{ text: 'A' }, { text: 'B' }]).run();   // Stapel
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();

Beachten Sie, dass ein Stapel-Insert ein einziges Statement ist, daher ist er durch das Variablenlimit von SQLite begrenzt (Zeilen × Spalten); wenn Sie es überschreiten, schlägt der Insert fehl – teilen Sie ihn selbst auf.

Operatoren werden aus sdk/db importiert:

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) mit mehreren Argumenten ist dasselbe wie and(a, b). Das zweite Argument eines Vergleichs ist standardmäßig ein Wert, kann aber auch eine andere Spalte oder sql`…` sein – z.B. eq(a.x, b.y). Aggregierte Funktionen (count/sum/avg/min/max) sind SQL-Fragmente für eine .select({ … })-Projektion.

Rohes SQL – wenn der Builder nicht ausreicht, wechseln Sie zu rohem SQL. Der Modus wird per Methode gewählt: db.run für Schreibvorgänge, db.all für viele Zeilen, db.get für eine Zeile, db.values für Zeilen als positionelle Arrays.

await db.run('UPDATE todos SET done = 1 WHERE id = :id', { ':id': 5 });
// → { rowsAffected: 3, lastInsertRowid: 0, rows: [] }

await db.all(sql`SELECT * FROM todos WHERE done = ${false}`);
await db.get(sql`SELECT count(*) AS c FROM todos`);
await db.values(sql`SELECT id, text FROM todos`);   // → [[1, 'milch kaufen']]

Ein Schreibvorgang wird zu einem run-Ergebnis aufgelöst – rowsAffected, lastInsertRowid und rows, die eine RETURNING-Klausel enthalten, wenn das SQL eine hatte. Greifen Sie auf db.values zu, wenn eine Abfrage zwei Spalten mit demselben Namen projiziert (ein Join wie SELECT u.id, o.id): db.all schlüsselt Zeilen nach Spaltennamen auf, sodass die zweite id die erste überschreiben würde.

Der sql`…` -Tag verwandelt ${value} in einen gebundenen Parameter und ${table.column} in einen Bezeichner und fügt verschachtelte sql-Fragmente ein. Verwenden Sie sql.raw('…') für einen Literal ohne Parameter.

Rohe Abfragen sind nicht an eine Tabelle gebunden, daher werden ihre Zeilen ohne Moduskonvertierung zurückgegeben – ein boolescher Wert ist 0/1, eine JSON-Spalte ist eine Zeichenkette, ein Zeitstempel ist eine Zahl. Nur der tabellengebundene Builder konvertiert Werte.

Migrationen

Ihre Datenbank ändert sich, wenn Ihr Bot wächst. Die Plattform hält dies sicher, indem sie das Bereitstellen von Code von der Änderung von Daten trennt und jede Schemaänderung nach ihrer Risikoeinstufung klassifiziert.

Das Bereitstellen ändert die Datenbank niemals. Wenn Sie eine geänderte schema.js mit npx tgcloud push bereitstellen, zeichnet die Plattform das neue Schema auf und informiert Sie darüber, wie sich die Datenbank ändern würde – wendet aber nichts an:

npx tgcloud push       # schema.js bereitstellen; meldet ausstehende DB-Änderungen
npx tgcloud migrate    # sie überprüfen und anwenden

npx tgcloud migrate berechnet den Unterschied zwischen Ihrem Schema und der Live-Datenbank und führt Sie Schritt für Schritt durch, mit einem laufenden [N/M]-Zähler:

  • Eine kurze Zusammenfassung aller ausstehenden Änderungen.
  • Sichere Änderungen, die auf Ihre Bestätigung hin zusammen in einem Schritt angewendet werden.
  • Warnungen (Löschungen, langsame Operationen), einzeln, jede separat bestätigt.
  • Manuelle Änderungen, angezeigt mit einem Grund und einem vorgeschlagenen Vorgehen, nicht automatisch angewendet.
  • Undokumentierte Objekte (in der Datenbank, aber nicht in Ihrem Schema), zur Kenntnisnahme angezeigt.

Es endet mit einer Zusammenfassung: angewendet, übersprungen, auf manuelle Korrektur wartend, nicht im Schema.

Siehe migrate für die Flags (--dry-run, --safe, --yes, --local).

Dinge entfernen

Das Löschen einer Tabelle oder Spalte aus schema.js löscht sie nicht – das würde eine versehentliche Löschung katastrophal machen. Um etwas zu entfernen, markieren Sie es als veraltet:

// eine Spalte löschen
text('email').deprecated('ersetzt durch login')

// eine ganze Tabelle löschen
export const oldSessions = table('old_sessions', { /* … */ }).deprecated('unused');

Beim nächsten migrate erscheint das veraltete Objekt als Warnung-Status-Löschung, die Sie einzeln bestätigen. Sobald es weg ist, entfernen Sie die Deklaration.

Spaltentyp ändern

Typänderungen sind manuell – SQLite kann sie nicht immer vor Ort durchführen, und das Erzwingen bestehender Werte ist eine Ermessensentscheidung. migrate zeigt die Änderung und ihre Begründung an; führen Sie sie selbst mit rohem SQL (db.run(...)) durch, typischerweise durch Erstellen einer neuen Spalte oder Tabelle, Kopieren von Daten und Austauschen.

Das SDK

Zur Laufzeit hat ein Modul eine Bibliothek: sdk. Sie bündelt die drei Dinge, die ein Bot-Backend benötigt – eine Datenbank, die Telegram Bot API und ausgehende HTTP-Anfragen – ohne Installation und ohne Konfiguration von Anmeldedaten. Die Datenbank (db) wird in Die Datenbank behandelt; dieser Abschnitt behandelt api, fetch und das globale console.

import { db, api, fetch, InputFile, BotApiError, EndpointError } from 'sdk';   // die gesamte Oberfläche
// oder aus Untermodulen:
import { table, integer, text, eq, sql } from 'sdk/db';
import { api } from 'sdk/api';
import { fetch } from 'sdk/fetch';
Import Was es ist
db Datenbank – Query-Builder und Schema-DSL → Die Datenbank
api Telegram Bot API – api.sendMessage(...) → unten
fetch Ausgehende HTTP-Anfragen → unten
InputFile Eine Datei (Bytes + Name) zum Hochladen – funktioniert sowohl mit api als auch mit fetch → Dateien
EndpointError Lehnt den Aufruf einer Mini-App mit einer Nachricht ab, die sie anzeigen kann → Endpunkte

Importieren Sie Ihre eigenen Projektmodule per relativen Pfad mit der .js-Erweiterung (from '../schema.js', from '../lib/cart.js'). Siehe Das Modulsystem .

Die Bot API

api bietet Ihnen die gesamte Telegram Bot API . Rufen Sie jede Methode als api.<method>(params) auf. Jede aktuelle – und zukünftige – Bot-API-Methode funktioniert ohne SDK-Update.

import { api } from 'sdk';

const me = await api.getMe();                            // → das entpackte Ergebnis
await api.sendMessage({ chat_id: id, text: 'Hallo!' });
await api.editMessageText({ chat_id, message_id, text: 'Aktualisiert' });
await api.answerCallbackQuery({ callback_query_id, text: 'Fertig' });

Die Antworthülle ist entpackt. Die Bot-API wickelt Ergebnisse normalerweise in { ok: true, result: … } ein. api gibt das result direkt zurück – getMe() wird zum Benutzerobjekt aufgelöst, nicht zu einer Hülle. Parameter verwenden die eigenen Namen der Bot-API im Snake-Case (chat_id, message_id, reply_markup, …).

Fehler lösen BotApiError aus. Wenn die Bot-API { ok: false } zurückgibt, löst der Aufruf stattdessen eine BotApiError aus, anstatt einen falsch positiven Wert zurückzugeben, sodass Sie ihn nicht versehentlich ignorieren können. Der Fehler trägt .code (der Bot API error_code) , .description (die menschenlesbare Nachricht) , .method (welche Methode fehlgeschlagen ist) und .parameters (zusätzliche Daten wie retry_after bei einer 429 oder migrate_to_chat_id). Fangen Sie sie ab, um einen erwarteten Fehler zu behandeln, und werfen Sie den Rest erneut:

import { api, BotApiError } from 'sdk';

try {
  await api.deleteMessage({ chat_id, message_id });
} catch (e) {
  if (e instanceof BotApiError && e.code === 400) {
    // 400 = die Nachricht ist bereits weg; das ist hier in Ordnung.
  } else {
    throw e;
  }
}

Dateien

Arbeiten Sie mit Bytes von Dateien, nicht nur mit file_ids – laden Sie neue Dateien hoch und laden Sie vorhandene herunter.

Hochladen – InputFile. Wickeln Sie rohe Bytes in ein InputFile (einen einfachen Container: Bytes, ein Dateiname und ein optionaler MIME-type) und übergeben Sie es überall dort, wo eine Bot-API-Methode eine Datei erwartet:

import { api, InputFile } from 'sdk';

const doc = new InputFile(bytes, 'bericht.pdf', { type: 'application/pdf' });
await api.sendDocument({ chat_id, document: doc });

// Dateien, die in Parametern verschachtelt sind, funktionieren auch – z.B. ein 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' }) },
]});

Die Bytes werden als echter Multipart-Upload an Telegram gestreamt – die Datei wird nicht als Ganzes in einem einzigen Aufruf an die Plattform gepuffert –, aber die Limits der Telegram Bot API gelten immer noch: Ein Bot kann derzeit bis zu 50 MB pro Datei hochladen (und 10 MB für ein Foto).

Herunterladen – nach file_id. Zwei Helfer ziehen die Bytes einer Datei zurück. Die Plattform löst die file_id für Sie auf (sie führt getFile aus und ruft den Inhalt im Hintergrund ab), sodass Sie nie den Bot-Token oder eine Download-URL berühren:

const bytes = await api.getFileContent(file_id);          // → Uint8Array (die ganze Datei)

const { file, body } = await api.getFileStream(file_id);  // stattdessen streamen
file.file_unique_id;                                      // die getFile-Infos
for await (const chunk of body) { /* Uint8Array */ }      // oder puffern: await stream.bytes()

Telegrams Bot API limitiert getFile-Downloads auf 20 MB, das ist also die Obergrenze für beide Helfer.

HTTP

fetch ist ein fetch-ähnlicher Client zum Aufrufen der Außenwelt – Drittanbieter-APIs, Webhooks, alles über 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();

Die Antwort spiegelt die Webplattform wider: res.status, res.statusText, res.ok (true für 200-299), res.url, res.headers (.get(), .has(), .keys(), .entries()) und Body-Leser await res.json() / await res.text().

Sie können den Body auch inkrementell als Stream lesen – for await (const chunk of res.body) { … } – so konsumieren Sie serverseitig gesendete Ereignisse oder Token-für-Token-Ausgaben von KI-APIs.

Body-Helfer setzen den passenden Content-Type für Sie:

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

Anforderungs-Bodies können auch Dateien und rohe Bytes tragen: übergeben Sie ein Uint8Array, ein InputFile oder ein FormData (für multipart/form-data) als body, und die Bytes werden gestreamt – so laden Sie zu einer Drittanbieter-API hoch, genauso wie Sie Dateien an Telegram senden:

import { fetch, FormData, InputFile } from 'sdk';

const form = new FormData();
form.append('file', new InputFile(bytes, 'foto.jpg', { type: 'image/jpeg' }));
form.append('caption', 'von meinem Bot');
const res = await fetch('https://api.example.com/upload', { method: 'POST', body: form });

Es verhält sich ansonsten wie der Standard fetch, den Sie bereits kennen, mit zwei Einschränkungen:

  • Die Antwort wird als Text gelesen – await res.json() / await res.text() oder chunk-weise gestreamt mit res.body.
  • Die Gesamtantwort ist auf 30 MB begrenzt. Diese Grenze deckt die gesamte Antwort ab – das Streaming mit res.body ermöglicht es Ihnen, einen großen Body inkrementell zu verarbeiten, aber es hebt das Limit nicht auf.

Protokollierung

Die Standard-Global-Konsole console ist verfügbar – nichts muss importiert werden, sie ist einfach wie in jedem JavaScript da. Ihre Ausgabe wird erfasst und von npx tgcloud run angezeigt, was sie zu Ihrem primären Debugging-Werkzeug während der Entwicklung macht.

console.log('verarbeite', { chatId: id });   // log / debug – normal
console.info('gestartet');                     // info – blau
console.warn('rate limited');                // warn – gelb
console.error(err);                          // error – rot, mit Stack-Trace

Jede Zeile ist mit dem [file:line] markiert, von dem sie stammt. console.error und console.trace fügen einen vollständigen Stack hinzu, während console.warn dies nicht tut. Wenn Sie ein Modul mit npx tgcloud run ausführen, werden diese Zeilen mit einem farbigen Präfix pro Ebene, der Zeit seit Beginn der Ausführung und dem Ursprung angezeigt – siehe run .

Befehlszeilenschnittstelle

tgcloud ist die Brücke zwischen Ihrem Projektordner und der Cloud. Sie erstellt Projektgerüste, zeigt Ihnen, was sich geändert hat, stellt bereit, führt Module aus und wendet Datenbankmigrationen an. Sie benötigt Node.js 18 oder neuer. Zwei Möglichkeiten, sie zu erhalten:

# Empfohlen – erstellen Sie ein Projekt mit der darin installierten CLI:
npm create @tgcloud/bot example_bot

# Oder installieren Sie die CLI global und initialisieren Sie einen leeren Ordner:
npm install -g @tgcloud/cli
tgcloud init

Das npm-Paket heißt @tgcloud/cli, und der daraus installierte Befehl ist tgcloud. Jeder Befehl funktioniert von jedem Unterordner Ihres Projekts aus.

Befehl Zweck
init Erstellt ein neues Projektgerüst im aktuellen Ordner
add Erstellt ein neues Modulgerüst (einen Handler, einen Endpunkt oder ein Lib-Modul)
login Verknüpft das Projekt mit einem Bot (speichert den Token)
status Zeigt an, was sich lokal im Vergleich zur Cloud geändert hat
diff Zeigt die zeilenweisen Änderungen an
push Stellt geänderte Module und das Build der Mini-App in der Cloud bereit
migrate Wendet Schemaänderungen auf die Datenbank an
run Führt ein Modul auf der Plattform aus, ohne es bereitzustellen
fetch Prüft, was bereitgestellt ist, ohne lokale Dateien zu ändern
pull Bringt lokale Dateien mit der Cloud in Einklang
reset Verwirft lokale Änderungen; stellt vom Cloud-Status wieder her
webhook Überprüft und synchronisiert den von der Plattform verwalteten Webhook
completion Gibt ein Shell-Vervollständigungsskript aus (bash/zsh/fish)
upgrade Verschiebt ein Projektlayout vor 0.2.0 in den Ordner tgcloud/

Authentifizierung

Ein Projekt wird durch seinen Token an einen Bot gebunden, der das Format app<id>:<secret> hat. Der Teil app<id> ist öffentlich und darf ausgegeben werden; das Geheimnis erscheint nie in Protokollen oder Fehlern.

Der Token wird in dieser Reihenfolge aufgelöst:

  1. TGCLOUD_TOKEN Umgebungsvariable – für CI; niemals auf die Festplatte geschrieben.
  2. .tgcloud/credentials – geschrieben von npx tgcloud login .
  3. Keines davon -> Ein Fehler, der Sie auf npx tgcloud login hinweist.

Die CLI fragt nie mitten im Befehl nach einem Token. Der Login ist immer der explizite login-Schritt. Wenn ein gespeicherter Token ungültig wird, entfernt die CLI ihn und fordert Sie auf, sich erneut anzumelden.

init

npx tgcloud init

Erstellt ein neues Projektgerüst im aktuellen Verzeichnis: den tgcloud/-Ordner mit schema.js, lib/, handlers/ und einem Starter-Handler, plus tgcloud.jsonc, AGENTS.md, docs/ und den .tgcloud/-Statusordner. Führen Sie es im Stammverzeichnis eines bestehenden Frontend-Projekts aus – es fügt den tgcloud/-Ordner und die Dokumentation hinzu und überschreibt niemals Ihre Dateien. Wenn es das Frontend erkennt, verweist es auch tgcloud.jsonc auf den Build-Ordner und fügt ein deploy-Skript (build, dann push) zu Ihrer package.json hinzu – nur, wenn Sie noch kein deploy-Skript haben. Siehe Mini-App Frontend .

init weigert sich, ein Projekt innerhalb eines anderen Projekts zu erstellen. Das erneute Ausführen im Stammverzeichnis eines Projekts ist in Ordnung und fügt einfach alles hinzu, was fehlt.

add

npx tgcloud add <target>

Erstellt ein einzelnes neues Modulgerüst, das verkabelt und bereit zur Bearbeitung ist – ein Handler, ein Endpunkt oder ein lib/-Modul.

npx tgcloud add handlers/callback_query   # ein neuer Update-Handler
npx tgcloud add endpoints/getProfile      # ein neuer Endpunkt für die Mini-App
npx tgcloud add lib/cart                  # ein neues gemeinsam genutztes Modul

Beachten Sie, dass add niemals eine vorhandene Datei überschreibt.

Das <target> ist der Pfad des Moduls (eine nachgestellte .js ist optional). Für handlers/ muss der Name ein Telegram-Update-Typ sein. Die Plattform bewirbt die gültige Menge, sodass ein ungültiger Name sofort abgelehnt wird. handlers/ und endpoints/ sind flach, während lib/ verschachtelt sein kann (lib/payments/stripe).

Der Modulname ist erforderlich. Das Übergeben nur des Verzeichnisses ist ein Fehler – aber ein hilfreicher: für handlers/ listet es die Update-Typen auf, die Sie noch nicht haben, sodass Sie einen kopieren können.

$ npx tgcloud add handlers
Error: Specify a name, e.g. "handlers/callback_query". Available handlers/ types: callback_query, inline_query, chat_member, …

<Tab>-Vervollständigung bietet die gleiche Menge – siehe completion .

Die generierte Datei hat einen Live-export default, sodass der Handler sofort aktiv ist, sobald Sie ihn bereitstellen – es muss nichts entkommentiert werden. Stellen Sie das neue Modul mit push bereit.

login

npx tgcloud login

Fordert Ihren CLI-Zugriffstoken an – von @BotFather → Ihr Bot → Serverless → CLI Access → Access token, ein separater Token von Ihrem Bot API Token – validiert ihn gegen die Plattform und speichert ihn in .tgcloud/credentials.

login ist der einzige Befehl, der nach einem Token fragt, und er erfordert ein interaktives Terminal. In CI setzen Sie stattdessen TGCLOUD_TOKEN.

status

npx tgcloud status

Zeigt pro Datei an, was zwischen Ihrem Arbeitsverzeichnis und der bereitgestellten Kopie geändert wurde: geändert, neu, gelöscht, unverändert. Er funktioniert offline und vergleicht mit dem Status der Cloud zum Zeitpunkt Ihres letzten push, pull oder fetch. Er warnt auch vor verirrten .js-Dateien in tgcloud/, die nicht bereitgestellt werden.

Für ein Projekt mit einer Mini-App zeigt status auch an, was sich seit der letzten Bereitstellung im Build-Ordner geändert hat.

diff

npx tgcloud diff

Ähnlich wie status, aber zeigt die tatsächlichen zeilenweisen Unterschiede für geänderte Module. Ebenfalls offline.

push

npx tgcloud push [files...]

Stellt Ihr Projekt atomar in der Cloud bereit.

Ohne Argumente stellt es das gesamte Projekt bereit, und der bereitgestellte Zustand spiegelt Ihren Ordner exakt wider – Module, die Sie lokal gelöscht haben, werden in der Cloud entfernt.

Mit Datei- oder Verzeichnisargumenten (npx tgcloud push tgcloud/handlers/message.js, npx tgcloud push handlers/) werden nur die Änderungen in diesen Dateien bereitgestellt. Ein gezieltes Push löscht niemals andere Module. Ein Ziel kann als Dateipfad (tgcloud/handlers/message.js), als Pfad innerhalb von tgcloud/ (handlers/message.js) oder als Modulname (handlers/message) angegeben werden – sie bedeuten alle dasselbe Modul.

Seine einzige Option ist --force – um die Prüfung auf Gleichzeitigkeit zu überspringen und den Inhalt der Cloud zu überschreiben. Verwenden Sie es nur, wenn Sie sicher sind (siehe Synchron bleiben ).

Wenn tgcloud.jsonc einen statischen Ordner benennt, stellt ein vollständiger push ihn im selben Stapel bereit – nur die geänderten Dateien werden hochgeladen – und gibt die Adresse der Website aus. Bauen Sie zuerst, denn push führt niemals Ihren Build aus (npm run deploy erledigt beides). npx tgcloud push dist/ stellt nur die statischen Dateien bereit, und ein Push, der nur Module benennt, lässt diese unberührt.

Nach einer Bereitstellung, wenn schema.js geändert wurde und die Datenbank nicht synchron ist, druckt push eine Zusammenfassung der ausstehenden Änderungen und schlägt npx tgcloud migrate vor. Es wendet sie niemals selbst an.

migrate

npx tgcloud migrate

Wendet Ihre Schemaänderungen auf die Datenbank an. Es berechnet den Unterschied zwischen schema.js und der Live-Datenbank und führt Sie dann Schritt für Schritt durch, mit einem laufenden [N/M]-Zähler:

  • Eine kurze Zusammenfassung aller ausstehenden Änderungen.
  • Sichere Änderungen, die auf Ihre Bestätigung hin zusammen in einem Schritt angewendet werden.
  • Warnungen (Löschungen, langsame Operationen), einzeln, jede separat bestätigt.
  • Manuelle Änderungen, angezeigt mit einem Grund und einem vorgeschlagenen Vorgehen, nicht automatisch angewendet.
  • Undokumentierte Objekte (in der Datenbank, aber nicht in Ihrem Schema), zur Kenntnisnahme angezeigt.

Es endet mit einer Zusammenfassung: angewendet, übersprungen, auf manuelle Korrektur wartend, nicht im Schema.

Siehe Migrationen für das Modell.

Optionen: --dry-run (alles drucken, nichts anwenden), --safe (sichere Änderungen automatisch anwenden, Warnungen überspringen), --yes (sichere Änderungen und jede Warnung automatisch anwenden, manuelle überspringen – mit Vorsicht verwenden), --local (Unterschiede zu Ihrer lokalen schema.js anstelle der bereitgestellten). Ohne Flag benötigt migrate ein Terminal und schlägt in einer nicht-interaktiven Umgebung fehl, anstatt zu raten.

run

npx tgcloud run <module> [args] [--ctx <json5>]

Führt einen Handler oder einen Endpunkt auf der Plattform ohne Bereitstellung aus, unter Verwendung Ihrer aktuellen lokalen Dateien. Dies ist die schnelle innere Schleife zum Testen von Logik.

  • <module> – ein einfacher Name (gesucht unter handlers/ und endpoints/) oder ein Pfad wie handlers/message. Ein einfacher Name, der in beiden Verzeichnissen existiert, muss mit seinem Verzeichnis angegeben werden.
  • [args] – die an den Handler übergebene Nutzlast, geschrieben in JSON5 , damit Sie Anführungszeichen bei Schlüsseln weglassen können. Es ist das Update-Typ-Objekt, das Ihr Handler erhält (z.B. eine Nachricht für handlers/message), oder die input eines Endpunkts, und es muss ein Objekt sein – kein Array oder ein einfacher String.
  • --ctx <json5> – das Kontextobjekt des Moduls (sein zweites Argument), ebenfalls JSON5. Verwenden Sie es, um das bereitzustellen, was Ihr Code von ctx liest – das rohe Update für einen Handler, --ctx '{ update: { update_id: 1 } }', oder die Init-Daten für einen Endpunkt, --ctx '{ initData: { user: { id: 1 } } }'. run verifiziert es nicht, da Sie Ihre Logik testen, nicht die Prüfungen der Plattform.
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hallo" }'

Die Plattform führt das Modul gegen den Modulraum aus, der aus Ihrem lokalen Projekt zusammengestellt wurde (sodass lokal geänderter lib/-Code ebenfalls verwendet wird), und gibt den Rückgabewert, alles mit console.* protokollierte und die verstrichene Zeit zurück. Lesen Sie große Argumente aus einer Datei mit npx tgcloud run handlers/message "$(cat message.json5)".

fetch

npx tgcloud fetch

Prüft, was derzeit bereitgestellt ist, ohne Ihre Arbeitsdateien zu berühren. Danach vergleichen status und diff mit dem neuesten Cloud-Status. Nützlich, um einen Konflikt zu betrachten, bevor Sie entscheiden, wie Sie ihn lösen.

pull

npx tgcloud pull

Bringt Ihre lokalen Dateien mit der Cloud in Einklang, indem Ihre Arbeitsdateien auf den bereitgestellten Zustand aktualisiert werden.

pull schreibt den Build-Ordner Ihrer Mini-App niemals.

reset

npx tgcloud reset

Verwirft Ihre lokalen Änderungen und stellt die Module aus dem zuletzt bekannten Cloud-Status wieder her. Verwenden Sie es, um ein Experiment wegzuwerfen. Der Build-Ordner der Mini-App wird nicht berührt.

webhook

npx tgcloud webhook
npx tgcloud webhook sync [--drop-pending]

Telegram liefert Updates über einen Webhook an Ihren Bot, der von der Plattform für Sie verwaltet wird – Sie müssen ihn nie von Hand auf etwas zeigen. npx tgcloud webhook zeigt seinen aktuellen Status an: die URL, die allowed_updates-Liste, wie viele Updates ausstehen, den letzten Lieferfehler (falls vorhanden) und ob er mit Ihren bereitgestellten Handlern synchron ist.

„Synchron“ bedeutet, dass der Webhook auf die Plattform zeigt und seine allowed_updates mit den von Ihnen bereitgestellten Handlern übereinstimmen – sodass Telegram genau die Update-Typen liefert, die Sie verarbeiten, und nichts anderes. Das Bereitstellen eines neuen Handlers (oder das Entfernen eines) kann den Webhook unsynchronisieren, bis er erneut aktualisiert wird; npx tgcloud status kennzeichnet dies ebenfalls.

npx tgcloud webhook sync behebt dies – er zeigt den Webhook erneut auf die Plattform und baut allowed_updates aus Ihren bereitgestellten Handlern neu auf. Fügen Sie --drop-pending hinzu, um Updates zu verwerfen, die Telegram bereits in die Warteschlange gestellt hatte, bevor die Synchronisierung erfolgte (andernfalls werden sie geliefert, sobald der Webhook wieder funktionsfähig ist).

completion

Hinweis: Die Tab-Vervollständigung funktioniert nur, wenn ein nacktes tgcloud in Ihrem PATH vorhanden ist – installieren Sie es also global (npm install -g @tgcloud/cli) oder legen Sie das Binärprogramm anderweitig auf Ihren PATH. Es kann sich nicht in npx einklinken.

tgcloud completion <bash|zsh|fish>

Gibt ein Shell-Vervollständigungsskript an stdout aus. Aktivieren Sie es einmal, dann vervollständigt <Tab> Befehle, Flags, Modulverzeichnisse, die Handler-Update-Typen, die Sie noch nicht haben, und Ihre lokalen ausführbaren Module.

# bash – benötigt das bash-completion-Paket:
echo 'eval "$(tgcloud completion bash)"' >> ~/.bashrc

# zsh – stellen Sie sicher, dass `autoload -U compinit && compinit` in Ihrem ~/.zshrc ausgeführt wird:
echo 'eval "$(tgcloud completion zsh)"' >> ~/.zshrc

# fish:
tgcloud completion fish > ~/.config/fish/completions/tgcloud.fish

Starten Sie Ihre Shell danach neu (oder laden Sie die Datei neu). Das Ausführen von tgcloud completion ohne Shell gibt diese Anweisungen erneut aus.

upgrade

npx tgcloud upgrade [--dry-run]

Einmalige Migration für Projekte, die vor CLI 0.2.0 erstellt wurden und schema.js, lib/ und handlers/ im Projektstammverzeichnis behielten. Sie verschiebt sie in den Ordner tgcloud/. Ihre Verbindung zum Bot bleibt erhalten und nichts muss neu bereitgestellt werden. --dry-run gibt die geplanten Verschiebungen aus, ohne etwas zu ändern. Andere Befehle laufen im alten Layout nicht, bis Sie ein Upgrade durchführen.

Synchron bleiben

Jedes Projekt hat eine monoton steigende Revision in der Cloud, die bei jedem Deploy inkrementiert wird. Die CLI merkt sich die Revision, mit der sie zuletzt synchronisiert hat, und sendet sie bei jedem push. Wenn die Cloud sich weiterentwickelt hat – weil eine andere Maschine oder ein Teamkollege bereitgestellt hat –, wird der Push abgelehnt, anstatt die Arbeit des anderen stillschweigend zu überschreiben, und die CLI bietet drei Wege nach vorne:

npx tgcloud fetch           # sehen, was sich in der Cloud geändert hat, dann erneut prüfen
npx tgcloud pull            # das Neueste in Ihre Arbeitsdateien holen
npx tgcloud push --force    # den Cloud-Status überschreiben (gefährlich)

Diese Prüfung auf optimistische Nebenläufigkeit ist der Grund, warum Sie einen Bot teamübergreifend teilen können, ohne ein Schritt-für-Schritt-Bereitstellungsprozess. Befehle werden bei Fehlern mit einem nicht-null-Wert beendet – ein abgelehnter Deploy, eine fehlgeschlagene Migration, ein Authentifizierungsfehler, ein Modul, das während der run fehlgeschlagen ist –, sodass sie sich sauber in Skripten und CI-Pipelines zusammensetzen.