Το Telegram Serverless σας επιτρέπει να εκτελείτε κώδικα backend για το bot και το Mini App σας απευθείας στην υποδομή του Telegram — χωρίς διακομιστές για προμήθεια, χωρίς containers που πρέπει να παραμένουν ενεργά, χωρίς να ανησυχείτε για την κλιμάκωση (scaling). Γράφετε απλά JavaScript modules, τα αναπτύσσετε με μία εντολή και το Telegram τα εκτελεί σε ένα γρήγορο, απομονωμένο V8 sandbox που βρίσκεται ακριβώς δίπλα στο Bot API και μια ενσωματωμένη βάση δεδομένων.
Αν έχετε συνδέσει ποτέ ένα bot σε έναν VPS, μια cloud function ή ένα πάνελ φιλοξενίας μόνο και μόνο για να απαντάτε σε ένα /start, αυτό είναι το κομμάτι που δεν χρειάζεται πλέον να κάνετε.
Γιατί serverless
Ένα bot του Telegram είναι, στην ουσία του, ένα πρόγραμμα που αντιδρά σε ενημερώσεις (updates). Παραδοσιακά, έπρεπε να φιλοξενείτε αυτό το πρόγραμμα κάπου που να είναι πάντα ενεργό, προσβάσιμο και ασφαλές — και στη συνέχεια να το διατηρείτε έτσι. Το Telegram Serverless αφαιρεί αυτό το επίπεδο εντελώς:
- Καμία υποδομή. Δεν υπάρχει μηχάνημα για ενοικίαση, ενημέρωση (patch) ή παρακολούθηση. Ο κώδικάς σας εκτελείται κατά παραγγελία και κλιμακώνεται αυτόματα μαζί με το bot σας.
- Περιλαμβάνονται τα διαπιστευτήρια. Το Telegram Bot API, μια βάση δεδομένων βασισμένη σε SQLite και η εξερχόμενη κίνηση HTTP είναι διαθέσιμα σε κάθε module από προεπιλογή — δεν χρειάζεται εγκατάσταση, ούτε σύνδεση διαπιστευτηρίων.
- Γρήγορη, απομονωμένη εκτέλεση. Κάθε κλήση εκτελείται σε ένα ελαφρύ V8 isolate, κοντά στα συστήματα του ίδιου του Telegram, ώστε οι κλήσεις προς το Bot API και τη βάση δεδομένων σας να είναι γρήγορες και αξιόπιστες.
- Μια πραγματική ροή εργασίας προγραμματιστή. Ένα project ζει σε έναν φάκελο στον υπολογιστή σας υπό τον έλεγχο εκδόσεων (version control). Επεξεργάζεστε αρχεία, βλέπετε ακριβώς τι άλλαξε, αναπτύσσετε ατομικά και προχωράτε το σχήμα της βάσης δεδομένων σας με ελεγμένες μεταναστεύσεις (migrations) — τον τρόπο που ήδη εργάζεστε με οτιδήποτε άλλο.
Το νοητικό μοντέλο: Πώς λειτουργεί το project
Εργάζεστε σε τρία σημεία, τα οποία αντιστοιχίζονται καθαρά το ένα με το άλλο:
| Πού | Τι ζει εκεί |
|---|---|
| Ο φάκελος του project | JavaScript modules — σχήμα, κοινόχρηστος κώδικας, handlers ενημερώσεων, endpoints για Mini App — και, αν έχετε, το front‑end του Mini App |
| Το cloud | Το αναπτυγμένο αντίγραφο αυτών των modules, η βάση δεδομένων του bot σας και τα αρχεία του Mini App |
Το tgcloud CLI |
Η γέφυρα — σας δείχνει τις διαφορές και τις συγχρονίζει |
Δεν κάνετε ποτέ SSH πουθενά. Επεξεργάζεστε αρχεία τοπικά, εκτελείτε npx tgcloud push και η πλατφόρμα αναλαμβάνει τα υπόλοιπα. Η κίνηση του bot σας διαχειρίζεται από τα αναπτυγμένα modules; η βάση δεδομένων σας διατηρείται μεταξύ των κλήσεων.
Ένα project έχει μόνο τέσσερα είδη κώδικα, όλα μέσα σε έναν φάκελο tgcloud/:
tgcloud/
├─ endpoints/ # συναρτήσεις που καλεί το Mini App σας
├─ handlers/ # συναρτήσεις που αντιδρούν σε τύπους ενημερώσεων του Telegram
├─ lib/ # κοινόχρηστος κώδικας που εισάγετε από οπουδήποτε
└─ schema.js # οι πίνακες της βάσης δεδομένων σας
Όταν φτάνει μια ενημέρωση — ένα μήνυμα, ένα πάτημα κουμπιού, ένα inline query — το Telegram τη δρομολογεί στον αντίστοιχο handler (handlers/message.js, handlers/callback_query.js, …) και καλεί το default export του. Αυτή η συνάρτηση επικοινωνεί με το Bot API και τη βάση δεδομένων μέσω του SDK και επιστρέφει. Αυτός είναι όλος ο βρόχος. Μια ενημέρωση χωρίς αντίστοιχο handler απλώς αγνοείται, οπότε προσθέτετε μόνο τους handlers που χρειάζεστε.
Ένα Mini App είναι προαιρετικό. Αν το bot σας έχει ένα, το front‑end του ζει στο ίδιο project, δίπλα στο tgcloud/. Το Serverless φιλοξενεί το build output και εκτελεί τα endpoints/ που καλεί. Δείτε
Mini App front‑end
.
Γρήγορη επίδειξη
Ακολουθεί ένα πλήρες, λειτουργικό demo bot. Απαντά σε κάθε μήνυμα και θυμάται πόσα έχει δει από κάθε 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;
// Εισαγωγή του μετρητή, ή αύξησή του αν αυτό το chat έχει ήδη έναν — και λήψη της
// γραμμής που προκύπτει στην ίδια δήλωση μέσω του .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: `Γεια! Έχω δει ${row.seen} μήνυμα(τα) από εσένα.`,
});
}
Ανάπτυξη:
npx tgcloud push # μεταφόρτωση των modules
npx tgcloud migrate # δημιουργία του πίνακα `counters`
Αυτό είναι ένα live bot με μόνιμη κατάσταση και χωρίς διακομιστή. Τα πάντα σε αυτό — api, db, το table() DSL — περιγράφονται στις παρακάτω ενότητες.
Το Serverless είναι ένα γενικό backend για bots και Mini Apps του Telegram, όχι ένα πρότυπο για ένα είδος εφαρμογής. Είναι ιδανικό για:
- Bots συνομιλητικής τεχνητής νοημοσύνης (Conversational AI) που χρειάζεται να αποθηκεύουν την κατάσταση ανά χρήστη σε μια βάση δεδομένων.
- Backends για Mini Apps που αποθηκεύουν δεδομένα χρηστών και εξυπηρετούν δυναμικό περιεχόμενο.
- Παιχνίδια και εργαλεία — συμπεριλαμβανομένων πινάκων κατάταξης, κουίζ και πολλά άλλα.
- Αυτοματισμούς και ενσωματώσεις που καλούν εξωτερικά HTTP APIs και στέλνουν αποτελέσματα σε chats.
Πρώτα βήματα
Αυτός ο οδηγός σας μεταφέρει από έναν κενό φάκελο σε ένα live bot που απαντά σε μηνύματα και αποθηκεύει δεδομένα. Υποθέτει ότι έχετε εγκατεστημένο το
Node.js
18 ή νεότερο και ένα bot εγγεγραμμένο στο
@BotFather
. Μέχρι το τέλος θα έχετε χρησιμοποιήσει κάθε εντολή που χρειάζεστε καθημερινά: push, migrate, run και status.
Πριν από οτιδήποτε άλλο, ενεργοποιήστε το Serverless. Στο @BotFather , ανοίξτε το bot σας → Serverless και ενεργοποιήστε το. Αυτό ενεργοποιεί τη λειτουργία για αυτό το bot και ξεκλειδώνει το διακριτικό πρόσβασης CLI, τους handlers, τη βιβλιοθήκη και τη βάση δεδομένων.
1. Δημιουργία project
Ο πιο γρήγορος τρόπος για να ξεκινήσετε είναι ο δημιουργός project, ο οποίος στήνει ένα project και εγκαθιστά το CLI σε αυτό:
npm create @tgcloud/bot example_bot
cd example_bot
Το όρισμα είναι ο φάκελος στόχος: περάστε . για να γίνει το στήσιμο στον τρέχοντα φάκελο, ή οποιαδήποτε διαδρομή. Λειτουργεί και σε υπάρχοντα φάκελο και δεν αντικαθιστά ποτέ αρχεία που έχετε ήδη.
Αυτό σας δίνει ένα έτοιμο για επεξεργασία project:
example_bot/
├─ tgcloud/ # ό,τι εκτελεί η πλατφόρμα
│ ├─ handlers/
│ │ └─ message.js # ένας αρχικός handler μηνυμάτων (επαναλαμβάνει το κείμενο)
│ ├─ lib/ # τα κοινόχρηστα modules σας μπαίνουν εδώ (κενό στην αρχή)
│ └─ schema.js # οι πίνακες της βάσης δεδομένων σας
├─ docs/
│ └─ tgcloud-sdk.md # αναφορά SDK (για εσάς και τα εργαλεία AI σας)
├─ AGENTS.md # προσανατολισμός για AI βοηθούς κωδικοποίησης
├─ package.json
└─ tgcloud.jsonc # διαμόρφωση project
Τα αρχεία που δημιουργούνται είναι αυτο-τεκμηριωμένα — το καθένα περιέχει σχολιασμένα παραδείγματα του τι μπορείτε να κάνετε στη συνέχεια.
Το CLI εγκαθίσταται στο project ως τοπική dev-dependency, οπότε το εκτελείτε με npx tgcloud <command> (το npx βρίσκει το αντίγραφο στο node_modules του project σας), ή μέσω των συντομεύσεων npm run που προσθέτει ο δημιουργός στο package.json (npm run deploy, npm run status). Από προεπιλογή δεν υπάρχει καθολικό tgcloud στο PATH σας.
Μπορείτε επίσης να το εγκαταστήσετε καθολικά — npm install -g @tgcloud/cli — αν προτιμάτε να πληκτρολογείτε ένα σκέτο tgcloud από οπουδήποτε. Αυτό είναι χρήσιμο για την εκτέλεση tgcloud init σε οποιονδήποτε κενό φάκελο, και είναι αυτό που χρειάζεται η αυτόματη συμπλήρωση του shell. Με όποιον τρόπο κι αν το κάνετε, παίρνετε το ίδιο project.
Φτιάχνετε ένα Mini App; Ξεκινήστε από το front-end του. Δημιουργήστε το με το συνηθισμένο σας εργαλείο, στη συνέχεια εκτελέστε τον δημιουργό project σε αυτόν τον φάκελο. Δείτε Mini App front‑end .
2. Σύνδεση του bot σας
Κάθε project είναι δεμένο με ένα bot. Συνδέστε τα με το login, το οποίο ζητά το διακριτικό πρόσβασης CLI (
@BotFather
→ το bot σας → Serverless → CLI Access → Access token — ένα διαφορετικό διακριτικό από το
API token του bot σας
) και το αποθηκεύει τοπικά:
npx tgcloud login
Το διακριτικό έχει τη μορφή app<id>:<secret>. Το CLI το διατηρεί στο .tgcloud/, το οποίο αγνοείται από το git, και δεν εκτυπώνει ποτέ το μέρος του μυστικού (secret). Το login είναι η μόνη φορά που σας ζητείται — δείτε
Authentication
για το πώς επιλύονται τα διακριτικά στο CI.
3. Δείτε τι συμβαίνει
Δύο εντολές σας λένε πού βρίσκονται τα πράγματα ανά πάσα στιγμή, και οι δύο πλήρως offline:
npx tgcloud status # τι άλλαξε τοπικά σε σχέση με το αναπτυγμένο αντίγραφο
npx tgcloud diff # οι αλλαγές γραμμή προς γραμμή
Αμέσως μετά το init όλα είναι καινούργια και τίποτα δεν έχει αναπτυχθεί ακόμα. Το status δείχνει τα αρχικά αρχεία που περιμένουν να ανέβουν.
4. Ανάπτυξη (Deploy)
Στείλτε τα modules σας στο cloud:
npx tgcloud push
Το push μεταφορτώνει κάθε τροποποιημένο module σε μία ατομική παρτίδα και ενημερώνει την τοπική σας εγγραφή για το τι περιέχει τώρα το cloud. Το bot σας είναι live: ανοίξτε το στο Telegram και στείλτε του ένα μήνυμα — ο αρχικός handler το επαναλαμβάνει.
Η ανάπτυξη δεν αγγίζει ποτέ τη βάση δεδομένων σας. Η ώθηση κώδικα και η αλλαγή του σχήματος της βάσης δεδομένων σας είναι εσκεμμένα ξεχωριστά βήματα, ώστε μια ανάπτυξη κώδικα να μην μπορεί ποτέ να σας εκπλήξει με μια μετανάστευση δεδομένων. Για αυτό υπάρχει το επόμενο βήμα.
5. Προσθήκη πίνακα βάσης δεδομένων
Ας κάνουμε το bot να θυμάται κάτι. Ανοίξτε το tgcloud/schema.js και δηλώστε έναν πίνακα:
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())`) ,
});
Αναπτύξτε το σχήμα και μετά εφαρμόστε το στη βάση δεδομένων:
npx tgcloud push # μεταφορτώνει το νέο σχήμα
npx tgcloud migrate # δημιουργεί τον πίνακα `messages`
Το push αναφέρει ότι το σχήμα δεν είναι συγχρονισμένο και σας δείχνει την εκκρεμή αλλαγή, αλλά δεν εφαρμόζει τίποτα. Το migrate σας καθοδηγεί στην αλλαγή και, με την επιβεβαίωσή σας, δημιουργεί τον πίνακα. Αυτό το μοντέλο δύο βημάτων — και τι συμβαίνει με πιο ριψοκίνδυνες αλλαγές όπως οι διαγραφές — καλύπτεται στις
Μεταναστεύσεις
.
6. Αποθήκευση και ανάγνωση δεδομένων
Τώρα χρησιμοποιήστε τον πίνακα από τον handler σας. Επεξεργαστείτε το tgcloud/handlers/message.js:
import { api, db } from 'sdk';
import { eq } from 'sdk/db';
import { messages } from '../schema.js';
export default async function (message) {
// Αποθήκευση αυτού του μηνύματος.
await db.insert(messages)
.values({ chatId: message.chat.id, text: message.text })
.run();
// Μέτρηση πόσα έχουμε αποθηκεύσει για αυτό το chat.
const count = await db.$count(messages, eq(messages.chatId, message.chat.id));
await api.sendMessage({
chat_id: message.chat.id,
text: `Αποθηκεύτηκε. Αυτό είναι το ${count}ο μήνυμα από αυτό το chat μέχρι στιγμής.`,
});
}
Αναπτύξτε τον ενημερωμένο handler με npx tgcloud push, μετά στείλτε στο bot σας μερικά μηνύματα και δείτε τον αριθμό να ανεβαίνει. Η βάση δεδομένων διατηρείται μεταξύ των κλήσεων — αυτή είναι η μνήμη του bot σας.
7. Δοκιμή χωρίς ανάπτυξη
Δεν χρειάζεται να κάνετε ανάπτυξη για να δοκιμάσετε μια αλλαγή. Το npx tgcloud run εκτελεί έναν handler στην πλατφόρμα χρησιμοποιώντας τα τοπικά αρχεία σας, χωρίς να τα δημοσιεύει:
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "γεια" }'
Το όρισμα είναι το payload που λαμβάνει ο handler σας — για τον handlers/message, ένα
Message
— γραμμένο σε
JSON5
(ώστε να μπορείτε να παραλείψετε τα εισαγωγικά στα κλειδιά). Η εντολή εκτυπώνει ό,τι κατέγραψε ο handler με console.*, την τιμή επιστροφής και πόσο χρόνο διήρκεσε. Αυτός είναι ο πιο γρήγορος βρόχος για επανάληψη στη λογική — χωρίς ανάπτυξη, χωρίς αναμονή για ένα πραγματικό μήνυμα.
8. Παραμονή σε συγχρονισμό
Καθώς εργάζεστε, μια χούφτα εντολές κρατούν το τοπικό project σας και το cloud ευθυγραμμισμένα: npx tgcloud status δείχνει τι άλλαξε, npx tgcloud push αναπτύσσει, npx tgcloud pull φέρνει το τοπικό project σας σε ευθυγράμμιση με το cloud, npx tgcloud fetch ελέγχει τι έχει αναπτυχθεί χωρίς να αγγίζει τα αρχεία σας, και npx tgcloud reset απορρίπτει τις τοπικές αλλαγές.
Αν δύο άτομα (ή δύο μηχανήματα) αναπτύξουν στο ίδιο bot, η πλατφόρμα ανιχνεύει τη σύγκρουση και το
pushσταματά για να σας επιτρέψει να κάνετε πρώταpull— δεν μπορείτε ποτέ να αντικαταστήσετε σιωπηλά τη δουλειά κάποιου άλλου. Δείτε Staying in sync .
Δημιουργία με AI
Προτιμάτε να χτίζετε με έναν βοηθό AI — ή μήπως ο μόνος κωδικοποιητής στην ομάδα σας είναι ένα AI; Μπορείτε ακόμα να παραδώσετε ένα bot. Κάναμε ένα πρώτο βήμα για να νιώθει ένα AI agent σαν στο σπίτι του σε ένα project: κάθε νέο στήνεται με ένα AGENTS.md και μια αναφορά docs/tgcloud-sdk.md που τα εργαλεία κωδικοποίησης ανάγνωσης agent διαβάζουν αυτόματα.
Μαζί με ένα μικρό, αυτόνομο runtime — ένα SDK, χωρίς πακέτα npm για διαχείριση — αυτό δίνει στον βοηθό ένα προβάδισμα στις συμβάσεις που το γενικό codegen τείνει να χάνει εδώ: σχετικές εισαγωγές (relative imports) με .js, χωρίς ξένα κλειδιά (foreign keys), κάθε κλήση db είναι ασύγχρονη, ένας handler ανά τύπο ενημέρωσης, και η ροή δύο βημάτων push/migrate.
Δοκιμάστε το:
npm create @tgcloud/bot my-bot
cd my-bot
opencode # ή Claude Code, Cursor, … — οποιοσδήποτε agent διαβάζει το AGENTS.md
Μετά απλώς ρωτήστε, σε απλή γλώσσα:
Γράψε ένα bot που θυμάται τη λίστα υποχρεώσεων κάθε ατόμου — πρόσθεσε ένα στοιχείο όταν στέλνουν κείμενο, και δείξε ολόκληρη τη λίστα όταν στέλνουν /list.
Ο βοηθός επεξεργάζεται το schema.js και τους handlers σας για εσάς; εσείς ελέγχετε, δοκιμάζετε μια αλλαγή ακαριαία με npx tgcloud run, μετά βγαίνετε live με npx tgcloud push και npx tgcloud migrate. Το AGENTS.md είναι μέρος του project σας — επεξεργαστείτε το καθώς το bot μεγαλώνει ώστε οι οδηγίες να παραμένουν ακριβείς.
Εν κινήσει με το BotFather
Μόνο με το τηλέφωνό σας; Ολόκληρο το project ζει και στο @BotFather — ανοίξτε το bot σας → Serverless και παίρνετε τα πάντα που διαχειρίζεται το CLI, σε μια οθόνη αφής:
- Handlers — δημιουργία, επεξεργασία και δοκιμαστική εκτέλεση handlers ενημερώσεων; το BotFather κρατά το webhook σε συγχρονισμό με τους handlers που έχετε (το ίδιο In sync / Out of sync που αναφέρει το CLI).
- Endpoints — οι συναρτήσεις που καλεί το Mini App σας.
- Library — τα κοινόχρηστα
lib/modules σας. - Βάση δεδομένων — επεξεργαστείτε το
schema.jsστην ίδια σύνταξη τύπου Drizzle, ελέγξτε τις εκκρεμείς αλλαγές και εφαρμόστε τις; το Save πραγματοποιεί την ανάπτυξη. - Αρχεία Mini App — το front-end που ανοίγουν οι χρήστες σας στο Telegram.
- CLI Access — πάρτε το διακριτικό πρόσβασης CLI από εδώ όταν επιστρέψετε σε ένα πληκτρολόγιο.
Είναι το ίδιο cloud project, οπότε μπορείτε να ξεκινήσετε έναν handler στο τηλέφωνό σας και να τον κάνετε npx tgcloud pull στο laptop σας αργότερα — τίποτα δεν είναι δεμένο με έναν μόνο client. Η εκτέλεση ενός handler εμφανίζει ακόμη και την έξοδο της Κονσόλας του απευθείας στο chat, όπως ακριβώς το
npx tgcloud run
.
Projects και modules
Ένα serverless project είναι ένας συνηθισμένος φάκελος υπό τον έλεγχο εκδόσεων. Όλα όσα εκτελεί η πλατφόρμα κάθονται σε έναν φάκελο, tgcloud/; το υπόλοιπο project είναι δικό σας — front-end mini-app, ρυθμίσεις, docs. Δεν υπάρχει βήμα build για το bot, δεν υπάρχουν node_modules στο runtime, και δεν υπάρχει σημείο εισόδου διακομιστή.
Ανατομία ενός project
example_bot/
├─ tgcloud/ # το αναπτυγμένο μέρος — τίποτα άλλο δεν είναι
│ ├─ handlers/ # handlers ενημερώσεων — επίπεδοι, μόνο ένα επίπεδο
│ │ ├─ message.js
│ │ └─ callback_query.js
│ ├─ endpoints/ # συναρτήσεις που καλεί το Mini App σας — επίπεδες, μόνο ένα επίπεδο
│ │ └─ getProfile.js
│ ├─ lib/ # κοινόχρηστα modules; επιτρέπονται υποκατάλογοι
│ │ ├─ reply.js
│ │ └─ internal/util.js
│ └─ schema.js # σχήμα βάσης δεδομένων — ένα αρχείο
└─ .tgcloud/ # κατάσταση CLI, git‑ignored
Μόνο τα αρχεία .js μέσα στο tgcloud/ — schema.js, lib/, handlers/, endpoints/ — αναπτύσσονται. Όλα τα άλλα — Markdown, αρχεία ρυθμίσεων, ο φάκελος .tgcloud/, οτιδήποτε έξω από το tgcloud/ — παραμένουν στον υπολογιστή σας. Τα ονόματα των modules είναι σχετικά με το tgcloud/. Το αρχείο tgcloud/handlers/message.js είναι το module handlers/message, και αυτό είναι το όνομα που παίρνει το npx tgcloud run.
Αναβαθμίζετε από παλαιότερο project; Projects που δημιουργήθηκαν πριν από το CLI 0.2.0 διατηρούσαν τα
schema.js,lib/καιhandlers/στη ρίζα του project. Εκτελέστεnpx tgcloud upgradeμία φορά για να τα μετακινήσετε στοtgcloud/.
schema.js — η βάση δεδομένων σας. Δηλώνει πίνακες ως named exports χρησιμοποιώντας το σχήμα DSL και ζει στο tgcloud/ ως ένα ενιαίο αρχείο. Αναπτύσσεται όπως κάθε άλλο module, αλλά η ανάπτυξή του δεν αλλάζει ποτέ τη βάση δεδομένων — οι αλλαγές σχήματος εφαρμόζονται ξεχωριστά με npx tgcloud migrate. Δείτε
The database
.
lib/ — κοινόχρηστος κώδικας, οτιδήποτε θέλετε να επαναχρησιμοποιήσετε σε handlers: καθαροί βοηθοί (pure helpers), επίπεδα πρόσβασης βάσης δεδομένων, μορφοποίηση, ενσωματώσεις με εξωτερικές υπηρεσίες. Το lib/ είναι ο μόνος κατάλογος που μπορεί να περιέχει υποκαταλόγους (lib/internal/util.js, lib/payments/stripe.js), οπότε μπορείτε να οργανώσετε μια μεγαλύτερη βάση κώδικα όπως θέλετε. Τα modules στο lib/ δεν καλούνται ποτέ απευθείας από την πλατφόρμα; υπάρχουν για να εισάγονται από handlers και το ένα από το άλλο.
handlers/ — τα σημεία εισόδου του bot σας. Κάθε αρχείο αντιστοιχεί σε έναν τύπο ενημέρωσης του Telegram, και η πλατφόρμα δρομολογεί κάθε εισερχόμενη ενημέρωση στον αντίστοιχο handler:
| Αρχείο | Χειρίζεται |
|---|---|
handlers/message.js |
Νέα εισερχόμενα μηνύματα |
handlers/inline_query.js |
Νέα εισερχόμενα inline queries |
handlers/callback_query.js |
Νέα εισερχόμενα callback queries |
| … | οποιοσδήποτε άλλος τύπος ενημέρωσης Bot API |
Το handlers/ είναι επίπεδο — χωρίς υποκαταλόγους. Το export default ενός handler είναι η συνάρτηση που καλεί η πλατφόρμα (δείτε
Handlers
).
Ένας τύπος ενημέρωσης χειρίζεται μόνο αν το αρχείο του handler υπάρχει και δεν είναι κενό. Αν δεν υπάρχει handlers/<type>.js — ή το αρχείο είναι κενό — οι ενημερώσεις αυτού του τύπου αγνοούνται και η πλατφόρμα δεν εκτελεί τίποτα για αυτές. Οπότε κρατήστε μόνο τους handlers που πραγματικά χρειάζεστε: ο καθένας που προσθέτετε είναι ένας άλλος τύπος ενημέρωσης που το bot σας ξυπνά για να επεξεργαστεί, και η παράλειψη των υπολοίπων σημαίνει ότι το Telegram δεν ξεκινά τον κώδικά σας για ενημερώσεις που ούτως ή άλλως θα απορρίπτατε.
Για να στήσετε έναν νέο handler, εκτελέστε
npx tgcloud add handlers/<type>.
endpoints/ — η πλευρά του διακομιστή του Mini App σας. Κάθε αρχείο είναι μια συνάρτηση που καλεί το Mini App με το όνομά της, οπότε το endpoints/getProfile.js απαντά στο POST /api/getProfile. Η πλατφόρμα ελέγχει ότι η κλήση προέρχεται από έναν χρήστη του Telegram μέσα στο Mini App σας πριν εκτελεστεί ο κώδικάς σας, και σας παραδίδει αυτόν τον χρήστη. Το endpoints/ είναι επίπεδο, όπως το handlers/. Δείτε
Endpoints
.
.tgcloud/ — κατάσταση τοπική του μηχανήματος που διαχειρίζεται εξ ολοκλήρου το CLI, συμπεριλαμβανομένων των αποθηκευμένων διαπιστευτηρίων σας. Αγνοείται από το git και δεν πρέπει ποτέ να διαβάζετε ή να γράφετε σε αυτό με το χέρι — χρησιμοποιήστε τις εντολές CLI αντ’ αυτού.
Το σύστημα module
Στο runtime, ένα module μπορεί να δει ακριβώς δύο πράγματα: το SDK της πλατφόρμας και τα άλλα modules στο project σας. Δεν υπάρχουν πακέτα npm, δεν υπάρχει σύστημα αρχείων και δεν υπάρχει δίκτυο εκτός από το fetch του SDK.
Εισαγάγετε τα δικά σας modules όπως θα κάνατε σε οποιοδήποτε JavaScript project — με σχετική διαδρομή, με την επέκταση .js — και το SDK με το όνομά του:
// tgcloud/handlers/message.js
import { users } from '../schema.js'; // το module σχήματος
import { addItem } from '../lib/cart.js'; // ένα lib module
import { format } from '../lib/internal/fmt.js'; // ένθετο lib module
import { db, api, fetch } from 'sdk'; // το SDK της πλατφόρμας
// tgcloud/lib/cart.js
import { format } from './internal/fmt.js'; // ένας γείτονας
import { users } from '../schema.js';
Μια σχετική εισαγωγή πρέπει να τελειώνει σε .js και δεν μπορεί να οδηγήσει έξω από το tgcloud/:
import { users } from '../schema'; // προσθέστε το .js
import config from '../../config.js'; // έξω από το tgcloud/
import { z } from 'zod'; // όχι πακέτα npm
Κάθε module έχει επίσης ένα όνομα — τη διαδρομή του μέσα στο tgcloud/ χωρίς την επέκταση, όπως schema, lib/cart ή handlers/message. Αυτό το όνομα χρησιμοποιεί το npx tgcloud run και η έξοδος του CLI, και ένα module μπορεί να εισαχθεί και από αυτό (from 'lib/cart', χωρίς επέκταση). Προτιμήστε τις σχετικές εισαγωγές στον κώδικά σας. Είναι αυτές που καταλαβαίνει ο editor σας — jump-to-definition, autocomplete, rename.
Ό,τι είναι διαθέσιμο στο runtime είναι ακριβώς δύο πράγματα: sdk και τα submodules του (sdk/db, sdk/api, sdk/fetch) — ολόκληρη η επιφάνεια της πλατφόρμας, δείτε
The SDK
— και τα δικά σας modules στο tgcloud/. Αυτή είναι η πλήρης λίστα. Αν ο κώδικάς σας importάρει οτιδήποτε άλλο, δεν θα επιλυθεί. Αυτός ο περιορισμός είναι που κρατά τα modules γρήγορα στη φόρτωση και ασφαλή στην εκτέλεση.
Handlers
Ένας handler είναι ένα module στο handlers/ του οποίου το default export επικαλείται η πλατφόρμα όταν φτάνει μια αντίστοιχη ενημέρωση.
// handlers/message.js
import { api } from 'sdk';
export default async function (message) {
await api.sendMessage({
chat_id: message.chat.id,
text: `Είπες: ${message.text ?? '(κανένα κείμενο)'}`,
});
}
Ένας handler λαμβάνει το payload της ενημέρωσης ως όρισμά του — η πλατφόρμα ξετυλίγει το
Update
του Telegram για εσάς. Ο handlers/message.js παίρνει το
Message
(δηλ. update.message); ο handlers/callback_query.js παίρνει το
CallbackQuery
; και ούτω καθεξής. Το δεύτερο όρισμα του handler είναι ένα αντικείμενο πλαισίου (context) ανά κλήση, ctx. Μεταφέρει το raw Update ως ctx.update — φτάστε σε αυτό όταν χρειάζεστε κάτι έξω από το payload, όπως το update_id.
Ένας handler μπορεί να είναι async (συνήθως είναι) και μπορεί να επιστρέψει μια τιμή. Φτάνει στο Bot API, στη βάση δεδομένων και σε εξερχόμενο HTTP μέσω του SDK.
Δεν χρειάζεστε μια πραγματική ενημέρωση για να δοκιμάσετε έναν handler. Το npx tgcloud run τον εκτελεί στην πλατφόρμα με το payload που παρέχετε και τον τρέχοντα τοπικό σας κώδικα:
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "γεια" }'
Το όρισμα είναι το payload — το ίδιο αντικείμενο που λαμβάνει ο handler σας — σε JSON5. Για να παρέχετε το ctx του handler (το δεύτερο όρισμά του), προσθέστε --ctx, π.χ. --ctx '{ update: { update_id: 1 } }'. Αυτό τρέχει ενάντια στα τοπικά αρχεία σας, οπότε μπορείτε να δοκιμάσετε αλλαγές πριν τις αναπτύξετε. Δείτε
run
.
Endpoints
Ένα endpoint είναι ένα module στο endpoints/ που καλεί το Mini App σας — το μέρος για οτιδήποτε δεν μπορεί να κάνει το front-end μόνο του, όπως ανάγνωση και εγγραφή στη βάση δεδομένων, κλήση του Bot API, ή επικοινωνία με εξωτερικές υπηρεσίες με τα μυστικά σας (secrets).
// 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('Το προφίλ δεν βρέθηκε', { userId: user.id });
return profile;
}
Το πρώτο όρισμα, input, είναι το αντικείμενο JSON που έστειλε το Mini App. Το δεύτερο, ctx, μεταφέρει ctx.initData — τα
init data
του Mini App (user, chat, start_param, auth_date, …), ήδη επαληθευμένα από την πλατφόρμα. Έχει το ίδιο σχήμα με το Telegram.WebApp.initDataUnsafe στον client, αλλά εδώ μπορείτε να το εμπιστευτείτε. Μια κλήση χωρίς έγκυρα init data απορρίπτεται με 401 πριν εκτελεστεί ο κώδικάς σας, οπότε το ctx.initData.user είναι πραγματικά το άτομο που άνοιξε το Mini App σας. Τα Endpoints μπορούν να καλούνται μόνο με αυτόν τον τρόπο — από το Mini App του bot σας, από έναν χρήστη του Telegram — οπότε δεν υπάρχει δημόσιο API για προστασία και δεν υπάρχει login για δημιουργία.
Ό,τι επιστρέφει το endpoint στέλνεται πίσω ως JSON. Για να αρνηθείτε μια κλήση, throw new EndpointError(description, parameters?). Το Mini App παίρνει την περιγραφή και το αντικείμενο parameters ως έχουν. Αν χρειαστεί να διακλαδωθείτε στο είδος του σφάλματος στο front-end, βάλτε έναν κωδικό στα parameters — { code: 'OUT_OF_STOCK' }. Οποιαδήποτε άλλη εξαίρεση αναφέρεται ως γενικό εσωτερικό σφάλμα, και το πραγματικό μήνυμα πηγαίνει μόνο στα logs σας, οπότε τα εσωτερικά δεν διαρρέουν ποτέ στον client.
Κλήση ενός endpoint από το Mini App
Το Mini App καλεί ένα endpoint με το όνομά του μέσω του Telegram.WebApp.Serverless.call από το επίσημο
Mini Apps SDK
, telegram-web-app.js — το script που το Mini App σας περιλαμβάνει ήδη. Στέλνει τα init data για εσάς και καλεί πίσω με ένα σφάλμα ή το αποτέλεσμα του endpoint:
Telegram.WebApp.Serverless.call('getProfile', { lang: 'el' }, function (err, profile) {
if (err) {
// err.message — η περιγραφή; err.parameters — ό,τι μετέφερε το EndpointError σας
if (err.type === 'ENDPOINT_ERROR') showMessage(err.message);
return;
}
render(profile);
});
Το input είναι προαιρετικό και πρέπει να είναι ένα απλό αντικείμενο. Χωρίς αυτό η κλήση είναι Serverless.call('getProfile', callback).
Το err.type είναι 'ENDPOINT_ERROR' όταν το endpoint σας πέταξε ένα EndpointError. Οποιαδήποτε άλλη τιμή σημαίνει ότι η πλατφόρμα απέρριψε την κλήση ή το endpoint απέτυχε απροσδόκητα, για παράδειγμα 'UNAUTHORIZED' όταν τα init data λείπουν ή είναι άκυρα.
Τα ονόματα των endpoints είναι σαν αναγνωριστικά JavaScript — γράμματα, ψηφία και _, που δεν ξεκινούν με ψηφίο (getProfile, place_order). Στήστε ένα με
npx tgcloud add endpoints/<name>
, και δοκιμάστε το χωρίς το Mini App με
run
, περνώντας τα init data μόνοι σας:
npx tgcloud run endpoints/getProfile '{}' --ctx '{ initData: { user: { id: 1, first_name: "Αννα" } } }'
Τι αναπτύσσεται
Όταν κάνετε npx tgcloud push, το CLI συγκεντρώνει κάθε αρχείο .js κάτω από το schema.js, lib/, handlers/ και endpoints/, και στέλνει αυτό το ακριβές σύνολο ως τον module space του project σας — μαζί με τον φάκελο build του Mini App, αν το tgcloud.jsonc ονομάζει κάποιον (δείτε παρακάτω). Οτιδήποτε υπάρχει στο cloud αλλά λείπει από το project σας αφαιρείται, οπότε η αναπτυγμένη κατάσταση αντικατοπτρίζει πάντα τον φάκελό σας — συμπεριλαμβανομένων των διαγραφών. Τίποτα έξω από το tgcloud/ δεν αναπτύσσεται ως κώδικας — το υπόλοιπο της ρίζας του project είναι δικό σας. Μέσα στο tgcloud/, ένα αρχείο .js που δεν βρίσκεται σε μία από αυτές τις τοποθεσίες (tgcloud/util.js, tgcloud/helpers/x.js) επισημαίνεται, ώστε να μην παραμείνει σιωπηλά μη αναπτυγμένο. Το Markdown, τα dotfiles και το .tgcloud/ δεν αναπτύσσονται ποτέ.
Mini App front-end
Η ρίζα του project είναι δική σας, και είναι το φυσικό μέρος για το front-end του Mini App σας — χτισμένο με React, Vue, Svelte ή απλό HTML, από το Vite ή οποιοδήποτε άλλο bundler. Το Serverless φιλοξενεί το build output του δίπλα στο bot σας. Ένα push αναπτύσσει τον κώδικα και τα στατικά αρχεία μαζί, και η εφαρμογή εξυπηρετείται στο https://app<app_id>.tgcloud.ai/ (το push εκτυπώνει την ακριβή διεύθυνση — χρησιμοποιήστε την ως URL του Mini App σας στο
@BotFather
).
Αναφορά κατάχρησης. Τα Mini Apps στο tgcloud.ai δημοσιεύονται από ανεξάρτητους προγραμματιστές. Για να αναφέρετε παράνομο περιεχόμενο ή κατάχρηση σε ένα subdomain
app<app_id>.tgcloud.ai, στείλτε email στο [email protected] .
Για να ξεκινήσετε, δημιουργήστε πρώτα το front-end, με όποιο εργαλείο θέλετε, μετά προσθέστε το Serverless από πάνω του:
npm create vite@latest example_app # ή create-vue, …
cd example_app
npm create @tgcloud/bot .
Ο δημιουργός project δεν αντικαθιστά ποτέ τα αρχεία σας. Προσθέτει τον φάκελο tgcloud/ δίπλα στο front-end, αναγνωρίζει τον φάκελο build, και προσθέτει ένα script npm run deploy που χτίζει την εφαρμογή και την αναπτύσσει μαζί με το bot.
Ό,τι φιλοξενείται ορίζεται στο tgcloud.jsonc, το αρχείο ρυθμίσεων του project στη ρίζα:
{
"$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 }]
}
}
Μόνο το "source" απαιτείται. Είναι ο φάκελος build, σχετικός με τη ρίζα του project. Τα άλλα κλειδιά του "static" ρυθμίζουν πώς εξυπηρετείται (παρακάτω). Το init δημιουργεί το tgcloud.jsonc για εσάς. Όταν αναγνωρίζει το front-end, συμπληρώνει επίσης το "source":
| Front‑end | Το init ορίζει το "source" σε |
|---|---|
| Vite — React, Vue, Svelte, Solid, … | dist |
| Next.js, Nuxt, SvelteKit, React Router, TanStack Start, Angular | τίποτα — ρυθμίστε πρώτα ένα στατικό export, μετά ορίστε το "source" στον φάκελό του εσείς |
Με οποιοδήποτε άλλο στήσιμο, ορίστε το "source" στον φάκελο build μόνοι σας.
Η γραμμή $schema δίνει στον editor σας αυτόματη συμπλήρωση και επικύρωση για κάθε κλειδί. Το αρχείο είναι JSON με σχόλια και επιτρεπόμενα κόμματα στο τέλος. Αν ένα άλλο εργαλείο σας πρέπει να το διαβάσει ως απλό JSON, ονομάστε το tgcloud.json — τότε πρέπει να είναι αυστηρό JSON.
Χτίστε πριν κάνετε push. Το push μεταφορτώνει τον φάκελο ακριβώς όπως είναι στον δίσκο — εκτός από αρχεία και φακέλους των οποίων το όνομα ξεκινά με τελεία, τα οποία παραλείπονται — και δεν εκτελεί ποτέ το build σας, οπότε ένα απλό npx tgcloud push μετά την επεξεργασία του front-end θα ανέπτυσσε το προηγούμενο build. Χρησιμοποιήστε το npm run deploy (εκτελεί το script build σας, μετά tgcloud push), ή χτίστε μόνοι σας πρώτα.
Οι επιλογές εξυπηρέτησης:
spa— εξυπηρέτηση τουindex.htmlγια διαδρομές που δεν υπάρχουν και δεν έχουν επέκταση αρχείου, ώστε ο client-side router να λειτουργεί κατά την επαναφόρτωση. Διαδρομές με επέκταση εξακολουθούν να παίρνουν404.immutable— μοτίβα για αρχεία με content-hash (το/assets/*του Vite) που οι browsers μπορούν να αποθηκεύσουν για έναν χρόνο. Χρησιμοποιήστε το μόνο για αρχεία με content-hash. Οποιοδήποτε άλλο αρχείο ταιριάξει εδώ θα έμενε παλιό (stale) στους browsers για έναν χρόνο, και δεν υπάρχει τρόπος να το ανακαλέσετε.headers— κεφαλίδες απόκρισης για τις διαδρομές που ταιριάζει ένας κανόνας (μια ακριβής διαδρομή, ή ένα πρόθεμα που τελειώνει σε/*). Μια τιμήnullαφαιρεί μια κεφαλίδα.redirects— κανόνες ανακατεύθυνσης, όπου κερδίζει η πρώτη αντιστοίχιση. Ένα υπάρχον αρχείο κερδίζει πάντα έναν κανόνα. Τοstatusείναι301εκτός αν το ορίσετε εσείς."static": "dist"— συντομογραφία για{ "source": "dist" }όταν δεν χρειάζεστε τίποτα άλλο."static": false— αφαίρεση του αναπτυγμένου site στο επόμενο push. Η διαγραφή του κλειδιού απλώς σταματά αυτό το project από το να διαχειρίζεται στατικά αρχεία, και ό,τι έχει αναπτυχθεί παραμένει.
Το site εξυπηρετείται από τη ρίζα του δικού του host, οπότε η προεπιλεγμένη base του bundler σας (/) είναι σωστή. Το Telegram Web ανοίγει τα Mini Apps σε ένα iframe, οπότε μην προσθέσετε X-Frame-Options ή μια περιοριστική πολιτική frame-ancestors.
Ο ίδιος host απαντά στα
endpoints
σας κάτω από το /api/ — εκεί πηγαίνει το Telegram.WebApp.Serverless.call('getProfile', …). Μην βάζετε δικά σας αρχεία κάτω από το /api/ στον φάκελο build.
Η βάση δεδομένων
Κάθε bot παίρνει τη δική του βάση δεδομένων — ένα κατάστημα βασισμένο σε SQLite που διατηρείται μεταξύ των κλήσεων και είναι διαθέσιμο σε κάθε module μέσω του db. Περιγράφετε τους πίνακές σας στο schema.js με ένα μικρό, τυποποιημένο DSL; τους διαβάζετε και τους γράφετε με έναν fluent query builder; και τους εξελίσσετε με ελεγμένες μεταναστεύσεις.
Ξέρετε το Drizzle ORM ; Τότε ξέρετε ήδη πώς να μιλήσετε στη βάση δεδομένων εδώ. Το σχήμα DSL και ο query builder ακολουθούν το Drizzle — οι column builders,
select().from().where(), οι τελεστές,onConflictDoUpdate,.returning(), και το tagsqlσυμπεριφέρονται όλα όπως περιμένετε, οπότε η ανάγνωση και η εγγραφή δεδομένων είναι το οικείο API που ήδη χρησιμοποιείτε. Απλώς το εισάγετε από τοsdk/db, και μερικές ιδιαιτερότητες της πλατφόρμας (κυρίως χωρίς ξένα κλειδιά ) επισημαίνονται εκεί που προκύπτουν.
Δήλωση πινάκων
Οι πίνακες είναι named exports στο schema.js. Η κλήση του table() χτίζει μια περιγραφή κατά τη φόρτωση (δεν αγγίζει τη βάση δεδομένων); η πλατφόρμα ανακαλύπτει τους εξαγόμενους πίνακες όταν αναπτύσσετε το schema.js και μεταναστεύει τη βάση δεδομένων για να ταιριάξει.
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είναι το όνομα του πίνακα SQL;columnsείναι ένας χάρτης JS property → ορισμός στήλης;extrasείναι ένα προαιρετικό callback(t) => ({ … })όπου τοtεκθέτει τις στήλες (t.createdείναι μια αναφορά σε αυτή τη στήλη) — δηλώστε ευρετήρια (indexes) και περιορισμούς επιπέδου πίνακα εδώ.
Τύποι στηλών
| Εργοστάσιο | Τύπος SQLite | Σημειώσεις |
|---|---|---|
text() |
TEXT | |
integer() |
INTEGER | |
real() |
REAL | alias float() |
numeric() |
NUMERIC | |
blob() |
BLOB | διαβάζει/γράφει Uint8Array |
boolean() |
INTEGER | αποθηκεύεται ως 0/1, διαβάζεται ως true/false |
json() |
TEXT | αυτόματο JSON.stringify / JSON.parse |
Το όρισμα ονόματος στήλης είναι προαιρετικό — παραλείψτε το και χρησιμοποιείται το JS κλειδί. Η επιλογή
modeελέγχει πώς μετατρέπονται οι τιμές μεταξύ SQLite και JavaScript.
mode |
Αποθηκεύεται ως | JS τιμή |
|---|---|---|
boolean |
INTEGER 0/1 | boolean |
json |
TEXT (JSON) | οποιοδήποτε αντικείμενο/πίνακας |
timestamp |
INTEGER (unix seconds) | Date |
timestamp_ms |
INTEGER (unix ms) | Date |
bytes |
BLOB | Uint8Array |
Τα boolean() και json() είναι συντομογραφίες για integer(name, { mode: 'boolean' }) και text(name, { mode: 'json' }).
Ένα blob() διαβάζει και γράφει ένα Uint8Array — το runtime δεν έχει Node Buffer (και το Buffer είναι υποκλάση του Uint8Array, οπότε αυτός είναι ο φορητός βασικός τύπος). Το mode παραπάνω διέπει μόνο πώς κωδικοποιείται μια τιμή, ανεξάρτητα από τον τύπο αποθήκευσης της στήλης: οπότε το blob('col', { mode: 'json' }) είναι το αντίστοιχο BLOB του json() — η ίδια κωδικοποίηση JSON, διατηρημένη σε μια στήλη BLOB αντί για TEXT.
TLDR: Το
blob()χρησιμοποιείUint8Array. Τοmodeελέγχει την κωδικοποίηση, όχι την αποθήκευση, οπότε τοblob(..., { mode: 'json' })αποθηκεύει JSON ως BLOB, ενώ τοjson()το αποθηκεύει ως TEXT.
Τροποποιητές στηλών
Οι τροποποιητές στηλών συνδέονται σε μια στήλη.
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' }) // ή 'virtual'
text('email').deprecated('αντικαταστάθηκε από login') // επισημαίνει τη στήλη για αφαίρεση
Το
.default()σε μια στήληjson()κωδικοποιεί την τιμή για εσάς; έναsql`…`default περνάει αυτούσιο. Το.deprecated()είναι τερματικό — δείτε Migrations .
Ευρετήρια και περιορισμοί
Τα ευρετήρια και οι περιορισμοί δηλώνονται στο extras callback, όπου οι στήλες είναι στο 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})`) , // ευρετήριο έκφρασης
active: index('idx_active').on(t.userId).where(sql`done = 0`) , // μερικό ευρετήριο
}));
Οι τροποποιητές επιπέδου πίνακα συνδέονται μετά το table(...): .strict(), .withoutRowid(), .deprecated('αιτιολογία').
Χωρίς ξένα κλειδιά (No foreign keys)
Το runtime τρέχει με το PRAGMA foreign_keys off. Ένα δηλωμένο ξένο κλειδί θα ήταν σιωπηλά αδρανές — χωρίς cascades, χωρίς προστασία ορφανών — το οποίο είναι χειρότερο από το να μην έχετε καθόλου, οπότε το DSL το καθιστά αδύνατο. Συγκεκριμένα, το .references() και το foreignKey() επιπέδου πίνακα πετάνε εξαίρεση όταν δηλώνονται, και ένα σχήμα που τα χρησιμοποιεί δεν θα αναπτυχθεί.
Θα πρέπει να μοντελοποιείτε τις σχέσεις με απλές στήλες (userId: integer('user_id')) και να επιβάλλετε την ακεραιότητα στον κώδικα της εφαρμογής σας: εισάγετε γονείς πριν από παιδιά, διαγράψτε παιδιά πριν από γονείς, χειριστείτε σωστά τα σφάλματα και καθαρίστε τα ορφανά με ένα LEFT JOIN … WHERE parent.id IS NULL όταν χρειάζεται.
Η απουσία ξένων κλειδιών είναι ένας εσκεμμένος περιορισμός, όχι παράβλεψη. Λάβετε το υπόψη από νωρίς όταν σχεδιάζετε το bot σας.
Ερωτήματα (Querying)
Το db είναι ένας fluent query builder. Κάθε ερώτημα είναι ασύγχρονο — οι τερματικές μέθοδοι (.all(), .get(), .values(), .run()) επιστρέφουν Promises, οπότε πάντα 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(); // όλες οι γραμμές
await db.select().from(todos).where(eq(todos.id, 1)).get(); // πρώτη γραμμή ή null
await db.select().from(todos).values(); // γραμμές ως πίνακες τιμών
await db.select().from(todos)
.where(and(eq(todos.userId, uid) , eq(todos.done, false)))
.orderBy(desc(todos.priority) , asc(todos.id))
.limit(10).offset(20)
.all();
// custom προβολή: { 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();
// μέτρηση γραμμών — βοηθός, όχι builder terminal:
await db.$count(todos); // όλες οι γραμμές
await db.$count(todos, eq(todos.done, false)); // με φίλτρο
- Chainable:
.where(),.orderBy(),.limit(),.offset(),.groupBy(),.having(),.distinct(). - Τερματικά:
.all(),.get(),.values(). - Μέτρηση γραμμών με
db.$count(table, where?)(ήcount()σε προβολή).
Εισαγωγή, ενημέρωση, διαγραφή:
await db.insert(todos).values({ userId: 1, text: 'Αγορά γάλακτος' }).run();
await db.insert(todos).values([{ text: 'Α' }, { text: 'Β' }]).run(); // παρτίδα
await db.insert(todos).values({ text: 'Χ' }).returning().run(); // RETURNING *
await db.insert(users).values({ tgId: 42, name: 'Αννα' })
.onConflictDoUpdate({ target: users.tgId, set: { name: 'Αννα' } }).run();
await db.update(todos).set({ done: true }).where(eq(todos.id, 1)).run();
await db.delete(todos).where(eq(todos.id, 1)).run();
Σημειώστε ότι μια εισαγωγή παρτίδας είναι μία δήλωση, οπότε περιορίζεται από το όριο μεταβλητών της SQLite (
γραμμές × στήλες); αν το ξεπεράσετε, η εισαγωγή βγάζει σφάλμα — σπάστε το σε κομμάτια μόνοι σας.
Οι τελεστές εισάγονται από το 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) με πολλά ορίσματα είναι το ίδιο με το and(a, b). Το δεύτερο όρισμα μιας σύγκρισης είναι μια τιμή από προεπιλογή, αλλά μπορεί να είναι άλλη στήλη ή sql`…` — π.χ. eq(a.x, b.y). Τα aggregates (count/sum/avg/min/max) είναι fragments sql για μια προβολή .select({ … }).
Raw SQL — όταν ο builder δεν αρκεί, πέστε σε raw SQL. Το mode επιλέγεται ανά μέθοδο: db.run για εγγραφές, db.all για πολλές γραμμές, db.get για μία γραμμή, db.values για γραμμές ως τοποθεσιακούς πίνακες.
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, 'αγορά γάλακτος']]
Μια εγγραφή επιλύεται σε ένα αποτέλεσμα εκτέλεσης — rowsAffected, lastInsertRowid, και rows, τα οποία μεταφέρουν μια ρήτρα RETURNING αν το SQL είχε μία. Φτάστε στο db.values όταν ένα ερώτημα προβάλλει δύο στήλες με το ίδιο όνομα (ένα join όπως SELECT u.id, o.id): το db.all κλειδώνει τις γραμμές με το όνομα της στήλης, οπότε το δεύτερο id θα αντικαθιστούσε το πρώτο.
Το tag sql`…` μετατρέπει το ${value} σε δεσμευμένη παράμετρο και το ${table.column} σε αναγνωριστικό, και συνενώνει ένθετα fragments sql. Χρησιμοποιήστε το sql.raw('…') για ένα literal χωρίς παραμέτρους.
Τα raw ερωτήματα δεν είναι δεμένα σε πίνακα, οπότε οι γραμμές τους επιστρέφουν χωρίς μετατροπή mode — ένα boolean είναι
0/1, μια στήλη JSON είναι string, ένα timestamp είναι αριθμός. Μόνο ο builder που είναι δεμένος σε πίνακα μετατρέπει τις τιμές.
Μεταναστεύσεις (Migrations)
Η βάση δεδομένων σας αλλάζει καθώς το bot σας μεγαλώνει. Η πλατφόρμα το κρατά ασφαλές διαχωρίζοντας την ανάπτυξη κώδικα από την αλλαγή δεδομένων, και ταξινομώντας κάθε αλλαγή σχήματος ανάλογα με το πόσο ριψοκίνδυνη είναι.
Η ανάπτυξη δεν αγγίζει ποτέ τη βάση δεδομένων. Όταν κάνετε npx tgcloud push σε ένα αλλαγμένο schema.js, η πλατφόρμα καταγράφει το νέο σχήμα και σας λέει τι θα άλλαζε η βάση δεδομένων — αλλά δεν εφαρμόζει τίποτα:
npx tgcloud push # ανάπτυξη schema.js; αναφέρει εκκρεμείς αλλαγές DB
npx tgcloud migrate # έλεγχος και εφαρμογή τους
Το npx tgcloud migrate υπολογίζει τη διαφορά μεταξύ του σχήματός σας και της live βάσης δεδομένων και σας καθοδηγεί σε αυτό. Πάντα σας ζητείται πριν εφαρμοστεί οτιδήποτε. Αυτό σημαίνει ότι μια ανάπτυξη κώδικα ρουτίνας δεν μπορεί ποτέ να προκαλέσει μια μετανάστευση δεδομένων κατά λάθος.
Κάθε εκκρεμής αλλαγή μεταφέρει μια κατάσταση που καθορίζει πώς τη χειρίζεται το migrate:
| Κατάσταση | Σημασία | Στο migrate |
|---|---|---|
| safe | Προσθετική και μη μπλοκαριστική — ένας νέος πίνακας, στήλη ή ευρετήριο | Εφαρμόζονται μαζί σε ένα βήμα, με επιβεβαίωση |
| warning | Πιθανώς καταστροφική ή αργή — διαγραφή κάποιου στοιχείου, ή ευρετήριο σε τεράστιο πίνακα | Παρουσιάζονται μία προς μία, η καθεμία επιβεβαιώνεται ξεχωριστά |
| manual | Δεν μπορεί να γίνει αυτόματα — π.χ. αλλαγή του τύπου μιας στήλης | Εμφανίζεται με καθοδήγηση; την εκτελείτε με το χέρι |
| undocumented | Υπάρχει στη βάση δεδομένων αλλά όχι στο σχήμα σας | Εμφανίζεται για ενημέρωση; δεν εφαρμόζεται |
Οι ασφαλείς αλλαγές είναι γρήγορες και αναστρέψιμες στο πνεύμα, οπότε περνούν μαζί. Κάθε προειδοποίηση είναι μια εσκεμμένη, ατομική επιβεβαίωση — δεν υπάρχει “εφαρμογή όλων” για καταστροφικές αλλαγές.
Οι χειροκίνητες αλλαγές έρχονται με μια αιτιολογία και μια προτεινόμενη ενέργεια. Το migrate τελειώνει με μια σύνοψη: πόσες αλλαγές εφαρμόστηκαν, παραλείφθηκαν, περιμένουν χειροκίνητη διόρθωση, ή δεν είναι στο σχήμα σας.
Δείτε
migrateγια τις σημαίες (--dry-run,--safe,--yes,--local).
Αφαίρεση πραγμάτων
Η διαγραφή ενός πίνακα ή στήλης από το schema.js δεν τους διαγράφει — αυτό θα καθιστούσε μια τυχαία διαγραφή καταστροφική. Για να αφαιρέσετε κάτι, επισημάνετέ το ως deprecated:
// διαγραφή μιας στήλης
text('email').deprecated('αντικαταστάθηκε από login')
// διαγραφή ολόκληρου πίνακα
export const oldSessions = table('old_sessions', { /* … */ }).deprecated('αχρησιμοποίητο');
Στο επόμενο migrate, το deprecated αντικείμενο εμφανίζεται ως διαγραφή με κατάσταση warning που επιβεβαιώνετε ξεχωριστά. Μόλις φύγει, αφαιρέστε τη δήλωση.
Αλλαγή του τύπου μιας στήλης
Οι αλλαγές τύπου είναι χειροκίνητες — η SQLite δεν μπορεί πάντα να τις κάνει επιτόπου (in place), και ο εξαναγκασμός (coercion) των υπαρχουσών τιμών είναι θέμα κρίσης. Το migrate θα δείξει την αλλαγή και τη λογική της; εκτελέστε την εσείς με raw SQL (db.run(...)), συνήθως δημιουργώντας μια νέα στήλη ή πίνακα, αντιγράφοντας δεδομένα και κάνοντας swap.
Το SDK
Στο runtime ένα module έχει μια βιβλιοθήκη: sdk. Πακετάρει τα τρία πράγματα που χρειάζεται ένα bot backend — μια βάση δεδομένων, το Telegram Bot API, και εξερχόμενο HTTP — χωρίς τίποτα προς εγκατάσταση και χωρίς διαπιστευτήρια προς ρύθμιση. Η βάση δεδομένων (db) καλύπτεται στο
The database
; αυτή η ενότητα καλύπτει τα api, fetch και το global console.
import { db, api, fetch, InputFile, BotApiError, EndpointError } from 'sdk'; // ολόκληρη η επιφάνεια
// ή από submodules:
import { table, integer, text, eq, sql } from 'sdk/db';
import { api } from 'sdk/api';
import { fetch } from 'sdk/fetch';
| Εισαγωγή | Τι είναι |
|---|---|
db |
Βάση δεδομένων — query builder και σχήμα DSL → The database |
api |
Telegram Bot API — api.sendMessage(...) →
below
|
fetch |
Εξερχόμενο HTTP → below |
InputFile |
Ένα αρχείο (bytes + όνομα) για μεταφόρτωση — λειτουργεί και με api και με fetch →
Files
|
EndpointError |
Αρνήθείτε την κλήση ενός Mini App με ένα μήνυμα που μπορεί να δείξει → Endpoints |
Εισαγάγετε τα δικά σας modules project με σχετική διαδρομή με την επέκταση .js (from '../schema.js', from '../lib/cart.js'). Δείτε
The module system
.
Το Bot API
Το api σας δίνει ολόκληρο το
Telegram Bot API
. Καλέστε οποιαδήποτε μέθοδο ως api.<method>(params). Κάθε τρέχουσα — και μελλοντική — μέθοδος Bot API λειτουργεί χωρίς απαιτούμενη ενημέρωση SDK.
import { api } from 'sdk';
const me = await api.getMe(); // → το ξετυλιγμένο αποτέλεσμα
await api.sendMessage({ chat_id: id, text: 'Γεια!' });
await api.editMessageText({ chat_id, message_id, text: 'Ενημερώθηκε' });
await api.answerCallbackQuery({ callback_query_id, text: 'Έγινε' });
Ο φάκελος απόκρισης ξετυλίγεται. Το Bot API κανονικά τυλίγει τα αποτελέσματα σε { ok: true, result: … }. Το api επιστρέφει το result απευθείας — το getMe() επιλύεται στο αντικείμενο χρήστη, όχι σε έναν φάκελο. Οι παράμετροι χρησιμοποιούν τα δικά τους snake_case ονόματα του Bot API (chat_id, message_id, reply_markup, …).
Οι αποτυχίες πετούν BotApiError. Όταν το Bot API επιστρέφει { ok: false }, η κλήση πετά ένα BotApiError αντί να επιστρέψει μια falsy τιμή, ώστε να μην μπορείτε να το αγνοήσετε κατά λάθος. Το σφάλμα μεταφέρει .code (τον κωδικό σφάλματος του Bot API), .description (το ανθρώπινα αναγνώσιμο μήνυμα), .method (ποια μέθοδος απέτυχε) και .parameters (επιπλέον δεδομένα όπως retry_after σε ένα 429, ή migrate_to_chat_id). Πιάστε το για να χειριστείτε μια αναμενόμενη αποτυχία και πετάξτε ξανά τα υπόλοιπα:
import { api, BotApiError } from 'sdk';
try {
await api.deleteMessage({ chat_id, message_id });
} catch (e) {
if (e instanceof BotApiError && e.code === 400) {
// 400 = το μήνυμα έχει ήδη φύγει; αυτό είναι εντάξει εδώ.
} else {
throw e;
}
}
Αρχεία
Εργαστείτε με bytes αρχείων, όχι μόνο με file_ids — ανεβάστε νέα αρχεία και κατεβάστε υπάρχοντα.
Μεταφόρτωση — InputFile. Τυλίξτε raw bytes σε ένα InputFile (ένας απλός κάτοχος: bytes, ένα όνομα αρχείου, και ένας προαιρετικός MIME type) και περάστε το οπουδήποτε μια μέθοδος Bot API αναμένει αρχείο:
import { api, InputFile } from 'sdk';
const doc = new InputFile(bytes, 'report.pdf', { type: 'application/pdf' });
await api.sendDocument({ chat_id, document: doc });
// αρχεία ένθετα μέσα σε παραμέτρους λειτουργούν επίσης — π.χ. ένα άλμπουμ:
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 μεταδίδονται (streamed) στο Telegram ως πραγματική μεταφόρτωση multipart — το αρχείο δεν γίνεται buffer ολόκληρο σε μια ενιαία κλήση στην πλατφόρμα — αλλά τα όρια του Bot API του Telegram εξακολουθούν να ισχύουν: ένα bot μπορεί αυτή τη στιγμή να ανεβάσει έως και 50 MB ανά αρχείο (και 10 MB για μια φωτογραφία).
Λήψη — με file_id. Δύο βοηθοί τραβάνε τα bytes ενός αρχείου πίσω. Η πλατφόρμα επιλύει το file_id για εσάς (τρέχει getFile και ανακτά το περιεχόμενο στο παρασκήνιο), οπότε δεν αγγίζετε ποτέ το bot token ή ένα download URL:
const bytes = await api.getFileContent(file_id); // → Uint8Array (ολόκληρο το αρχείο)
const { file, body } = await api.getFileStream(file_id); // stream it instead
file.file_unique_id; // οι πληροφορίες getFile
for await (const chunk of body) { /* Uint8Array */ } // ή buffer it: await stream.bytes()
Το Bot API του Telegram περιορίζει τις λήψεις
getFileστα 20 MB, οπότε αυτό είναι το ταβάνι και για τους δύο βοηθούς.
HTTP
Το fetch είναι ένας client τύπου fetch για την κλήση του έξω κόσμου — τρίτα APIs, webhooks, οτιδήποτε μέσω 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: 'Παύλος' }) ,
});
if (!res.ok) throw new Error(res.statusText);
const data = await res.json();
Η απόκριση αντικατοπτρίζει το web platform: res.status, res.statusText, res.ok (true για 200–299), res.url, res.headers (.get(), .has(), .keys(), .entries()), και αναγνώστες σώματος await res.json() / await res.text().
Μπορείτε επίσης να διαβάσετε το σώμα σταδιακά ως stream —
for await (const chunk of res.body) { … }— που είναι ο τρόπος που καταναλώνετε server-sent events ή output token-by-token από AI APIs.
Οι βοηθοί σώματος ορίζουν το αντίστοιχο Content-Type για εσάς:
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('γεια') }); // text/plain
Τα σώματα αιτημάτων μπορούν επίσης να μεταφέρουν αρχεία και raw bytes: περάστε ένα Uint8Array, ένα InputFile, ή ένα FormData (για multipart/form-data) ως body, και τα bytes μεταδίδονται έξω — οπότε μπορείτε να ανεβάσετε σε ένα τρίτο API με τον ίδιο τρόπο που στέλνετε αρχεία στο Telegram:
import { fetch, FormData, InputFile } from 'sdk';
const form = new FormData();
form.append('file', new InputFile(bytes, 'photo.jpg', { type: 'image/jpeg' }));
form.append('caption', 'από το bot μου');
const res = await fetch('https://api.example.com/upload', { method: 'POST', body: form });
Κατά τα άλλα συμπεριφέρεται όπως το τυπικό fetch που ήδη γνωρίζετε, με δύο περιορισμούς:
- Η απόκριση διαβάζεται ως κείμενο —
await res.json()/await res.text(), ή μεταδίδεται κομμάτι-κομμάτι μεres.body. - Η συνολική απόκριση περιορίζεται στα 30 MB. Αυτό το όριο καλύπτει ολόκληρη την απόκριση — η μετάδοση με
res.bodyσάς επιτρέπει να επεξεργαστείτε ένα μεγάλο σώμα σταδιακά, αλλά δεν αυξάνει το όριο.
Logging
Το τυπικό global console είναι διαθέσιμο — τίποτα για εισαγωγή, είναι απλώς εκεί όπως σε οποιαδήποτε JavaScript. Η έξοδός του καταγράφεται και εμφανίζεται από το npx tgcloud run, το οποίο το καθιστά το κύριο εργαλείο αποσφαλμάτωσής σας κατά την ανάπτυξη.
console.log('επεξεργασία', { chatId: id }); // log / debug — απλό
console.info('ξεκίνησε'); // info — μπλε
console.warn('ρυθμός περιορισμένος'); // warn — κίτρινο
console.error(err); // error — κόκκινο, με stack trace
Κάθε γραμμή επισημαίνεται με το [file:line] από όπου προήλθε. Το console.error και το console.trace προσθέτουν ένα πλήρες stack, ενώ το console.warn όχι. Όταν κάνετε npx tgcloud run σε ένα module, αυτές οι γραμμές εκτυπώνονται με ένα χρωματιστό πρόθεμα ανά επίπεδο, τον χρόνο από την έναρξη της εκτέλεσης, και την προέλευση — δείτε
run
.
Command-line interface
Το tgcloud είναι η γέφυρα μεταξύ του φακέλου του project σας και του cloud. Στήνει projects, σας δείχνει τι άλλαξε, αναπτύσσει, εκτελεί modules, και εφαρμόζει μεταναστεύσεις βάσης δεδομένων. Χρειάζεται
Node.js
18 ή νεότερο. Δύο τρόποι για να το πάρετε:
# Προτεινόμενο — δημιουργία project με το CLI εγκατεστημένο σε αυτό:
npm create @tgcloud/bot example_bot
# Ή εγκατάσταση του CLI καθολικά και init ενός κενού φακέλου:
npm install -g @tgcloud/cli
tgcloud init
Το πακέτο npm είναι @tgcloud/cli, και η εντολή που εγκαθιστά είναι tgcloud. Κάθε εντολή λειτουργεί από οποιονδήποτε υποκατάλογο του project σας.
| Εντολή | Σκοπός |
|---|---|
init
|
Στήσιμο νέου project στον τρέχοντα φάκελο |
add
|
Στήσιμο νέου module (handler, endpoint ή lib module) |
login
|
Σύνδεση του project σε ένα bot (αποθηκεύει το διακριτικό) |
status
|
Εμφάνιση τι άλλαξε τοπικά vs. στο cloud |
diff
|
Εμφάνιση αλλαγών γραμμή προς γραμμή |
push
|
Ανάπτυξη αλλαγμένων modules και build του Mini App στο cloud |
migrate
|
Εφαρμογή αλλαγών σχήματος στη βάση δεδομένων |
run
|
Εκτέλεση module στην πλατφόρμα χωρίς ανάπτυξη |
fetch
|
Έλεγχος τι έχει αναπτυχθεί, χωρίς αλλαγή τοπικών αρχείων |
pull
|
Ευθυγράμμιση τοπικών αρχείων με το cloud |
reset
|
Απόρριψη τοπικών αλλαγών; επαναφορά από την κατάσταση cloud |
webhook
|
Επιθεώρηση και επανασυγχρονισμός του webhook που διαχειρίζεται η πλατφόρμα |
completion
|
Εκτύπωση script συμπλήρωσης shell (bash/zsh/fish) |
upgrade
|
Μετακίνηση layout project προ-0.2.0 στο tgcloud/ |
Authentication
Ένα project είναι δεμένο με ένα bot μέσω του διακριτικού του, το οποίο έχει τη μορφή app<id>:<secret>. Το μέρος app<id> είναι δημόσιο και μπορεί να εκτυπωθεί; το μυστικό δεν εμφανίζεται ποτέ σε logs ή σφάλματα.
Το διακριτικό επιλύεται με αυτή τη σειρά:
TGCLOUD_TOKENπεριβαλλοντική μεταβλητή — για CI; δεν γράφεται ποτέ στον δίσκο..tgcloud/credentials— γραμμένο από τοnpx tgcloud login.- Κανένα — ένα σφάλμα που σας οδηγεί στο
npx tgcloud login.
Το CLI δεν ζητά ποτέ διακριτικό κατά τη διάρκεια μιας εντολής. Το login είναι πάντα το ρητό βήμα login. Αν ένα αποθηκευμένο διακριτικό καταστεί άκυρο, το CLI το αφαιρεί και σας ζητά να κάνετε ξανά login.
init
npx tgcloud init
Στήνει ένα νέο project στον τρέχοντα κατάλογο: τον φάκελο tgcloud/ με schema.js, lib/, handlers/ και έναν αρχικό handler, συν tgcloud.jsonc, AGENTS.md, docs/ και τον φάκελο κατάστασης .tgcloud/. Εκτελέστε το στη ρίζα ενός υπάρχοντος front-end project επίσης — προσθέτει τον φάκελο tgcloud/ και τα docs, και δεν αντικαθιστά ποτέ τα αρχεία σας. Αν αναγνωρίσει το front-end, δείχνει επίσης το tgcloud.jsonc στον φάκελο build και προσθέτει ένα script deploy (build, μετά push) στο package.json σας — μόνο αν δεν έχετε ήδη script deploy. Δείτε
Mini App front‑end
.
Το init αρνείται να δημιουργήσει ένα project μέσα σε ένα άλλο project. Η επανεκτέλεσή του στη ρίζα ενός project είναι εντάξει και απλώς προσθέτει ό,τι λείπει.
add
npx tgcloud add <target>
Στήνει ένα μόνο νέο module, συνδεδεμένο και έτοιμο για επεξεργασία — handler, endpoint ή lib/ module.
npx tgcloud add handlers/callback_query # νέος handler ενημερώσεων
npx tgcloud add endpoints/getProfile # νέο endpoint για το Mini App
npx tgcloud add lib/cart # νέο κοινόχρηστο module
Σημειώστε ότι το
addδεν αντικαθιστά ποτέ ένα υπάρχον αρχείο.
Το <target> είναι η διαδρομή του module (μια τελική επέκταση .js είναι προαιρετική). Για τους handlers/, το όνομα πρέπει να είναι τύπος ενημέρωσης Telegram. Η πλατφόρμα διαφημίζει το έγκυρο σύνολο, οπότε ένα άκυρο όνομα απορρίπτεται εξαρχής. Τα handlers/ και endpoints/ είναι επίπεδα, ενώ το lib/ μπορεί να είναι ένθετο (lib/payments/stripe).
Το όνομα του module απαιτείται. Η παροχή μόνο του καταλόγου είναι σφάλμα — αλλά βοηθητικό: για τους handlers/ παραθέτει τους τύπους ενημερώσεων που δεν έχετε ήδη, ώστε να μπορείτε να αντιγράψετε έναν.
$ npx tgcloud add handlers
Σφάλμα: Ορίστε ένα όνομα, π.χ. "handlers/callback_query". Διαθέσιμοι τύποι handlers/: callback_query, inline_query, chat_member, …
Η συμπλήρωση
<Tab>προσφέρει το ίδιο σύνολο — δείτεcompletion.
Το παραγόμενο αρχείο έχει ένα ζωντανό export default, οπότε ο handler είναι ενεργός μόλις κάνετε ανάπτυξη — δεν υπάρχει τίποτα για uncomment. Αναπτύξτε το νέο module με
push
.
login
npx tgcloud login
Ζητά το διακριτικό πρόσβασης CLI σας — από
@BotFather
→ το bot σας → Serverless → CLI Access → Access token, ένα διαφορετικό διακριτικό από το
API token του bot σας
— το επικυρώνει στην πλατφόρμα, και το αποθηκεύει στο .tgcloud/credentials.
Το
loginείναι η μόνη εντολή που ζητά διακριτικό, και απαιτεί διαδραστικό τερματικό. Στο CI, ορίστε τοTGCLOUD_TOKENαντ’ αυτού.
status
npx tgcloud status
Εμφανίζει, ανά αρχείο, τι άλλαξε μεταξύ του καταλόγου εργασίας σας και του αναπτυγμένου αντιγράφου: τροποποιημένο, νέο, διαγραμμένο, αμετάβλητο. Λειτουργεί offline, συγκρίνοντας με την κατάσταση του cloud από την τελευταία σας push, pull ή fetch. Προειδοποιεί επίσης για αδέσποτα αρχεία .js μέσα στο tgcloud/ που δεν θα αναπτυχθούν.
Για ένα project με Mini App, το status εμφανίζει επίσης τι άλλαξε στον φάκελο build του από την τελευταία ανάπτυξη.
diff
npx tgcloud diff
Όπως το status, αλλά δείχνει τις πραγματικές διαφορές γραμμή προς γραμμή για τα τροποποιημένα modules. Επίσης offline.
push
npx tgcloud push [files...]
Αναπτύσσει το project σας στο cloud σε μία ατομική παρτίδα.
Με κανένα όρισμα, αναπτύσσει ολόκληρο το project, και η αναπτυγμένη κατάσταση γίνεται για να αντικατοπτρίζει ακριβώς τον φάκελό σας — modules που διαγράψατε τοπικά αφαιρούνται στο cloud.
Με ορίσματα αρχείων ή καταλόγων (npx tgcloud push tgcloud/handlers/message.js, npx tgcloud push handlers/), αναπτύσσει μόνο τις αλλαγές σε αυτά τα αρχεία. Ένα στοχευμένο push δεν διαγράφει ποτέ άλλα modules. Ένας στόχος μπορεί να γραφτεί ως διαδρομή αρχείου (tgcloud/handlers/message.js), η διαδρομή μέσα στο tgcloud/ (handlers/message.js) ή το όνομα module (handlers/message) — όλα σημαίνουν το ίδιο module.
Η μία επιλογή του είναι το --force — για παράκαμψη του ελέγχου ταυτόχρονης εκτέλεσης και αντικατάσταση όποιου υπάρχει στο cloud. Χρησιμοποιήστε το μόνο όταν είστε σίγουροι (δείτε
Staying in sync
).
Αν το tgcloud.jsonc ονομάζει έναν στατικό φάκελο, ένα πλήρες push τον αναπτύσσει στην ίδια παρτίδα — μόνο τα αρχεία που άλλαξαν μεταφορτώνονται — και εκτυπώνει τη διεύθυνση του site. Χτίστε πρώτα, γιατί το push δεν εκτελεί ποτέ το build σας (npm run deploy τα κάνει και τα δύο). Το npx tgcloud push dist/ αναπτύσσει μόνο τα στατικά αρχεία, και ένα push που ονομάζει μόνο modules τα αφήνει άθικτα.
Μετά από μια ανάπτυξη, αν το schema.js άλλαξε και η βάση δεδομένων δεν είναι συγχρονισμένη, το push εκτυπώνει μια σύνοψη των εκκρεμών αλλαγών και προτείνει npx tgcloud migrate. Δεν τις εφαρμόζει ποτέ μόνο του.
migrate
npx tgcloud migrate
Εφαρμόζει τις αλλαγές σχήματός σας στη βάση δεδομένων. Υπολογίζει τη διαφορά μεταξύ του schema.js και της live βάσης δεδομένων, μετά σας καθοδηγεί σε αυτό βήμα-βήμα με έναν μετρητή [N/M] που τρέχει:
- Μια σύντομη σύνοψη όλων των εκκρεμών αλλαγών.
- Ασφαλείς αλλαγές, που εφαρμόζονται μαζί σε ένα ενιαίο βήμα με την επιβεβαίωσή σας.
- Προειδοποιήσεις (διαγραφές, αργές λειτουργίες), μία προς μία, η καθεμία επιβεβαιώνεται ξεχωριστά.
- Χειροκίνητες αλλαγές, που εμφανίζονται με αιτιολογία και προτεινόμενη ενέργεια, δεν εφαρμόζονται αυτόματα.
- Ατεκμηρίωτα αντικείμενα (στη βάση δεδομένων αλλά όχι στο σχήμα σας), εμφανίζονται για ενημέρωση.
Τελειώνει με σύνοψη: εφαρμόστηκαν, παραλείφθηκαν, περιμένουν χειροκίνητη διόρθωση, δεν είναι στο σχήμα.
Δείτε Migrations για το μοντέλο.
Επιλογές: --dry-run (εκτύπωση τα πάντα, εφαρμογή τίποτα), --safe (αυτόματη εφαρμογή ασφαλών αλλαγών, παράλειψη προειδοποιήσεων), --yes (αυτόματη εφαρμογή ασφαλών αλλαγών και κάθε προειδοποίησης, παράλειψη χειροκίνητων — χρησιμοποιήστε με προσοχή), --local (διαφορά έναντι του τοπικού schema.js αντί για του αναπτυγμένου). Χωρίς σημαία, το migrate απαιτεί τερματικό και βγάζει σφάλμα σε μη διαδραστικό περιβάλλον αντί να μαντεύει.
run
npx tgcloud run <module> [args] [--ctx <json5>]
Εκτελεί έναν handler ή ένα endpoint στην πλατφόρμα χωρίς ανάπτυξη, χρησιμοποιώντας τα τρέχοντα τοπικά αρχεία σας. Αυτός είναι ο γρήγορος εσωτερικός βρόχος για δοκιμή λογικής.
<module>— ένα απλό όνομα (αναζητείται κάτω από ταhandlers/καιendpoints/) ή μια διαδρομή όπωςhandlers/message. Ένα απλό όνομα που υπάρχει και στους δύο καταλόγους πρέπει να γραφτεί με τον κατάλογό του.[args]— το payload που περνά στον handler, γραμμένο σε JSON5 ώστε να μπορείτε να παραλείψετε τα εισαγωγικά στα κλειδιά. Είναι το αντικείμενο τύπου ενημέρωσης που λαμβάνει ο handler σας (π.χ. ένα Message γιαhandlers/message), ή τοinputενός endpoint, και πρέπει να είναι αντικείμενο — όχι πίνακας ή απλό string.--ctx <json5>— το αντικείμενο πλαισίου (context) του module (το δεύτερο όρισμά του), επίσης JSON5. Χρησιμοποιήστε το για να παρέχετε ό,τι διαβάζει ο κώδικάς σας από τοctx— την raw ενημέρωση για έναν handler,--ctx '{ update: { update_id: 1 } }', ή τα init data για ένα endpoint,--ctx '{ initData: { user: { id: 1 } } }'. Τοrunδεν το επαληθεύει, αφού δοκιμάζετε τη λογική σας, όχι τους ελέγχους της πλατφόρμας.
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "γεια" }'
Η πλατφόρμα εκτελεί το module έναντι του module space που συναρμολογήθηκε από το τοπικό project σας (οπότε χρησιμοποιείται και ο τοπικά τροποποιημένος κώδικας lib/) και επιστρέφει την τιμή επιστροφής, οτιδήποτε καταγράφηκε με console.*, και τον χρόνο που πέρασε. Διαβάστε μεγάλα ορίσματα από ένα αρχείο με npx tgcloud run handlers/message "$(cat message.json5)".
fetch
npx tgcloud fetch
Ελέγχει τι έχει αναπτυχθεί τρέχουσα, χωρίς να αγγίζει τα τοπικά αρχεία εργασίας σας. Μετά από αυτό, τα status και diff συγκρίνουν με την τελευταία κατάσταση cloud. Χρήσιμο για να κοιτάξετε μια σύγκρουση πριν αποφασίσετε πώς να την επιλύσετε.
pull
npx tgcloud pull
Φέρνει το τοπικό project σας σε ευθυγράμμιση με το cloud ενημερώνοντας τα τοπικά αρχεία σας στην αναπτυγμένη κατάσταση.
Το pull δεν γράφει ποτέ τον φάκελο build του Mini App σας.
reset
npx tgcloud reset
Απορρίπτει τις τοπικές αλλαγές σας και επαναφέρει τα modules από την τελευταία γνωστή κατάσταση cloud. Χρησιμοποιήστε το για να πετάξετε ένα πείραμα. Ο φάκελος build του Mini App δεν αγγίζεται.
webhook
npx tgcloud webhook
npx tgcloud webhook sync [--drop-pending]
Το Telegram παραδίδει ενημερώσεις στο bot σας μέσω ενός webhook, το οποίο η πλατφόρμα διαχειρίζεται για εσάς — δεν το δείχνετε ποτέ πουθενά με το χέρι. Το npx tgcloud webhook εμφανίζει την τρέχουσα κατάστασή του: το URL, τη λίστα allowed_updates, πόσες ενημερώσεις εκκρεμούν, το τελευταίο σφάλμα παράδοσης (αν υπάρχει), και αν είναι συγχρονισμένο με τους αναπτυγμένους handlers σας.
“Συγχρονισμένο” σημαίνει ότι το webhook δείχνει στην πλατφόρμα και τα allowed_updates του ταιριάζουν με τους handlers που έχετε αναπτύξει — οπότε το Telegram παραδίδει ακριβώς τους τύπους ενημερώσεων που χειρίζεστε, και τίποτα άλλο. Η ανάπτυξη ενός νέου handler (ή η αφαίρεση ενός) μπορεί να αφήσει το webhook εκτός συγχρονισμού μέχρι να ανανεωθεί; το npx tgcloud status επισημαίνει και αυτό.
Το npx tgcloud webhook sync το διορθώνει — δείχνει ξανά το webhook στην πλατφόρμα και ξαναχτίζει τα allowed_updates από τους αναπτυγμένους handlers σας. Προσθέστε --drop-pending για να απορρίψετε ενημερώσεις που το Telegram είχε ήδη στην ουρά πριν τον συγχρονισμό (αλλιώς παραδίδονται μόλις το webhook γίνει ξανά υγιές).
completion
Σημείωση: η tab-completion λειτουργεί μόνο όταν ένα απλό
tgcloudείναι στοPATHσας — οπότε εγκαταστήστε το καθολικά (npm install -g @tgcloud/cli) ή διαφορετικά βάλτε το binary στοPATHσας. Δεν μπορεί να συνδεθεί στοnpx.
tgcloud completion <bash|zsh|fish>
Εκτυπώνει ένα script συμπλήρωσης shell στο stdout. Ενεργοποιήστε το μία φορά, μετά το <Tab> συμπληρώνει εντολές, σημαίες, καταλόγους module, τους τύπους ενημερώσεων handler που δεν έχετε ακόμα, και τα τοπικά εκτελέσιμα modules σας.
# bash — χρειάζεται το πακέτο bash-completion:
echo 'eval "$(tgcloud completion bash)"' >> ~/.bashrc
# zsh — βεβαιωθείτε ότι το `autoload -U compinit && compinit` τρέχει στο ~/.zshrc σας:
echo 'eval "$(tgcloud completion zsh)"' >> ~/.zshrc
# fish:
tgcloud completion fish > ~/.config/fish/completions/tgcloud.fish
Επανεκκινήστε το shell σας (ή ξαναφορτώστε το αρχείο) στη συνέχεια. Η εκτέλεση του tgcloud completion χωρίς shell εκτυπώνει ξανά αυτές τις οδηγίες.
upgrade
npx tgcloud upgrade [--dry-run]
Μια φορά μετανάστευση για projects που δημιουργήθηκαν πριν από το CLI 0.2.0, τα οποία διατηρούσαν τα schema.js, lib/ και handlers/ στη ρίζα του project. Τα μετακινεί στο tgcloud/. Ο σύνδεσμός σας με το bot διατηρείται, και δεν χρειάζεται να αναπτυχθεί τίποτα ξανά. Το --dry-run εκτυπώνει τις προγραμματισμένες μετακινήσεις χωρίς να αγγίζει τίποτα. Άλλες εντολές δεν τρέχουν στο παλιό layout μέχρι να αναβαθμίσετε.
Staying in sync
Κάθε project έχει μια μονοτονικά αυξανόμενη αναθεώρηση (revision) στο cloud, η οποία ανεβαίνει σε κάθε ανάπτυξη. Το CLI θυμάται την αναθεώρηση με την οποία συγχρονίστηκε τελευταία και τη στέλνει σε κάθε push. Αν το cloud έχει προχωρήσει — επειδή ένα άλλο μηχάνημα ή συμπαίκτης έκανε ανάπτυξη — το push απορρίπτεται αντί να αντικαθιστά σιωπηλά τη δουλειά τους, και το CLI προσφέρει τρεις δρόμους προς τα εμπρός:
npx tgcloud fetch # δείτε τι άλλαξε στο cloud, μετά ξαναελέγξτε
npx tgcloud pull # φέρτε το πιο πρόσφατο στα αρχεία εργασίας σας
npx tgcloud push --force # αντικαταστήστε την κατάσταση cloud (επικίνδυνο)
Αυτός ο έλεγχος optimistic-concurrency είναι ο λόγος που μπορείτε να μοιραστείτε ένα bot σε μια ομάδα χωρίς μια διαδικασία ανάπτυξης lockstep. Οι εντολές βγαίνουν με non-zero κωδικό σε αποτυχία — μια απορριφθείσα ανάπτυξη, μια αποτυχημένη μετανάστευση, ένα σφάλμα ελέγχου ταυτότητας, ένα module που πέταξε εξαίρεση κατά το run — οπότε συντίθενται καθαρά σε scripts και CI pipelines.