टेलीग्राम सर्वरलेस (Telegram Serverless) आपको अपने बॉट और मिनी ऐप के लिए बैकएंड कोड सीधे टेलीग्राम के इंफ्रास्ट्रक्चर पर चलाने की सुविधा देता है — इसमें न तो सर्वर प्रोविज़न करने की ज़रूरत है, न ही कंटेनर्स को चालू रखने की चिंता, और न ही स्केलिंग के बारे में सोचने की आवश्यकता है। आप सामान्य जावास्क्रिप्ट मॉड्यूल लिखते हैं, उन्हें एक कमांड के साथ तैनात (deploy) करते हैं, और टेलीग्राम उन्हें एक तेज़, अलग-थलग V8 सैंडबॉक्स में चलाता है जो बॉट एपीआई और इन-बिल्ट डेटाबेस के ठीक बगल में स्थित है।

यदि आपने कभी किसी /start का जवाब देने के लिए किसी बॉट को VPS, क्लाउड फ़ंक्शन या होस्टिंग पैनल से जोड़ा है, तो यह वह हिस्सा है जिसे आपको अब करने की ज़रूरत नहीं है।

सर्वरलेस क्यों

टेलीग्राम बॉट मूल रूप से एक ऐसा प्रोग्राम है जो अपडेट पर प्रतिक्रिया देता है। पारंपरिक रूप से आपको उस प्रोग्राम को ऐसी जगह होस्ट करना पड़ता था जो हमेशा चालू, सुलभ और सुरक्षित रहे — और फिर उसे वैसा ही बनाए रखना पड़ता था। टेलीग्राम सर्वरलेस उस परत को पूरी तरह से हटा देता है:

  • कोई इंफ्रास्ट्रक्चर नहीं। किराए पर लेने, पैच करने या मॉनिटर करने के लिए कोई मशीन नहीं है। आपका कोड ज़रूरत पड़ने पर चलता है और आपके बॉट के साथ स्वचालित रूप से स्केल होता है।
  • क्रेडेंशियल्स शामिल हैं। टेलीग्राम बॉट एपीआई, SQLite-समर्थित डेटाबेस, और आउटबाउंड HTTP हर मॉड्यूल के लिए पहले से उपलब्ध हैं — कुछ भी इंस्टॉल करने या क्रेडेंशियल्स जोड़ने की ज़रूरत नहीं है।
  • तेज़, अलग-थलग निष्पादन (execution)। प्रत्येक आह्वान (invocation) एक हल्के V8 आइसोलेट में चलता है, जो टेलीग्राम के अपने सिस्टम के करीब होता है, इसलिए बॉट एपीआई और आपके डेटाबेस के लिए कॉल तेज़ और विश्वसनीय होते हैं।
  • एक वास्तविक डेवलपर वर्कफ़्लो। एक प्रोजेक्ट आपकी मशीन पर वर्ज़न कंट्रोल के तहत एक फ़ोल्डर में रहता है। आप फ़ाइलों को संपादित करते हैं, देखते हैं कि क्या बदला है, परमाणु रूप से (atomically) तैनात करते हैं, और समीक्षा किए गए माइग्रेशन के साथ अपने डेटाबेस स्कीमा को आगे बढ़ाते हैं — ठीक वैसे ही जैसे आप बाकी सब कुछ के साथ काम करते हैं।

मानसिक मॉडल: प्रोजेक्ट कैसे काम करता है

आप तीन जगहों पर काम करते हैं, और वे एक-दूसरे के साथ स्पष्ट रूप से मैप होते हैं:

कहाँ वहाँ क्या रहता है
आपका प्रोजेक्ट फ़ोल्डर जावास्क्रिप्ट मॉड्यूल — स्कीमा, साझा कोड, अपडेट हैंडलर, मिनी ऐप एंडपॉइंट्स — और, यदि आपके पास है, तो मिनी ऐप का फ्रंट-एंड
क्लाउड उन मॉड्यूल्स की तैनात प्रति (copy), आपके बॉट का डेटाबेस, और मिनी ऐप की फ़ाइलें
tgcloud CLI ब्रिज — यह आपको अंतर दिखाता है और उन्हें सिंक करता है

आप कभी किसी चीज़ में SSH नहीं करते। आप स्थानीय रूप से फ़ाइलों को संपादित करते हैं, npx tgcloud push चलाते हैं, और प्लेटफ़ॉर्म वहां से सब कुछ संभाल लेता है। आपके बॉट का ट्रैफ़िक तैनात मॉड्यूल्स द्वारा नियंत्रित किया जाता है; आपका डेटाबेस इनवोकेशंस के बीच बना रहता है।

एक प्रोजेक्ट में केवल चार प्रकार के कोड होते हैं, सभी एक tgcloud/ फ़ोल्डर के अंदर:

tgcloud/
├─ endpoints/  # फ़ंक्शंस जिन्हें आपका मिनी ऐप कॉल करता है
├─ handlers/   # फ़ंक्शंस जो टेलीग्राम अपडेट प्रकारों पर प्रतिक्रिया देते हैं
├─ lib/        # साझा कोड जिसे आप कहीं से भी आयात करते हैं
└─ schema.js   # आपके डेटाबेस टेबल

जब कोई अपडेट आता है — एक मैसेज, एक बटन प्रेस, एक इनलाइन क्वेरी — तो टेलीग्राम इसे मिलान करने वाले हैंडलर (handlers/message.js, handlers/callback_query.js, …) पर रूट करता है और उसके डिफ़ॉल्ट एक्सपोर्ट को कॉल करता है। वह फ़ंक्शन SDK के माध्यम से बॉट एपीआई और डेटाबेस से बात करता है, और लौट आता है। यही पूरा लूप है। बिना मिलान वाले हैंडलर वाले अपडेट को बस अनदेखा कर दिया जाता है, इसलिए आप केवल वही हैंडलर जोड़ते हैं जिनकी आपको आवश्यकता है।

मिनी ऐप वैकल्पिक है। यदि आपके बॉट के पास एक है, तो उसका फ्रंट-एंड उसी प्रोजेक्ट में, tgcloud/ के बगल में रहता है। सर्वरलेस उसके बिल्ड आउटपुट को होस्ट करता है और उन endpoints/ को चलाता है जिन्हें वह कॉल करता है। देखें मिनी ऐप फ्रंट-एंड ।

त्वरित डेमो

यहाँ एक पूर्ण, काम करने वाला डेमो बॉट है। यह हर मैसेज का जवाब देता है और याद रखता है कि उसने प्रत्येक चैट से कितने मैसेज देखे हैं।

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;

  // काउंटर डालें, या यदि इस चैट के पास पहले से ही एक है तो उसे बढ़ाएं — और .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: `Hello! I've seen ${row.seen} message(s) from you.`,
  });
}

इसे तैनात करें:

npx tgcloud push       # मॉड्यूल अपलोड करें
npx tgcloud migrate    # `counters` टेबल बनाएं

यह स्थायी स्थिति वाला एक लाइव बॉट है और इसमें कोई सर्वर नहीं है। इसमें सब कुछ — api, db, table() DSL — नीचे दिए गए अनुभागों में वर्णित है।

सर्वरलेस टेलीग्राम बॉट्स और मिनी ऐप्स के लिए एक सामान्य बैकएंड है, न कि किसी एक तरह के ऐप के लिए टेम्प्लेट। यह इनके लिए आदर्श है:

  • संवादात्मक एआई बॉट्स जिन्हें डेटाबेस में प्रति-उपयोगकर्ता स्थिति (state) संग्रहीत करने की आवश्यकता होती है।
  • मिनी ऐप बैकएंड जो उपयोगकर्ता डेटा संग्रहीत करते हैं और गतिशील सामग्री प्रदान करते हैं।
  • गेम्स और टूल्स — जिसमें लीडरबोर्ड, क्विज़ और बहुत कुछ शामिल हैं।
  • ऑटोमेशन और इंटीग्रेशन जो थर्ड-पार्टी HTTP एपीआई को कॉल करते हैं और परिणाम चैट में भेजते हैं।

शुरुआत करना

यह वॉकथ्रू आपको एक खाली फ़ोल्डर से एक ऐसे लाइव बॉट तक ले जाएगा जो मैसेज का जवाब देता है और डेटा स्टोर करता है। यह मानकर चलता है कि आपने Node.js 18 या नया इंस्टॉल किया है और @BotFather के साथ एक बॉट रजिस्टर किया है। अंत तक आपने हर उस कमांड का उपयोग कर लिया होगा जिसकी आपको दैनिक आधार पर आवश्यकता है: push, migrate, run, और status।

किसी भी चीज़ से पहले, सर्वरलेस चालू करें। @BotFather में, अपना बॉट खोलें → Serverless और इसे चालू करें। यह इस बॉट के लिए सुविधा को चालू करता है और इसके CLI एक्सेस टोकन, हैंडलर, लाइब्रेरी और डेटाबेस को अनलॉक करता है।

1. एक प्रोजेक्ट बनाएं

शुरू करने का सबसे तेज़ तरीका प्रोजेक्ट क्रिएटर है, जो एक प्रोजेक्ट तैयार करता है और उसमें CLI इंस्टॉल करता है:

npm create @tgcloud/bot example_bot
cd example_bot

तर्क लक्ष्य फ़ोल्डर है: वर्तमान फ़ोल्डर में तैयार करने के लिए . पास करें, या कोई भी पथ। यह मौजूदा फ़ोल्डर में भी काम करता है और आपके पास पहले से मौजूद फ़ाइलों को कभी ओवरराइट नहीं करता है।

यह आपको संपादन के लिए तैयार एक प्रोजेक्ट देता है:

example_bot/
├─ tgcloud/             # सब कुछ जो प्लेटफ़ॉर्म चलाता है
│  ├─ handlers/
│  │  └─ message.js     # एक स्टार्टर मैसेज हैंडलर (टेक्स्ट वापस गूँजता है)
│  ├─ lib/              # आपके साझा मॉड्यूल यहाँ जाते हैं (शुरू करने के लिए खाली)
│  └─ schema.js         # आपके डेटाबेस टेबल
├─ docs/
│  └─ tgcloud-sdk.md    # SDK संदर्भ (आपके और आपके एआई टूल्स के लिए)
├─ AGENTS.md            # एआई कोडिंग सहायकों के लिए ओरिएंटेशन
├─ package.json
└─ tgcloud.jsonc        # प्रोजेक्ट कॉन्फ़िग

स्कैफोल्ड की गई फ़ाइलें स्वयं-व्याख्यात्मक हैं — प्रत्येक में उन चीजों के टिप्पणीबद्ध उदाहरण हैं जो आप आगे कर सकते हैं।

CLI प्रोजेक्ट में स्थानीय देव-निर्भरता (dev-dependency) के रूप में इंस्टॉल होता है, इसलिए आप इसे npx tgcloud <command> के साथ चलाते हैं (npx आपके प्रोजेक्ट के node_modules में कॉपी ढूंढता है), या उन npm run शॉर्टकट्स के माध्यम से जिन्हें स्कैफोल्ड package.json में जोड़ता है (npm run deploy, npm run status)। डिफ़ॉल्ट रूप से आपके PATH पर कोई वैश्विक tgcloud नहीं होता है।

आप इसे वैश्विक स्तर पर भी इंस्टॉल कर सकते हैं — npm install -g @tgcloud/cli — यदि आप कहीं से भी एक नग्न tgcloud टाइप करना पसंद करते हैं। यह किसी भी खाली फ़ोल्डर में tgcloud init चलाने के लिए आसान है, और यही वह है जिसकी शेल टैब-पूर्णता (tab-completion) को आवश्यकता होती है। किसी भी तरह आपको वही प्रोजेक्ट मिलता है।

मिनी ऐप बना रहे हैं? इसके फ्रंट-एंड से शुरुआत करें। इसे अपने सामान्य टूल के साथ बनाएं, फिर उस फ़ोल्डर में प्रोजेक्ट क्रिएटर चलाएं। देखें मिनी ऐप फ्रंट-एंड ।

2. अपने बॉट को लिंक करें

हर प्रोजेक्ट एक बॉट से बंधा होता है। उन्हें login के साथ कनेक्ट करें, जो आपके CLI एक्सेस टोकन के लिए पूछता है ( @BotFather → आपका बॉट → Serverless → CLI Access → Access token — आपके बॉट के एपीआई टोकन से एक अलग टोकन) और इसे स्थानीय रूप से संग्रहीत करता है:

npx tgcloud login

टोकन का प्रारूप app<id>:<secret> है। CLI इसे .tgcloud/ में रखता है, जो git-ignored है, और गुप्त भाग को कभी प्रिंट नहीं करता है। लॉगिन ही एकमात्र समय है जब आपसे इसके लिए पूछा जाता है — देखें ऑथेंटिकेशन यह जानने के लिए कि CI में टोकन कैसे हल किए जाते हैं।

3. चारों ओर देखें

दो कमांड आपको बताते हैं कि किसी भी क्षण स्थिति क्या है, दोनों पूरी तरह से ऑफ़लाइन:

npx tgcloud status     # स्थानीय स्तर पर क्या बदला है बनाम तैनात कॉपी
npx tgcloud diff       # लाइन-दर-लाइन परिवर्तन

init के तुरंत बाद सब कुछ नया है और अभी तक कुछ भी तैनात नहीं किया गया है। status स्टार्टर फ़ाइलों को ऊपर जाने की प्रतीक्षा करते हुए दिखाता है।

4. तैनात करें (Deploy)

अपने मॉड्यूल क्लाउड पर भेजें:

npx tgcloud push

push हर बदले हुए मॉड्यूल को एक परमाणु बैच में अपलोड करता है और क्लाउड में वर्तमान में क्या है, इसके आपके स्थानीय रिकॉर्ड को अपडेट करता है। आपका बॉट लाइव है: इसे टेलीग्राम में खोलें और इसे एक मैसेज भेजें — स्टार्टर हैंडलर इसे वापस गूँजता है।

तैनात करना आपके डेटाबेस को कभी नहीं छूता है। कोड पुश करना और अपने डेटाबेस स्कीमा को बदलना जानबूझकर अलग-अलग चरण हैं, इसलिए कोड तैनाती आपको डेटा माइग्रेशन के साथ कभी आश्चर्यचकित नहीं कर सकती है। यही वह है जिसके लिए अगला चरण है।

5. एक डेटाबेस टेबल जोड़ें

आइए बॉट को कुछ याद रखने के लिए कहें। 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. डेटा स्टोर और पढ़ें

अब अपने हैंडलर से टेबल का उपयोग करें। 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();

  // गिनें कि हमने इस चैट के लिए कितने स्टोर किए हैं।
  const count = await db.$count(messages, eq(messages.chatId, message.chat.id));

  await api.sendMessage({
    chat_id: message.chat.id,
    text: `Saved. That's ${count} message(s) from this chat so far.`,
  });
}

अद्यतन हैंडलर को npx tgcloud push के साथ तैनात करें, फिर अपने बॉट को कुछ मैसेज भेजें और गिनती को बढ़ते हुए देखें। डेटाबेस इनवोकेशंस के बीच बना रहता है — यह आपके बॉट की मेमोरी है।

7. बिना तैनात किए टेस्ट करें

परिवर्तन आज़माने के लिए आपको तैनात करने की आवश्यकता नहीं है। npx tgcloud run अपनी स्थानीय फ़ाइलों का उपयोग करके प्लेटफ़ॉर्म पर एक हैंडलर को निष्पादित करता है, उन्हें प्रकाशित किए बिना:

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

तर्क वह पेलोड है जो आपका हैंडलर प्राप्त करता है — handlers/message के लिए, एक Message — JSON5 में लिखा गया (ताकि आप कुंजियों को उद्धृत करने से बच सकें)। कमांड वह सब कुछ प्रिंट करता है जो हैंडलर ने console.* के साथ लॉग किया था, रिटर्न मान, और इसमें कितना समय लगा। तर्क पर पुनरावृति (iterating) के लिए यह सबसे तंग लूप है — कोई तैनाती नहीं, कोई वास्तविक मैसेज की प्रतीक्षा नहीं।

8. सिंक में रहें

जैसे-जैसे आप काम करते हैं, कमांड का एक समूह आपके स्थानीय प्रोजेक्ट और क्लाउड को संरेखित रखता है: npx tgcloud status दिखाता है कि क्या बदला है, npx tgcloud push तैनात करता है, npx tgcloud pull आपके स्थानीय प्रोजेक्ट को क्लाउड के साथ लाता है, npx tgcloud fetch बिना आपकी फ़ाइलों को छुए चेक करता है कि क्या तैनात है, और npx tgcloud reset स्थानीय परिवर्तनों को छोड़ देता है।

यदि दो लोग (या दो मशीनें) एक ही बॉट पर तैनात करते हैं, तो प्लेटफ़ॉर्म संघर्ष का पता लगाता है और push रुक जाता है ताकि आप पहले pull कर सकें — आप किसी और के काम को कभी चुपचाप ओवरराइट नहीं कर सकते। देखें सिंक में रहना ।

एआई के साथ निर्माण

एआई सहायक के साथ निर्माण करना पसंद करते हैं — या क्या आपकी टीम का एकमात्र कोडर एक एआई है? आप अभी भी एक बॉट शिप कर सकते हैं। हमने एआई एजेंट को प्रोजेक्ट में घर जैसा महसूस कराने के लिए एक पहला कदम उठाया है: हर नए को AGENTS.md और docs/tgcloud-sdk.md संदर्भ के साथ स्कैफोल्ड किया जाता है जिसे एजेंटिक कोडिंग टूल्स स्वचालित रूप से पढ़ते हैं।

एक छोटे, स्वयं-निहित रनटाइम के साथ — एक SDK, कोई npm पैकेज नहीं — जो सहायक को उन परंपराओं पर एक शुरुआती शुरुआत देता है जिन्हें सामान्य कोडेज यहाँ चूक जाते हैं: .js के साथ सापेक्ष आयात, कोई विदेशी कुंजी नहीं, हर db कॉल एसिंक है, प्रति अपडेट प्रकार एक हैंडलर, और दो-चरणीय push/migrate प्रवाह।

इसे आज़माएं:

npm create @tgcloud/bot my-bot
cd my-bot
opencode            # या Claude Code, Cursor, … — कोई भी एजेंट जो AGENTS.md पढ़ता है

फिर बस पूछें, सरल भाषा में:

एक ऐसा बॉट लिखें जो प्रत्येक व्यक्ति की टू-डू सूची को याद रखे — जब वे टेक्स्ट भेजें तो एक आइटम जोड़ें, और जब वे /list भेजें तो पूरी सूची दिखाएं।

सहायक आपके लिए schema.js और आपके हैंडलर संपादित करता है; आप समीक्षा करें, npx tgcloud run के साथ तुरंत एक परिवर्तन का परीक्षण करें, फिर npx tgcloud push और npx tgcloud migrate के साथ लाइव हो जाएं। AGENTS.md आपके प्रोजेक्ट का हिस्सा है — इसे संपादित करें जैसे बॉट बढ़ता है ताकि मार्गदर्शन सटीक बना रहे।

BotFather के साथ चलते-फिरते

सिर्फ अपने फोन पर? पूरा प्रोजेक्ट @BotFather में भी रहता है — अपना बॉट खोलें → Serverless और आपको वह सब कुछ मिलता है जिसे CLI प्रबंधित करता है, टचस्क्रीन पर:

  • हैंडलर — अपडेट हैंडलर बनाएं, संपादित करें, और टेस्ट-रन करें; BotFather वेबहुक को उन हैंडलर्स के साथ सिंक में रखता है जो आपके पास हैं (वही In sync / Out of sync जो CLI रिपोर्ट करता है)।
  • एंडपॉइंट्स — वे फ़ंक्शंस जिन्हें आपका मिनी ऐप कॉल करता है।
  • लाइब्रेरी — आपके साझा lib/ मॉड्यूल।
  • डेटाबेस — उसी Drizzle-जैसे सिंटैक्स में schema.js संपादित करें, लंबित परिवर्तनों की समीक्षा करें, और उन्हें लागू करें; Save तैनात करता है।
  • मिनी ऐप फ़ाइलें — फ्रंट-एंड जिसे आपके उपयोगकर्ता टेलीग्राम में खोलते हैं।
  • CLI एक्सेस — जब आप वापस कीबोर्ड पर हों तो CLI एक्सेस टोकन यहाँ से प्राप्त करें।

यह एक और वही क्लाउड प्रोजेक्ट है, इसलिए आप अपने फोन पर एक हैंडलर शुरू कर सकते हैं और बाद में इसे अपने लैपटॉप पर npx tgcloud pull कर सकते हैं — कुछ भी एक क्लाइंट से बंधा नहीं है। एक हैंडलर चलाना चैट में ही अपना Console आउटपुट दिखाता है, ठीक npx tgcloud run की तरह।

प्रोजेक्ट्स और मॉड्यूल

एक सर्वरलेस प्रोजेक्ट वर्ज़न कंट्रोल के तहत एक सामान्य फ़ोल्डर है। प्लेटफ़ॉर्म जो कुछ भी चलाता है वह एक फ़ोल्डर, tgcloud/ में रहता है; प्रोजेक्ट का बाकी हिस्सा आपका है — एक मिनी-ऐप फ्रंट-एंड, कॉन्फ़िग, डॉक्स। बॉट के लिए कोई बिल्ड चरण नहीं है, रनटाइम पर कोई node_modules नहीं है, और कोई सर्वर एंट्री पॉइंट नहीं है।

एक प्रोजेक्ट की शारीरिक रचना (Anatomy)

example_bot/
├─ tgcloud/             # तैनात भाग — इसके अलावा कुछ नहीं है
│  ├─ handlers/         # अपडेट हैंडलर — फ्लैट, केवल एक स्तर
│  │  ├─ message.js
│  │  └─ callback_query.js
│  ├─ endpoints/        # फ़ंक्शंस जिन्हें आपका मिनी ऐप कॉल करता है — फ्लैट, केवल एक स्तर
│  │  └─ getProfile.js
│  ├─ lib/              # साझा मॉड्यूल; सबडायरेक्टरी की अनुमति है
│  │  ├─ reply.js
│  │  └─ internal/util.js
│  └─ schema.js         # डेटाबेस स्कीमा — एक फ़ाइल
└─ .tgcloud/            # CLI स्थिति, git-ignored

केवल tgcloud/ के अंदर की .js फ़ाइलें — schema.js, lib/, handlers/, endpoints/ — तैनात की जाती हैं। बाकी सब कुछ — मार्कडाउन, कॉन्फ़िग फ़ाइलें, .tgcloud/ फ़ोल्डर, tgcloud/ के बाहर कुछ भी — आपकी मशीन पर रहता है। मॉड्यूल के नाम tgcloud/ के सापेक्ष हैं। फ़ाइल tgcloud/handlers/message.js मॉड्यूल handlers/message है, और वही नाम npx tgcloud run लेता है।

पुराने प्रोजेक्ट से अपग्रेड कर रहे हैं? CLI 0.2.0 से पहले बनाए गए प्रोजेक्ट्स ने schema.js, lib/ और handlers/ को प्रोजेक्ट रूट पर रखा था। उन्हें tgcloud/ में ले जाने के लिए एक बार npx tgcloud upgrade चलाएं।

schema.js — आपका डेटाबेस। यह स्कीमा DSL का उपयोग करके नामित निर्यात के रूप में टेबल घोषित करता है और एक फ़ाइल के रूप में tgcloud/ में रहता है। यह किसी अन्य मॉड्यूल की तरह तैनात किया जाता है, लेकिन इसे तैनात करना डेटाबेस को कभी नहीं बदलता है — स्कीमा परिवर्तन अलग से npx tgcloud migrate के साथ लागू किए जाते हैं। देखें डेटाबेस ।

lib/ — साझा कोड, कुछ भी जिसे आप हैंडलर्स में फिर से उपयोग करना चाहते हैं: शुद्ध सहायक, डेटाबेस एक्सेस परतें, स्वरूपण, बाहरी सेवाओं के साथ एकीकरण। lib/ एकमात्र निर्देशिका है जिसमें सबडायरेक्टरी हो सकती हैं (lib/internal/util.js, lib/payments/stripe.js) , इसलिए आप एक बड़े कोडबेस को अपनी पसंद के अनुसार व्यवस्थित कर सकते हैं। lib/ में मॉड्यूल प्लेटफ़ॉर्म द्वारा सीधे कभी नहीं बुलाए जाते हैं; वे हैंडलर्स और एक-दूसरे द्वारा आयात किए जाने के लिए मौजूद हैं।

handlers/ — आपके बॉट के एंट्री पॉइंट्स। प्रत्येक फ़ाइल एक टेलीग्राम अपडेट प्रकार के अनुरूप है, और प्लेटफ़ॉर्म प्रत्येक आने वाले अपडेट को मिलान करने वाले हैंडलर पर रूट करता है:

फ़ाइल संभालता है
handlers/message.js नए आने वाले मैसेज
handlers/inline_query.js नई आने वाली इनलाइन क्वेरी
handlers/callback_query.js नई आने वाली कॉलबैक क्वेरी
… कोई अन्य बॉट एपीआई अपडेट प्रकार

handlers/ फ्लैट है — कोई सबडायरेक्टरी नहीं। एक हैंडलर का export default वह फ़ंक्शन है जिसे प्लेटफ़ॉर्म कॉल करता है (देखें हैंडलर्स )।

एक अपडेट प्रकार को केवल तभी संभाला जाता है यदि उसकी हैंडलर फ़ाइल मौजूद हो और गैर-खाली हो। यदि कोई handlers/<type>.js नहीं है — या फ़ाइल खाली है — तो उस प्रकार के अपडेट को अनदेखा कर दिया जाता है, और प्लेटफ़ॉर्म उनके लिए कुछ भी नहीं चलाता है। इसलिए केवल वही हैंडलर रखें जिनकी आपको वास्तव में आवश्यकता है: प्रत्येक जिसे आप जोड़ते हैं वह एक और अपडेट प्रकार है जिसे आपका बॉट प्रोसेस करने के लिए जागता है, और बाकी को छोड़ने का मतलब है कि टेलीग्राम उन अपडेट के लिए आपके कोड को स्पिन नहीं करता है जिन्हें आप वैसे भी छोड़ देंगे।

नया हैंडलर स्कैफोल्ड करने के लिए, npx tgcloud add handlers/<type> चलाएं।

endpoints/ — आपके मिनी ऐप का सर्वर साइड। प्रत्येक फ़ाइल एक फ़ंक्शन है जिसे मिनी ऐप नाम से कॉल करता है, इसलिए endpoints/getProfile.js POST /api/getProfile का जवाब देता है। प्लेटफ़ॉर्म यह जांचता है कि कॉल आपके कोड के चलने से पहले आपके मिनी ऐप के अंदर टेलीग्राम उपयोगकर्ता से आती है, और आपको वह उपयोगकर्ता सौंपता है। endpoints/ फ्लैट है, handlers/ की तरह। देखें एंडपॉइंट्स ।

.tgcloud/ — CLI द्वारा पूरी तरह से प्रबंधित मशीन-स्थानीय स्थिति, जिसमें आपके सहेजे गए क्रेडेंशियल्स शामिल हैं। यह git-ignored है, और आपको इसे कभी भी हाथ से पढ़ना या लिखना नहीं चाहिए — इसके बजाय CLI कमांड का उपयोग करें।

मॉड्यूल सिस्टम

रनटाइम पर, एक मॉड्यूल बिल्कुल दो चीजें देख सकता है: प्लेटफ़ॉर्म SDK और आपके प्रोजेक्ट के अन्य मॉड्यूल। कोई npm पैकेज नहीं है, कोई फ़ाइल सिस्टम नहीं है, और SDK के fetch के माध्यम से कोई नेटवर्क नहीं है।

अपने स्वयं के मॉड्यूल को उस तरह आयात करें जैसे आप किसी भी जावास्क्रिप्ट प्रोजेक्ट में करेंगे — सापेक्ष पथ द्वारा, .js एक्सटेंशन के साथ — और SDK को उसके नाम से:

// tgcloud/handlers/message.js
import { users } from '../schema.js';              //  स्कीमा मॉड्यूल
import { addItem } from '../lib/cart.js';          //  एक lib मॉड्यूल
import { format } from '../lib/internal/fmt.js';  //  नेस्टेड lib मॉड्यूल
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 पैकेज नहीं

हर मॉड्यूल का एक नाम भी होता है — tgcloud/ के अंदर इसका पथ बिना एक्सटेंशन के, जैसे schema, lib/cart या handlers/message। वह नाम है जिसका उपयोग npx tgcloud run और CLI का आउटपुट करते हैं, और एक मॉड्यूल को उससे भी आयात किया जा सकता है (from 'lib/cart', कोई एक्सटेंशन नहीं)। अपने कोड में सापेक्ष आयात को प्राथमिकता दें। वे वही हैं जिन्हें आपका संपादक समझता है — जंप-टू-डेफिनेशन, ऑटोकंप्लीट, रीनेम।

रनटाइम पर क्या उपलब्ध है, बिल्कुल दो चीजें हैं: sdk और उसके सबमॉड्यूल (sdk/db, sdk/api, sdk/fetch) — पूरी प्लेटफ़ॉर्म सतह, देखें SDK — और tgcloud/ में आपके अपने मॉड्यूल। यह पूरी सूची है। यदि आपका कोड कुछ और import करता है, तो यह हल नहीं होगा। यह बाधा वही है जो मॉड्यूल्स को लोड होने में तेज़ और चलने में सुरक्षित रखती है।

हैंडलर्स

एक हैंडलर handlers/ में एक मॉड्यूल है जिसका डिफ़ॉल्ट एक्सपोर्ट प्लेटफ़ॉर्म द्वारा तब लागू किया जाता है जब कोई मिलान अपडेट आता है।

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

export default async function (message) {
  await api.sendMessage({
    chat_id: message.chat.id,
    text: `You said: ${message.text ?? '(no text)'}`,
  });
}

एक हैंडलर अपडेट के पेलोड को अपने तर्क के रूप में प्राप्त करता है — प्लेटफ़ॉर्म आपके लिए टेलीग्राम Update को अनरैप करता है। handlers/message.js को Message मिलता है (यानी update.message); handlers/callback_query.js को CallbackQuery मिलता है; और इसी तरह। हैंडलर का दूसरा तर्क एक प्रति-आह्वान संदर्भ ऑब्जेक्ट, ctx है। यह ctx.update के रूप में कच्चा Update ले जाता है — जब आपको पेलोड के बाहर कुछ चाहिए हो, जैसे update_id, तो इसके लिए पहुंचें।

एक हैंडलर async (आमतौर पर होता है) हो सकता है और एक मान वापस कर सकता है। यह SDK के माध्यम से बॉट एपीआई, डेटाबेस और आउटबाउंड HTTP तक पहुंचता है।

एक हैंडलर का परीक्षण करने के लिए आपको एक वास्तविक अपडेट की आवश्यकता नहीं है। npx tgcloud run इसे आपके द्वारा आपूर्ति किए गए पेलोड और आपके वर्तमान स्थानीय कोड के साथ प्लेटफ़ॉर्म पर निष्पादित करता है:

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

तर्क पेलोड है — वही ऑब्जेक्ट जो आपका हैंडलर प्राप्त करता है — JSON5 में। हैंडलर के ctx (उसके दूसरे तर्क) की आपूर्ति करने के लिए, --ctx जोड़ें, उदाहरण के लिए --ctx '{ update: { update_id: 1 } }'। यह आपकी स्थानीय फ़ाइलों के खिलाफ चलता है, इसलिए आप उन्हें तैनात करने से पहले परिवर्तनों को आज़मा सकते हैं। देखें run ।

एंडपॉइंट्स

एक एंडपॉइंट endpoints/ में एक मॉड्यूल है जिसे आपका मिनी ऐप कॉल करता है — वह जगह जहाँ कुछ भी फ्रंट-एंड अपने दम पर नहीं कर सकता, जैसे डेटाबेस को पढ़ना और लिखना, बॉट एपीआई को कॉल करना, या आपके रहस्यों के साथ बाहरी सेवाओं से बात करना।

// 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('Profile not found', { userId: user.id });
  return profile;
}

पहला तर्क, input, वह JSON ऑब्जेक्ट है जिसे मिनी ऐप ने भेजा है। दूसरा, ctx, ctx.initData ले जाता है — मिनी ऐप का init डेटा (user, chat, start_param, auth_date, …) , जो पहले से प्लेटफ़ॉर्म द्वारा सत्यापित है। इसका आकार क्लाइंट पर Telegram.WebApp.initDataUnsafe के समान है, लेकिन यहाँ आप इस पर भरोसा कर सकते हैं। वैध init डेटा के बिना एक कॉल को आपके कोड के चलने से पहले 401 के साथ अस्वीकार कर दिया जाता है, इसलिए ctx.initData.user वास्तव में वह व्यक्ति है जिसने आपका मिनी ऐप खोला। एंडपॉइंट्स को केवल इस तरह से कॉल किया जा सकता है — आपके बॉट के मिनी ऐप से, एक टेलीग्राम उपयोगकर्ता द्वारा — इसलिए कोई सार्वजनिक एपीआई सुरक्षित करने के लिए नहीं है और कोई लॉगिन बनाने के लिए नहीं है।

एंडपॉइंट जो कुछ भी वापस करता है उसे JSON के रूप में वापस भेजा जाता है। किसी कॉल को अस्वीकार करने के लिए, throw new EndpointError(description, parameters?) करें। मिनी ऐप को विवरण और parameters ऑब्जेक्ट वैसे ही मिलता है जैसे वे हैं। यदि आपको फ्रंट-एंड में त्रुटि के प्रकार पर शाखा बनाने की आवश्यकता है, तो parameters में एक कोड डालें — { code: 'OUT_OF_STOCK' }। किसी भी अन्य अपवाद को एक सामान्य आंतरिक त्रुटि के रूप में रिपोर्ट किया जाता है, और वास्तविक मैसेज केवल आपके लॉग में जाता है, इसलिए आंतरिक चीज़ें कभी भी क्लाइंट तक नहीं पहुँचती हैं।

मिनी ऐप से एंडपॉइंट कॉल करना

मिनी ऐप आधिकारिक मिनी ऐप्स SDK से Telegram.WebApp.Serverless.call के साथ नाम से एक एंडपॉइंट कॉल करता है, telegram-web-app.js — वह स्क्रिप्ट जिसे आपका मिनी ऐप पहले से शामिल करता है। यह आपके लिए init डेटा भेजता है और एक त्रुटि या एंडपॉइंट के परिणाम के साथ वापस कॉल करता है:

Telegram.WebApp.Serverless.call('getProfile', { lang: 'en' }, function (err, profile) {
  if (err) {
    // err.message — विवरण; err.parameters — जो आपके EndpointError ने किया
    if (err.type === 'ENDPOINT_ERROR') showMessage(err.message);
    return;
  }
  render(profile);
});

इनपुट वैकल्पिक है और एक सादा ऑब्जेक्ट होना चाहिए। इसके बिना कॉल Serverless.call('getProfile', callback) है।

err.type 'ENDPOINT_ERROR' होता है जब आपके एंडपॉइंट ने EndpointError थ्रो किया हो। किसी अन्य मान का मतलब है कि प्लेटफ़ॉर्म ने कॉल को अस्वीकार कर दिया या एंडपॉइंट अप्रत्याशित रूप से विफल हो गया, उदाहरण के लिए 'UNAUTHORIZED' जब init डेटा गायब या अमान्य हो।

एंडपॉइंट के नाम जावास्क्रिप्ट पहचानकर्ताओं की तरह होते हैं — अक्षर, अंक और _, जो अंक (getProfile, place_order) से शुरू नहीं होते हैं। npx tgcloud add endpoints/<name> के साथ एक स्कैफोल्ड करें, और run के साथ मिनी ऐप के बिना आज़माएं, init डेटा खुद पास करें:

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

क्या तैनात किया जाता है

जब आप npx tgcloud push करते हैं, तो CLI schema.js, lib/, handlers/, और endpoints/ के तहत हर .js फ़ाइल को इकट्ठा करता है, और उस सटीक सेट को आपके प्रोजेक्ट के मॉड्यूल स्पेस के रूप में भेजता है — मिनी ऐप के बिल्ड फ़ोल्डर के साथ, यदि tgcloud.jsonc एक का नाम लेता है (नीचे देखें)। क्लाउड में मौजूद लेकिन आपके प्रोजेक्ट में अनुपस्थित कुछ भी हटा दिया जाता है, इसलिए तैनात स्थिति हमेशा आपके फ़ोल्डर को दर्शाती है — हटाए गए सहित। tgcloud/ के बाहर कुछ भी कोड के रूप में तैनात नहीं किया जाता है — प्रोजेक्ट रूट का बाकी हिस्सा आपका है। tgcloud/ के अंदर, एक .js फ़ाइल जो उन स्थानों में से एक में नहीं है (tgcloud/util.js, tgcloud/helpers/x.js) को ध्वजांकित किया जाता है, ताकि यह चुपचाप तैनात न हो। मार्कडाउन, डॉटफाइल्स, और .tgcloud/ कभी तैनात नहीं किए जाते हैं।

मिनी ऐप फ्रंट-एंड

प्रोजेक्ट रूट आपका है, और यह आपके मिनी ऐप के फ्रंट-एंड के लिए स्वाभाविक जगह है — जिसे React, Vue, Svelte या सादे HTML के साथ बनाया गया है, Vite या किसी अन्य बंडलर द्वारा। सर्वरलेस आपके बॉट के बगल में इसके बिल्ड आउटपुट को होस्ट करता है। एक push कोड और स्टेटिक फ़ाइलों को एक साथ तैनात करता है, और ऐप https://app<app_id>.tgcloud.ai/ पर परोसा जाता है (push सटीक पता प्रिंट करता है — इसे @BotFather में अपने मिनी ऐप के URL के रूप में उपयोग करें)।

दुरुपयोग की रिपोर्टिंग। tgcloud.ai पर मिनी ऐप्स स्वतंत्र डेवलपर्स द्वारा प्रकाशित किए जाते हैं। app<app_id>.tgcloud.ai सबडोमेन पर अवैध सामग्री या दुरुपयोग की रिपोर्ट करने के लिए, [email protected] पर ईमेल करें।

शुरू करने के लिए, पहले फ्रंट-एंड बनाएं, जिस टूल के साथ आप पसंद करते हैं, फिर उसके ऊपर सर्वरलेस जोड़ें:

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

प्रोजेक्ट क्रिएटर आपकी फ़ाइलों को कभी ओवरराइट नहीं करता है। यह फ्रंट-एंड के बगल में tgcloud/ फ़ोल्डर जोड़ता है, बिल्ड फ़ोल्डर को पहचानता है, और एक npm run deploy स्क्रिप्ट जोड़ता है जो ऐप बनाता है और इसे बॉट के साथ तैनात करता है।

क्या होस्ट किया जाता है, यह tgcloud.jsonc में सेट किया जाता है, प्रोजेक्ट रूट पर प्रोजेक्ट की कॉन्फ़िग फ़ाइल:

{
  "$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" आवश्यक है। यह प्रोजेक्ट रूट के सापेक्ष बिल्ड फ़ोल्डर है। "static" की अन्य कुंजियाँ कॉन्फ़िगर करती हैं कि इसे कैसे परोसा जाता है (नीचे)। init आपके लिए tgcloud.jsonc बनाता है। जब यह फ्रंट-एंड को पहचानता है, तो यह "source" को भी भर देता है:

फ्रंट-एंड init "source" को सेट करता है
Vite — React, Vue, Svelte, Solid, … dist
Next.js, Nuxt, SvelteKit, React Router, TanStack Start, Angular कुछ नहीं — पहले एक स्टेटिक एक्सपोर्ट कॉन्फ़िगर करें, फिर "source" को स्वयं उसके फ़ोल्डर पर सेट करें

किसी अन्य सेटअप के साथ, "source" को स्वयं अपने बिल्ड फ़ोल्डर पर सेट करें।

$schema लाइन आपके संपादक को हर कुंजी के लिए ऑटोकंप्लीट और सत्यापन देती है। फ़ाइल टिप्पणियों और ट्रेलिंग अल्पविराम के साथ JSON है। यदि आपके किसी अन्य टूल को इसे सादे JSON के रूप में पढ़ना है, तो इसका नाम tgcloud.json रखें — फिर यह सख्त JSON होना चाहिए।

पुश करने से पहले बनाएं। push फ़ोल्डर को डिस्क पर बिल्कुल वैसा ही अपलोड करता है — उन फ़ाइलों और फ़ोल्डरों को छोड़कर जिनका नाम एक बिंदु से शुरू होता है, जिन्हें छोड़ दिया जाता है — और कभी भी आपका बिल्ड नहीं चलाता है, इसलिए फ्रंट-एंड को संपादित करने के बाद एक सादा npx tgcloud push पिछले बिल्ड को तैनात करेगा। npm run deploy का उपयोग करें (यह आपकी build स्क्रिप्ट चलाता है, फिर tgcloud push), या पहले स्वयं बनाएं।

परोसने के विकल्प:

  • spa — उन पथों के लिए index.html परोसें जो मौजूद नहीं हैं और जिनका कोई फ़ाइल एक्सटेंशन नहीं है, ताकि क्लाइंट-साइड राउटर रीलोड पर काम करे। एक्सटेंशन वाले पथों को अभी भी 404 मिलता है।
  • immutable — कंटेंट-हैश फ़ाइलों के लिए पैटर्न (Vite के /assets/*) जिन्हें ब्राउज़र एक वर्ष के लिए कैश कर सकते हैं। इसका उपयोग केवल कंटेंट-हैश फ़ाइलों के लिए करें। यहाँ मेल खाने वाली कोई भी अन्य फ़ाइल एक वर्ष तक ब्राउज़र में पुरानी बनी रहेगी, और इसे वापस लेने का कोई तरीका नहीं है।
  • headers — उन पथों के लिए प्रतिक्रिया हेडर जिनसे एक नियम मेल खाता है (एक सटीक पथ, या /* में समाप्त होने वाला उपसर्ग)। null मान एक हेडर को हटा देता है।
  • redirects — रीडायरेक्ट नियम, जहाँ पहला मिलान जीतता है। एक मौजूदा फ़ाइल हमेशा एक नियम को हरा देती है। status 301 है जब तक आप इसे सेट नहीं करते।
  • "static": "dist" — जब आपको कुछ और नहीं चाहिए तो { "source": "dist" } के लिए एक आशुलिपि।
  • "static": false — अगले पुश पर तैनात साइट को हटा दें। कुंजी को हटाने से केवल यह प्रोजेक्ट स्टेटिक फ़ाइलों को प्रबंधित करना बंद कर देता है, और जो तैनात है वह बना रहता है।

साइट को उसके अपने होस्ट के रूट से परोसा जाता है, इसलिए आपके बंडलर का डिफ़ॉल्ट base (/) सही है। टेलीग्राम वेब मिनी ऐप्स को iframe में खोलता है, इसलिए X-Frame-Options या प्रतिबंधित frame-ancestors नीति न जोड़ें।

वही होस्ट /api/ के तहत आपके एंडपॉइंट्स का जवाब देता है — वही है जहाँ Telegram.WebApp.Serverless.call('getProfile', …) जाता है। बिल्ड फ़ोल्डर में /api/ के तहत अपनी खुद की फ़ाइलें न रखें।

डेटाबेस

हर बॉट को अपना डेटाबेस मिलता है — एक SQLite-समर्थित स्टोर जो इनवोकेशंस के बीच बना रहता है और db के माध्यम से हर मॉड्यूल के लिए उपलब्ध है। आप अपने टेबल्स का वर्णन schema.js में एक छोटे, टाइप्ड DSL के साथ करते हैं; आप उन्हें एक धाराप्रवाह क्वेरी बिल्डर के साथ पढ़ते और लिखते हैं; और आप उन्हें समीक्षा किए गए माइग्रेशन के साथ विकसित करते हैं।

क्या Drizzle ORM जानते हैं? फिर आप पहले से ही जानते हैं कि यहाँ डेटाबेस से कैसे बात करनी है। स्कीमा DSL और क्वेरी बिल्डर Drizzle का पालन करते हैं — कॉलम बिल्डर्स, select().from().where(), ऑपरेटर, onConflictDoUpdate, .returning(), और sql टैग सभी वैसे ही व्यवहार करते हैं जैसे आप उम्मीद करेंगे, इसलिए डेटा पढ़ना और लिखना वह परिचित एपीआई है जिसका आप पहले से उपयोग करते हैं। आप बस इसे sdk/db से आयात करते हैं, और प्लेटफ़ॉर्म की कुछ विशिष्टताएं (विशेष रूप से कोई विदेशी कुंजी नहीं ) इंगित की जाती हैं जहाँ वे सामने आती हैं।

टेबल्स घोषित करना

टेबल्स schema.js में नामित निर्यात (named exports) हैं। 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 एसक्यूएल टेबल का नाम है;
  • columns जेएस प्रॉपर्टी का एक नक्शा है → कॉलम परिभाषा;
  • extras एक वैकल्पिक कॉलबैक (t) => ({ … }) है जहाँ t कॉलम को उजागर करता है (t.created उस कॉलम का संदर्भ है) — यहाँ इंडेक्स और टेबल-स्तरीय बाधाएं घोषित करें।

कॉलम प्रकार

फैक्ट्री SQLite प्रकार नोट्स
text() TEXT
integer() INTEGER
real() REAL उपनाम float()
numeric() NUMERIC
blob() BLOB पढ़ता/लिखता Uint8Array
boolean() INTEGER 0/1 के रूप में संग्रहीत, true/false के रूप में पढ़ा गया
json() TEXT ऑटो JSON.stringify / JSON.parse

कॉलम का नाम तर्क वैकल्पिक है — इसे छोड़ दें और जेएस कुंजी का उपयोग किया जाता है। mode विकल्प नियंत्रित करता है कि मान SQLite और जावास्क्रिप्ट के बीच कैसे परिवर्तित होते हैं।

mode के रूप में संग्रहीत जेएस मान
boolean INTEGER 0/1 boolean
json TEXT (JSON) कोई भी ऑब्जेक्ट/ऐरे
timestamp INTEGER (unix सेकंड्स) Date
timestamp_ms INTEGER (unix ms) Date
bytes BLOB Uint8Array

boolean() और json() integer(name, { mode: 'boolean' }) और text(name, { mode: 'json' }) के लिए आशुलिपि हैं।

एक blob() Uint8Array को पढ़ता और लिखता है — रनटाइम में कोई नोड Buffer नहीं है (और एक Buffer एक Uint8Array सबक्लास है, इसलिए यह पोर्टेबल बेस प्रकार है)। ऊपर दिए गए mode केवल यह नियंत्रित करते हैं कि एक मान कैसे एन्कोड किया जाता है, कॉलम के भंडारण प्रकार से स्वतंत्र: इसलिए blob('col', { mode: 'json' }) json() का BLOB समकक्ष है — वही JSON एन्कोडिंग, TEXT के बजाय BLOB कॉलम में रखा गया है।

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('replaced by login')   // कॉलम को हटाने के लिए चिह्नित करता है

एक json() कॉलम पर .default() आपके लिए मान को एन्कोड करता है; एक sql`…` डिफ़ॉल्ट को शब्दशः पारित किया जाता है। .deprecated() टर्मिनल है — देखें माइग्रेशन ।

इंडेक्स और बाधाएं

इंडेक्स और बाधाएं extras कॉलबैक में घोषित की जाती हैं, जहाँ कॉलम स्कोप में होते हैं।

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('reason')।

कोई विदेशी कुंजी नहीं

रनटाइम PRAGMA foreign_keys बंद के साथ चलता है। एक घोषित विदेशी कुंजी चुपचाप निष्क्रिय होगी — कोई कैस्केड नहीं, कोई अनाथ सुरक्षा नहीं — जो बिल्कुल भी न होने से बदतर है, इसलिए DSL इसे असंभव बना देता है। अर्थात्, .references() और टेबल-स्तरीय foreignKey() घोषित होने पर थ्रो करते हैं, और एक स्कीमा जो उनका उपयोग करता है वह तैनात नहीं होगा।

आपको सादे कॉलम (userId: integer('user_id')) के साथ संबंधों को मॉडल करना चाहिए और एप्लिकेशन कोड में अखंडता (integrity) लागू करनी चाहिए: बच्चों से पहले माता-पिता डालें, माता-पिता से पहले बच्चों को हटाएं, त्रुटियों को ठीक से संभालें और जब आपको ज़रूरत हो तो LEFT JOIN … WHERE parent.id IS NULL के साथ अनाथों को स्वीप करें।

विदेशी कुंजियों की अनुपस्थिति एक जानबूझकर की गई बाधा है, चूक नहीं। अपने बॉट की योजना बनाते समय शुरू में ही इसके लिए हिसाब रखें।

क्वेरी करना

db एक धाराप्रवाह क्वेरी बिल्डर है। हर क्वेरी एसिंक्रोनस है — टर्मिनल विधियां (.all(), .get(), .values(), .run()) प्रॉमिस लौटाती हैं, इसलिए हमेशा 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();

// कस्टम प्रक्षेपण: { 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();

// पंक्ति गिनती — एक सहायक, बिल्डर टर्मिनल नहीं:
await db.$count(todos);                        // सभी पंक्तियाँ
await db.$count(todos, eq(todos.done, false)); // एक फ़िल्टर के साथ
  • चेन करने योग्य: .where(), .orderBy(), .limit(), .offset(), .groupBy(), .having(), .distinct()।
  • टर्मिनल: .all(), .get(), .values()।
  • db.$count(table, where?) के साथ पंक्तियों की गिनती करें (या प्रक्षेपण में count())।

डालें, अपडेट करें, हटाएं:

await db.insert(todos).values({ userId: 1, text: 'Buy milk' }).run();
await db.insert(todos).values([{ text: 'A' }, { text: 'B' }]).run();   // बैच
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();

ध्यान दें कि एक बैच इंसर्ट एक स्टेटमेंट है, इसलिए यह 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)। एग्रीगेट्स (count/sum/avg/min/max) एक .select({ … }) प्रक्षेपण के लिए sql टुकड़े हैं।

Raw SQL — जब बिल्डर पर्याप्त नहीं होता है, तो raw SQL पर जाएं। मोड को विधि द्वारा चुना जाता है: लिखने के लिए 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, 'buy milk']]

एक राइट एक रन परिणाम में हल हो जाता है — rowsAffected, lastInsertRowid, और rows, जो एक RETURNING क्लॉज़ ले जाते हैं यदि SQL के पास एक था। जब कोई क्वेरी एक ही नाम के दो कॉलम प्रोजेक्ट करती है (एक जॉइन जैसे SELECT u.id, o.id) तो db.values के लिए पहुंचें: db.all कॉलम नाम द्वारा पंक्तियों को कुंजीबद्ध करता है, इसलिए दूसरा id पहले को ओवरराइट कर देगा।

sql`…` टैग ${value} को एक बाध्य पैरामीटर और ${table.column} को एक पहचानकर्ता में बदल देता है, और नेस्टेड sql टुकड़ों को जोड़ता है। बिना किसी पैरामीटर के अक्षरशः के लिए sql.raw('…') का उपयोग करें।

Raw क्वेरीज़ किसी टेबल से बंधी नहीं होती हैं, इसलिए उनकी पंक्तियाँ मोड रूपांतरण के बिना वापस आती हैं — एक बूलियन 0/1 है, एक JSON कॉलम एक स्ट्रिंग है, एक टाइमस्टैम्प एक संख्या है। केवल टेबल-बाउंड बिल्डर ही मानों को परिवर्तित करता है।

माइग्रेशन

आपका डेटाबेस बदलता है जैसे-जैसे आपका बॉट बढ़ता है। प्लेटफ़ॉर्म कोड तैनात करने को डेटा बदलने से अलग करके, और हर स्कीमा परिवर्तन को उसके जोखिम के आधार पर वर्गीकृत करके इसे सुरक्षित रखता है।

तैनात करना डेटाबेस को कभी नहीं छूता है। जब आप एक बदले हुए schema.js को npx tgcloud push करते हैं, तो प्लेटफ़ॉर्म नया स्कीमा रिकॉर्ड करता है और आपको बताता है कि डेटाबेस क्या बदलेगा — लेकिन कुछ भी लागू नहीं करता है:

npx tgcloud push       # schema.js तैनात करें; लंबित DB परिवर्तनों की रिपोर्ट करें
npx tgcloud migrate    # समीक्षा करें और उन्हें लागू करें

npx tgcloud migrate आपके स्कीमा और लाइव डेटाबेस के बीच अंतर की गणना करता है और आपको इसके माध्यम से ले जाता है। कुछ भी लागू करने से पहले आपसे हमेशा पूछा जाता है। इसका मतलब है कि एक नियमित कोड तैनाती गलती से डेटा माइग्रेशन को कभी ट्रिगर नहीं कर सकती है।

प्रत्येक लंबित परिवर्तन एक स्थिति ले जाता है जो निर्धारित करता है कि migrate इसके साथ कैसा व्यवहार करता है:

स्थिति अर्थ migrate में
safe योगात्मक और गैर-अवरोधक — एक नई टेबल, कॉलम या इंडेक्स एक ही चरण में एक साथ लागू, पुष्टि पर
warning संभावित रूप से विनाशकारी या धीमा — कुछ गिराना, या एक विशाल टेबल पर इंडेक्स एक बार में एक प्रस्तुत, प्रत्येक की पुष्टि अलग से
manual स्वचालित रूप से नहीं किया जा सकता — उदाहरण के लिए एक कॉलम का प्रकार बदलना मार्गदर्शन के साथ दिखाया गया; आप इसे हाथ से करते हैं
undocumented डेटाबेस में मौजूद है लेकिन आपके स्कीमा में नहीं जागरूकता के लिए दिखाया गया; लागू नहीं किया गया

सुरक्षित परिवर्तन त्वरित और भावना में प्रतिवर्ती (reversible) हैं, इसलिए वे एक साथ गुजरते हैं। प्रत्येक चेतावनी एक जानबूझकर, व्यक्तिगत पुष्टि है — विनाशकारी परिवर्तनों के लिए कोई “सभी लागू करें” नहीं है।

मैनुअल परिवर्तन एक कारण और सुझाई गई कार्रवाई के साथ आते हैं। migrate एक सारांश के साथ समाप्त होता है: कितने परिवर्तन लागू किए गए, छोड़ दिए गए, मैन्युअल सुधार की प्रतीक्षा कर रहे, या आपके स्कीमा में नहीं थे।

migrate देखें झंडे के लिए (--dry-run, --safe, --yes, --local)।

चीजों को हटाना

schema.js से एक टेबल या कॉलम को हटाना उसे ड्रॉप नहीं करता है — यह एक आकस्मिक विलोपन को विनाशकारी बना देगा। कुछ हटाने के लिए, इसे मूल्यह्रास (deprecated) के रूप में चिह्नित करें:

// एक कॉलम ड्रॉप करें
text('email').deprecated('replaced by login')

// पूरी टेबल ड्रॉप करें
export const oldSessions = table('old_sessions', { /* … */ }).deprecated('unused');

अगले migrate पर, मूल्यह्रास वस्तु एक चेतावनी-स्थिति ड्रॉप के रूप में दिखाई देती है जिसे आप व्यक्तिगत रूप से पुष्टि करते हैं। एक बार जब यह चला जाता है, तो घोषणा हटा दें।

कॉलम का प्रकार बदलना

प्रकार के परिवर्तन मैनुअल हैं — SQLite हमेशा उन्हें जगह में नहीं कर सकता है, और मौजूदा मानों को मजबूर करना एक निर्णय कॉल है। migrate परिवर्तन और उसके तर्क को दिखाएगा; इसे स्वयं raw SQL (db.run(...)) के साथ करें, आमतौर पर एक नया कॉलम या टेबल बनाकर, डेटा कॉपी करके, और स्वैपिंग करके।

SDK

रनटाइम पर एक मॉड्यूल की एक लाइब्रेरी होती है: sdk। यह उन तीन चीजों को बंडल करता है जिनकी एक बॉट बैकएंड को आवश्यकता होती है — एक डेटाबेस, टेलीग्राम बॉट एपीआई, और आउटबाउंड HTTP — बिना कुछ इंस्टॉल किए और बिना कोई क्रेडेंशियल कॉन्फ़िगर किए। डेटाबेस (db) डेटाबेस में कवर किया गया है; यह अनुभाग api, fetch, और console ग्लोबल को कवर करता है।

import { db, api, fetch, InputFile, BotApiError, EndpointError } from 'sdk';   // पूरी सतह
// या सबमॉड्यूल से:
import { table, integer, text, eq, sql } from 'sdk/db';
import { api } from 'sdk/api';
import { fetch } from 'sdk/fetch';
आयात यह क्या है
db डेटाबेस — क्वेरी बिल्डर और स्कीमा DSL → डेटाबेस
api टेलीग्राम बॉट एपीआई — api.sendMessage(...) → नीचे
fetch आउटबाउंड HTTP → नीचे
InputFile अपलोड करने के लिए एक फ़ाइल (बाइट्स + नाम) — दोनों api और fetch के साथ काम करता है → फ़ाइलें
EndpointError मिनी ऐप के कॉल को एक मैसेज के साथ अस्वीकार करें जिसे वह दिखा सकता है → एंडपॉइंट्स

अपने स्वयं के प्रोजेक्ट मॉड्यूल को सापेक्ष पथ के साथ .js एक्सटेंशन के साथ आयात करें (from '../schema.js', from '../lib/cart.js')। देखें मॉड्यूल सिस्टम ।

बॉट एपीआई

api आपको पूरा टेलीग्राम बॉट एपीआई देता है। किसी भी विधि को api.<method>(params) के रूप में कॉल करें। प्रत्येक वर्तमान — और भविष्य — बॉट एपीआई विधि बिना SDK अपडेट के काम करती है।

import { api } from 'sdk';

const me = await api.getMe();                            // → अनरैप्ड परिणाम
await api.sendMessage({ chat_id: id, text: 'Hello!' });
await api.editMessageText({ chat_id, message_id, text: 'Updated' });
await api.answerCallbackQuery({ callback_query_id, text: 'Done' });

प्रतिक्रिया लिफाफा (envelope) अनरैप किया गया है। बॉट एपीआई सामान्य रूप से परिणामों को { ok: true, result: … } में लपेटता है। api सीधे result लौटाता है — getMe() उपयोगकर्ता ऑब्जेक्ट में हल होता है, न कि रैपर में। पैरामीटर बॉट एपीआई के अपने snake_case नामों (chat_id, message_id, reply_markup, …) का उपयोग करते हैं।

विफलताएं BotApiError थ्रो करती हैं। जब बॉट एपीआई { ok: false } लौटाता है, तो कॉल एक गलत मान लौटाने के बजाय BotApiError थ्रो करती है, इसलिए आप इसे गलती से अनदेखा नहीं कर सकते। त्रुटि .code (बॉट एपीआई error_code) , .description (मानव-पठनीय मैसेज) , .method (कौन सी विधि विफल रही) , और .parameters (अतिरिक्त डेटा जैसे 429 पर retry_after, या 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;
  }
}

फ़ाइलें

फ़ाइल बाइट्स के साथ काम करें, न कि केवल file_ids के साथ — नई फ़ाइलें अपलोड करें और मौजूदा फ़ाइलों को डाउनलोड करें।

अपलोड — InputFile। कच्चे बाइट्स को एक InputFile (एक सादा धारक: बाइट्स, एक फ़ाइल नाम, और एक वैकल्पिक MIME type) में लपेटें और इसे वहां पास करें जहाँ बॉट एपीआई विधि एक फ़ाइल की उम्मीद करती है:

import { api, InputFile } from 'sdk';

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

// params के अंदर नेस्टेड फ़ाइलें भी काम करती हैं — उदाहरण के लिए एक एल्बम:
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' }) },
]});

बाइट्स को एक वास्तविक मल्टीपार्ट अपलोड के रूप में टेलीग्राम में स्ट्रीम किया जाता है — फ़ाइल को प्लेटफ़ॉर्म पर एक ही कॉल में पूरा बफ़र नहीं किया जाता है — लेकिन टेलीग्राम के बॉट एपीआई सीमाएं अभी भी लागू होती हैं: एक बॉट वर्तमान में प्रति फ़ाइल 50 MB तक (और फोटो के लिए 10 MB) अपलोड कर सकता है।

डाउनलोड — file_id द्वारा। दो सहायक फ़ाइल के बाइट्स को वापस खींचते हैं। प्लेटफ़ॉर्म आपके लिए file_id को हल करता है (यह getFile चलाता है और पर्दे के पीछे सामग्री लाता है), इसलिए आप बॉट टोकन या डाउनलोड URL को कभी नहीं छूते हैं:

const bytes = await api.getFileContent(file_id);          // → Uint8Array (पूरी फ़ाइल)

const { file, body } = await api.getFileStream(file_id);  // इसके बजाय स्ट्रीम करें
file.file_unique_id;                                      // getFile जानकारी
for await (const chunk of body) { /* Uint8Array */ }      // या इसे बफ़र करें: await stream.bytes()

टेलीग्राम का बॉट एपीआई getFile डाउनलोड को 20 MB पर सीमित करता है, इसलिए दोनों सहायकों के लिए यह छत है।

HTTP

fetch बाहरी दुनिया को कॉल करने के लिए एक fetch-जैसा क्लाइंट है — थर्ड-पार्टी एपीआई, वेबहुक, 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();

प्रतिक्रिया वेब प्लेटफ़ॉर्म को दर्शाती है: res.status, res.statusText, res.ok (200-299 के लिए सत्य), res.url, res.headers (.get(), .has(), .keys(), .entries()), और बॉडी रीडर्स await res.json() / await res.text()।

आप बॉडी को स्ट्रीम के रूप में वृद्धिशील रूप से भी पढ़ सकते हैं — for await (const chunk of res.body) { … } — जो वह है जिसे आप सर्वर-भेजे गए इवेंट या एआई एपीआई से टोकन-दर-टोकन आउटपुट का उपभोग करने के लिए उपयोग करते हैं।

बॉडी सहायक आपके लिए मिलान करने वाले 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('hi') });     // text/plain

अनुरोध बॉडी में फ़ाइलें और कच्चे बाइट्स भी हो सकते हैं: body के रूप में Uint8Array, InputFile, या FormData (multipart/form-data के लिए) पास करें, और बाइट्स बाहर स्ट्रीम हो जाते हैं — इसलिए आप थर्ड-पार्टी एपीआई पर उसी तरह अपलोड कर सकते हैं जैसे आप टेलीग्राम पर फ़ाइलें भेजते हैं:

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

const form = new FormData();
form.append('file', new InputFile(bytes, 'photo.jpg', { type: 'image/jpeg' }));
form.append('caption', 'from my 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 के साथ स्ट्रीमिंग आपको वृद्धिशील रूप से एक बड़ी बॉडी को प्रोसेस करने देती है, लेकिन यह सीमा को नहीं बढ़ाती है।

लॉगिंग

मानक वैश्विक console उपलब्ध है — कुछ भी आयात करने के लिए नहीं, यह बस वहीं है जैसे किसी भी जावास्क्रिप्ट में। इसका आउटपुट npx tgcloud run द्वारा कैप्चर और दिखाया जाता है, जो इसे विकास के दौरान आपका प्राथमिक डिबगिंग टूल बनाता है।

console.log('processing', { chatId: id });   // लॉग / डिबग — सादा
console.info('started');                     // जानकारी  — नीला
console.warn('rate limited');                // चेतावनी  — पीला
console.error(err);                          // त्रुटि — लाल, स्टैक ट्रेस के साथ

प्रत्येक पंक्ति को उस [file:line] के साथ टैग किया जाता है जहाँ से यह आई है। console.error और console.trace एक पूर्ण स्टैक जोड़ते हैं, जबकि console.warn नहीं करता है। जब आप npx tgcloud run करते हैं, तो ये लाइनें स्तर के अनुसार रंगीन उपसर्ग, रन शुरू होने के बाद का समय और मूल के साथ प्रिंट की जाती हैं — देखें run ।

कमांड-लाइन इंटरफ़ेस

tgcloud आपके प्रोजेक्ट फ़ोल्डर और क्लाउड के बीच का पुल है। यह प्रोजेक्ट स्कैफोल्ड करता है, आपको दिखाता है कि क्या बदला है, तैनात करता है, मॉड्यूल चलाता है, और डेटाबेस माइग्रेशन लागू करता है। इसे Node.js 18 या नया चाहिए। इसे प्राप्त करने के दो तरीके:

# अनुशंसित — इसमें इंस्टॉल किए गए CLI के साथ एक प्रोजेक्ट बनाएं:
npm create @tgcloud/bot example_bot

# या CLI को वैश्विक स्तर पर इंस्टॉल करें और एक खाली फ़ोल्डर को init करें:
npm install -g @tgcloud/cli
tgcloud init

npm पैकेज @tgcloud/cli है, और कमांड जो यह इंस्टॉल करता है tgcloud है। हर कमांड आपके प्रोजेक्ट के किसी भी सबफ़ोल्डर से काम करता है।

कमांड उद्देश्य
init वर्तमान फ़ोल्डर में एक नया प्रोजेक्ट स्कैफोल्ड करें
add एक नया मॉड्यूल स्कैफोल्ड करें (हैंडलर, एंडपॉइंट या lib मॉड्यूल)
login प्रोजेक्ट को एक बॉट से लिंक करें (टोकन सहेजता है)
status स्थानीय बनाम क्लाउड में क्या बदला है दिखाएं
diff लाइन-दर-लाइन परिवर्तन दिखाएं
push बदले हुए मॉड्यूल और मिनी ऐप के बिल्ड को क्लाउड पर तैनात करें
migrate डेटाबेस पर स्कीमा परिवर्तन लागू करें
run तैनात किए बिना प्लेटफ़ॉर्म पर एक मॉड्यूल निष्पादित करें
fetch चेक करें कि क्या तैनात है, स्थानीय फ़ाइलों को बदले बिना
pull स्थानीय फ़ाइलों को क्लाउड के अनुरूप लाएं
reset स्थानीय परिवर्तनों को छोड़ें; क्लाउड स्थिति से पुनर्स्थापित करें
webhook प्लेटफ़ॉर्म-प्रबंधित वेबहुक का निरीक्षण और पुन: सिंक करें
completion शेल पूर्णता स्क्रिप्ट प्रिंट करें (bash/zsh/fish)
upgrade एक पूर्व-0.2.0 प्रोजेक्ट लेआउट को tgcloud/ में ले जाएं

ऑथेंटिकेशन

एक प्रोजेक्ट उसके टोकन द्वारा एक बॉट से बंधा होता है, जिसका प्रारूप app<id>:<secret> है। app<id> भाग सार्वजनिक है और इसे प्रिंट किया जा सकता है; सीक्रेट लॉग या त्रुटियों में कभी नहीं दिखता है।

टोकन को इस क्रम में हल किया जाता है:

  1. TGCLOUD_TOKEN पर्यावरण चर — CI के लिए; कभी डिस्क पर नहीं लिखा जाता है।
  2. .tgcloud/credentials — npx tgcloud login द्वारा लिखित।
  3. कोई नहीं → npx tgcloud login की ओर इशारा करने वाली एक त्रुटि।

CLI मिड-कमांड टोकन के लिए कभी संकेत नहीं देता है। लॉगिन हमेशा स्पष्ट login चरण है। यदि कोई सहेजा गया टोकन अमान्य हो जाता है, तो CLI इसे हटा देता है और आपसे फिर से login करने के लिए कहता है।

init

npx tgcloud init

वर्तमान निर्देशिका में एक नया प्रोजेक्ट स्कैफोल्ड करता है: schema.js, lib/, handlers/ और एक स्टार्टर हैंडलर के साथ tgcloud/ फ़ोल्डर, प्लस tgcloud.jsonc, AGENTS.md, docs/, और .tgcloud/ स्थिति फ़ोल्डर। किसी मौजूदा फ्रंट-एंड प्रोजेक्ट के रूट में भी इसे चलाएं — यह tgcloud/ फ़ोल्डर और डॉक्स जोड़ता है, और कभी भी आपकी फ़ाइलों को ओवरराइट नहीं करता है। यदि यह फ्रंट-एंड को पहचानता है, तो यह tgcloud.jsonc को बिल्ड फ़ोल्डर पर इंगित करता है और आपकी package.json में एक deploy स्क्रिप्ट (बनाएं, फिर पुश करें) जोड़ता है — केवल यदि आपके पास पहले से deploy स्क्रिप्ट नहीं है। देखें मिनी ऐप फ्रंट-एंड ।

init किसी अन्य प्रोजेक्ट के अंदर एक प्रोजेक्ट बनाने से इनकार करता है। किसी प्रोजेक्ट के अपने रूट में इसे फिर से चलाना ठीक है और बस कुछ भी गायब होने पर जोड़ता है।

add

npx tgcloud add <target>

एक एकल नया मॉड्यूल स्कैफोल्ड करता है, वायर अप और संपादित करने के लिए तैयार — एक हैंडलर, एक एंडपॉइंट या एक lib/ मॉड्यूल।

npx tgcloud add handlers/callback_query   # एक नया अपडेट हैंडलर
npx tgcloud add endpoints/getProfile      # मिनी ऐप के लिए एक नया एंडपॉइंट
npx tgcloud add lib/cart                  # एक नया साझा मॉड्यूल

ध्यान दें कि add कभी भी मौजूदा फ़ाइल को ओवरराइट नहीं करता है।

<target> मॉड्यूल का पथ है (एक ट्रेलिंग .js वैकल्पिक है)। handlers/ के लिए, नाम एक टेलीग्राम अपडेट प्रकार होना चाहिए। प्लेटफ़ॉर्म वैध सेट का विज्ञापन करता है, इसलिए एक अमान्य नाम को सामने से ही अस्वीकार कर दिया जाता है। handlers/ और endpoints/ फ्लैट हैं, जबकि lib/ को नेस्टेड (lib/payments/stripe) किया जा सकता है।

मॉड्यूल नाम आवश्यक है। केवल निर्देशिका देना एक त्रुटि है — लेकिन एक उपयोगी: handlers/ के लिए यह उन अपडेट प्रकारों को सूचीबद्ध करता है जो आपके पास पहले से नहीं हैं, इसलिए आप एक को कॉपी कर सकते हैं।

$ npx tgcloud add handlers
Error: एक नाम निर्दिष्ट करें, उदाहरण के लिए "handlers/callback_query"। उपलब्ध handlers/ प्रकार: callback_query, inline_query, chat_member, …

<Tab> पूर्णता वही सेट प्रदान करती है — देखें completion ।

निर्मित फ़ाइल में एक लाइव export default है, इसलिए हैंडलर सक्रिय है जैसे ही आप तैनात करते हैं — टिप्पणी हटाने के लिए कुछ भी नहीं है। push के साथ नया मॉड्यूल तैनात करें।

login

npx tgcloud login

CLI एक्सेस टोकन के लिए संकेत देता है — @BotFather → आपका बॉट → Serverless → CLI Access → Access token — आपके बॉट के एपीआई टोकन से एक अलग टोकन — इसे प्लेटफ़ॉर्म के खिलाफ सत्यापित करता है, और इसे .tgcloud/credentials में सहेजता है।

login एकमात्र कमांड है जो टोकन के लिए पूछती है, और इसके लिए एक इंटरैक्टिव टर्मिनल की आवश्यकता होती है। CI में, इसके बजाय TGCLOUD_TOKEN सेट करें।

status

npx tgcloud status

दिखाता है, प्रति फ़ाइल, आपके वर्किंग डायरेक्टरी और तैनात कॉपी के बीच क्या बदला है: संशोधित, नया, हटाया गया, अपरिवर्तित। यह ऑफ़लाइन काम करता है, आपके पिछले push, pull या fetch के समय क्लाउड की स्थिति के खिलाफ तुलना करता है। यह tgcloud/ के अंदर आवारा .js फ़ाइलों के बारे में भी चेतावनी देता है जो तैनात नहीं की जाएंगी।

मिनी ऐप वाले प्रोजेक्ट के लिए, status यह भी दिखाता है कि अंतिम तैनाती के बाद से उसके बिल्ड फ़ोल्डर में क्या बदला है।

diff

npx tgcloud diff

status की तरह, लेकिन बदले हुए मॉड्यूल के लिए वास्तविक लाइन-दर-लाइन अंतर दिखाता है। यह भी ऑफ़लाइन।

push

npx tgcloud push [files...]

एक परमाणु बैच में अपने प्रोजेक्ट को क्लाउड पर तैनात करता है।

बिना किसी तर्क के, यह पूरे प्रोजेक्ट को तैनात करता है, और तैनात स्थिति को आपके फ़ोल्डर को बिल्कुल प्रतिबिंबित करने के लिए बनाया जाता है — जो मॉड्यूल आपने स्थानीय रूप से हटा दिए हैं उन्हें क्लाउड में हटा दिया जाता है।

फ़ाइल या निर्देशिका तर्कों के साथ (npx tgcloud push tgcloud/handlers/message.js, npx tgcloud push handlers/) , यह केवल उन फ़ाइलों में परिवर्तनों को तैनात करता है। एक लक्षित पुश अन्य मॉड्यूल्स को कभी नहीं हटाता है। एक लक्ष्य को फ़ाइल पथ (tgcloud/handlers/message.js) , tgcloud/ के अंदर का पथ (handlers/message.js) या मॉड्यूल नाम (handlers/message) के रूप में लिखा जा सकता है — वे सभी एक ही मॉड्यूल का मतलब हैं।

इसका एक विकल्प --force है — समवर्ती जांच (concurrency check) को छोड़ने और क्लाउड में जो कुछ भी है उसे ओवरराइट करने के लिए। इसका उपयोग केवल तभी करें जब आप सुनिश्चित हों (देखें सिंक में रहना )।

यदि tgcloud.jsonc एक स्टेटिक फ़ोल्डर का नाम लेता है, तो एक पूर्ण push इसे उसी बैच में तैनात करता है — केवल वे फ़ाइलें जो बदली हैं अपलोड की जाती हैं — और साइट का पता प्रिंट करता है। पहले बनाएं, क्योंकि push कभी भी आपका बिल्ड नहीं चलाता है (npm run deploy दोनों करता है)। npx tgcloud push dist/ केवल स्टेटिक फ़ाइलों को तैनात करता है, और एक पुश जो केवल मॉड्यूल्स का नाम लेता है उन्हें अछूता छोड़ देता है।

एक तैनाती के बाद, यदि schema.js बदल गया और डेटाबेस सिंक से बाहर है, तो push लंबित परिवर्तनों का सारांश प्रिंट करता है और npx tgcloud migrate का सुझाव देता है। यह उन्हें कभी खुद लागू नहीं करता है।

migrate

npx tgcloud migrate

डेटाबेस पर अपने स्कीमा परिवर्तन लागू करता है। यह schema.js और लाइव डेटाबेस के बीच अंतर की गणना करता है, फिर आपको एक रनिंग [N/M] काउंटर के साथ एक-एक करके ले जाता है:

  • सभी लंबित परिवर्तनों का एक संक्षिप्त सारांश।
  • सुरक्षित परिवर्तन, आपकी पुष्टि पर एक ही चरण में लागू।
  • चेतावनी (ड्रॉप, धीमी संचालन) , एक बार में एक, प्रत्येक की पुष्टि अलग से की जाती है।
  • मैनुअल परिवर्तन, एक कारण और सुझाई गई कार्रवाई के साथ दिखाया गया, स्वचालित रूप से लागू नहीं किया गया।
  • अलेखित वस्तुएं (डेटाबेस में लेकिन आपके स्कीमा में नहीं) , जागरूकता के लिए दिखाया गया।

यह सारांश के साथ समाप्त होता है: लागू किया गया, छोड़ दिया गया, मैन्युअल सुधार की प्रतीक्षा कर रहा, स्कीमा में नहीं।

मॉडल के लिए माइग्रेशन देखें।

विकल्प: --dry-run (सब कुछ प्रिंट करें, कुछ भी लागू न करें) , --safe (सुरक्षित परिवर्तनों को ऑटो-लागू करें, चेतावनी छोड़ें) , --yes (सुरक्षित परिवर्तनों और हर चेतावनी को ऑटो-लागू करें, मैन्युअल छोड़ें — सावधानी के साथ उपयोग करें) , --local (तैनात के बजाय अपने स्थानीय schema.js के खिलाफ अंतर)। एक झंडे के बिना, migrate को एक टर्मिनल की आवश्यकता होती है और अनुमान लगाने के बजाय गैर-इंटरैक्टिव वातावरण में त्रुटि करता है।

run

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

प्लेटफ़ॉर्म पर एक हैंडलर या एक एंडपॉइंट निष्पादित करता है बिना तैनात किए, अपनी वर्तमान स्थानीय फ़ाइलों का उपयोग करके। यह तर्क का परीक्षण करने के लिए तेज़ आंतरिक लूप है।

  • <module> — एक नग्न नाम (handlers/ और endpoints/ के तहत खोजा गया) या handlers/message जैसा पथ। एक नग्न नाम जो दोनों निर्देशिकाओं में मौजूद है, उसे अपनी निर्देशिका के साथ लिखा जाना चाहिए।
  • [args] — हैंडलर को पारित पेलोड, JSON5 में लिखा गया ताकि आप उद्धरण कुंजियों को छोड़ सकें। यह अपडेट-प्रकार ऑब्जेक्ट है जो आपका हैंडलर प्राप्त करता है (जैसे handlers/message के लिए एक Message) , या एक एंडपॉइंट का input, और यह एक ऑब्जेक्ट होना चाहिए — ऐरे या नग्न स्ट्रिंग नहीं।
  • --ctx <json5> — मॉड्यूल का संदर्भ ऑब्जेक्ट (इसका दूसरा तर्क) , भी JSON5। इसका उपयोग उन चीजों की आपूर्ति करने के लिए करें जो आपका कोड ctx से पढ़ता है — हैंडलर के लिए कच्चा अपडेट, --ctx '{ update: { update_id: 1 } }', या एंडपॉइंट के लिए init डेटा, --ctx '{ initData: { user: { id: 1 } } }'। run इसे सत्यापित नहीं करता है, क्योंकि आप अपने तर्क का परीक्षण कर रहे हैं, प्लेटफ़ॉर्म की जांच नहीं।
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hi" }'

प्लेटफ़ॉर्म आपके स्थानीय प्रोजेक्ट (इसलिए स्थानीय रूप से बदले गए lib/ कोड का भी उपयोग किया जाता है) से इकट्ठे मॉड्यूल स्पेस के खिलाफ मॉड्यूल चलाता है और रिटर्न मान, console.* के साथ लॉग की गई कोई भी चीज़, और बीता हुआ समय लौटाता है। npx tgcloud run handlers/message "$(cat message.json5)" के साथ एक फ़ाइल से बड़े तर्क पढ़ें।

fetch

npx tgcloud fetch

आपकी वर्किंग फ़ाइलों को छुए बिना, चेक करता है कि वर्तमान में क्या तैनात है। इसके बाद, status और diff नवीनतम क्लाउड स्थिति के खिलाफ तुलना करते हैं। हल करने का निर्णय लेने से पहले संघर्ष को देखने के लिए उपयोगी।

pull

npx tgcloud pull

अपनी वर्किंग फ़ाइलों को तैनात स्थिति में अपडेट करके अपने स्थानीय प्रोजेक्ट को क्लाउड के अनुरूप लाता है।

pull कभी भी आपके मिनी ऐप का बिल्ड फ़ोल्डर नहीं लिखता है।

reset

npx tgcloud reset

अपने स्थानीय परिवर्तनों को छोड़ देता है और पिछले ज्ञात क्लाउड स्थिति से मॉड्यूल को पुनर्स्थापित करता है। एक प्रयोग को फेंकने के लिए इसका उपयोग करें। मिनी ऐप का बिल्ड फ़ोल्डर नहीं छुआ जाता है।

webhook

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

टेलीग्राम आपके बॉट को एक वेबहुक के माध्यम से अपडेट वितरित करता है, जिसे प्लेटफ़ॉर्म आपके लिए प्रबंधित करता है — आप कभी भी इसे हाथ से कहीं भी इंगित नहीं करते हैं। npx tgcloud webhook इसकी वर्तमान स्थिति दिखाता है: URL, allowed_updates सूची, कितने अपडेट लंबित हैं, अंतिम डिलीवरी त्रुटि (यदि कोई हो) , और क्या यह सिंक में है आपके तैनात हैंडलर्स के साथ।

“सिंक में” का मतलब है कि वेबहुक प्लेटफ़ॉर्म की ओर इशारा करता है और इसकी allowed_updates आपके तैनात हैंडलर्स से मेल खाती हैं — इसलिए टेलीग्राम बिल्कुल वही अपडेट प्रकार वितरित करता है जिन्हें आप संभालते हैं, और कुछ भी नहीं। एक नया हैंडलर तैनात करना (या एक हटाना) वेबहुक को सिंक से बाहर छोड़ सकता है जब तक कि इसे ताज़ा न किया जाए; npx tgcloud status इसे भी ध्वजांकित करता है।

npx tgcloud webhook sync इसे ठीक करता है — यह वेबहुक को प्लेटफ़ॉर्म पर फिर से इंगित करता है और आपके तैनात हैंडलर्स से allowed_updates को फिर से बनाता है। उन अपडेट को छोड़ने के लिए --drop-pending जोड़ें जिन्हें टेलीग्राम ने सिंक से पहले ही कतारबद्ध कर दिया था (अन्यथा वेबहुक स्वस्थ होने के बाद उन्हें फिर से वितरित किया जाता है)।

completion

नोट: टैब-पूर्णता केवल तब काम करती है जब एक नग्न tgcloud आपके PATH पर हो — इसलिए इसे वैश्विक स्तर पर इंस्टॉल करें (npm install -g @tgcloud/cli) या अन्यथा बाइनरी को अपने PATH पर रखें। यह npx में हुक नहीं कर सकता।

tgcloud completion <bash|zsh|fish>

stdout के लिए एक शेल पूर्णता स्क्रिप्ट प्रिंट करता है। इसे एक बार सक्षम करें, फिर <Tab> कमांड, झंडे, मॉड्यूल निर्देशिकाएं, हैंडलर अपडेट-प्रकार जो आपके पास अभी तक नहीं हैं, और आपके स्थानीय चलाने योग्य मॉड्यूल को पूरा करता है।

# 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

उसके बाद अपना शेल रीस्टार्ट करें (या फ़ाइल को फिर से स्रोत करें)। बिना शेल के tgcloud completion चलाना फिर से ये निर्देश प्रिंट करता है।

upgrade

npx tgcloud upgrade [--dry-run]

CLI 0.2.0 से पहले बनाए गए प्रोजेक्ट्स के लिए एकमुश्त माइग्रेशन, जो schema.js, lib/ और handlers/ को प्रोजेक्ट रूट पर रखते थे। यह उन्हें tgcloud/ में ले जाता है। बॉट से आपका लिंक बना रहता है, और कुछ भी फिर से तैनात करने की आवश्यकता नहीं है। --dry-run बिना कुछ छुए नियोजित चाल प्रिंट करता है। अन्य कमांड पुराने लेआउट पर तब तक नहीं चलते जब तक आप अपग्रेड नहीं करते।

सिंक में रहना

हर प्रोजेक्ट का क्लाउड में एक मोनोटोनिक रूप से बढ़ता संशोधन (revision) है, जो प्रत्येक तैनाती पर बंप किया जाता है। CLI उस संशोधन को याद रखता है जिसके साथ उसने अंतिम बार सिंक किया था और प्रत्येक push पर उसे भेजता है। यदि क्लाउड आगे बढ़ गया है — क्योंकि किसी अन्य मशीन या टीम के साथी ने तैनात किया है — तो पुश उनके काम को चुपचाप ओवरराइट करने के बजाय अस्वीकार कर दिया जाता है, और CLI आगे बढ़ने के तीन तरीके प्रदान करता है:

npx tgcloud fetch           # देखें कि क्लाउड में क्या बदला है, फिर पुनः चेक करें
npx tgcloud pull            # नवीनतम को अपनी वर्किंग फ़ाइलों में लाएं
npx tgcloud push --force    # क्लाउड स्थिति को ओवरराइट करें (खतरनाक)

यह आशावादी-समवर्ती जांच (optimistic-concurrency check) यही कारण है कि आप बिना लॉकस्टेप तैनाती प्रक्रिया के एक टीम में एक बॉट साझा कर सकते हैं। कमांड विफलता पर गैर-शून्य बाहर निकलते हैं — एक अस्वीकृत तैनाती, एक विफल माइग्रेशन, एक प्रमाणीकरण त्रुटि, एक मॉड्यूल जिसने run के दौरान थ्रो किया — इसलिए वे स्क्रिप्ट और CI पाइपलाइनों में सफाई से काम करते हैं।