টেলিগ্রাম সার্ভারলেস (Telegram Serverless) আপনাকে আপনার বোট এবং মিনি অ্যাপের ব্যাকএন্ড কোড সরাসরি টেলিগ্রামের অবকাঠামোতে চালানোর সুবিধা দেয় — এর জন্য কোনো সার্ভার প্রভিশন করতে হয় না, কোনো কন্টেইনার চালু রাখতে হয় না, এবং স্কেলিং নিয়ে চিন্তা করতে হয় না। আপনি সাধারণ জাভাস্ক্রিপ্ট মডিউল লিখবেন, একটি মাত্র কমান্ড দিয়ে ডিপ্লয় করবেন, আর টেলিগ্রাম সেগুলো বোট এপিআই এবং বিল্ট-ইন ডাটাবেসের ঠিক পাশেই একটি দ্রুত ও বিচ্ছিন্ন (isolated) ভি৮ (V8) স্যান্ডবক্সে চালাবে।
যদি আপনি কখনো একটি /start কমান্ডের উত্তর দেওয়ার জন্য কোনো বোটকে ভিপিএস (VPS), ক্লাউড ফাংশন বা হোস্টিং প্যানেলের সাথে যুক্ত করে থাকেন, তবে এই কাজটি আর আপনার করতে হবে না।
সার্ভারলেস কেন
একটি টেলিগ্রাম বোট মূলত এমন একটি প্রোগ্রাম যা আপডেটের প্রতিক্রিয়া জানায়। ঐতিহাসিকভাবে, আপনাকে সেই প্রোগ্রামটি এমন কোথাও হোস্ট করতে হতো যা সবসময় চালু, সচল এবং নিরাপদ থাকে — এবং সবসময় সেভাবেই রাখতে হতো। টেলিগ্রাম সার্ভারলেস সেই স্তরটিকে পুরোপুরি সরিয়ে ফেলে:
- কোনো অবকাঠামো নেই। ভাড়া নেওয়ার, প্যাচ করার বা মনিটর করার মতো কোনো মেশিন নেই। আপনার কোড চাহিদা অনুযায়ী চলে এবং আপনার বোটের সাথে স্বয়ংক্রিয়ভাবে স্কেল হয়।
- ক্রেডেনশিয়াল অন্তর্ভুক্ত। টেলিগ্রাম বোট এপিআই, এসকিউলাইট (SQLite)-ভিত্তিক ডাটাবেস এবং আউটবাউন্ড এইচটিটিপি প্রতিটি মডিউলের জন্য ডিফল্টভাবে পাওয়া যায় — কিছুই ইনস্টল করতে হয় না, কোনো ক্রেডেনশিয়াল সেটআপ করতে হয় না।
- দ্রুত ও বিচ্ছিন্ন এক্সিকিউশন। প্রতিটি ইনভোকেশন একটি হালকা ভি৮ আইসোলেটে চলে, যা টেলিগ্রামের নিজস্ব সিস্টেমের কাছাকাছি থাকে, তাই বোট এপিআই এবং আপনার ডাটাবেসে কলগুলো দ্রুত এবং নির্ভরযোগ্য হয়।
- একটি প্রকৃত ডেভেলপার ওয়ার্কফ্লো। একটি প্রজেক্ট আপনার মেশিনের একটি ফোল্ডারে ভার্সন কন্ট্রোলের অধীনে থাকে। আপনি ফাইল এডিট করেন, ঠিক কী পরিবর্তন হয়েছে তা দেখেন, অ্যাটমিকভাবে ডিপ্লয় করেন এবং রিভিউ করা মাইগ্রেশনের মাধ্যমে ডাটাবেস স্কিমা আপডেট করেন — ঠিক যেভাবে আপনি অন্যান্য কাজের ক্ষেত্রে করেন।
মানসিক মডেল: প্রজেক্টটি কীভাবে কাজ করে
আপনি তিনটি জায়গায় কাজ করবেন এবং সেগুলো একে অপরের সাথে স্বচ্ছভাবে সংযুক্ত:
| কোথায় | কী থাকে |
|---|---|
| আপনার প্রজেক্ট ফোল্ডার | জাভাস্ক্রিপ্ট মডিউল — স্কিমা, শেয়ার্ড কোড, আপডেট হ্যান্ডলার, মিনি অ্যাপ এন্ডপয়েন্ট — এবং যদি থাকে, তবে মিনি অ্যাপের ফ্রন্ট-এন্ড |
| ক্লাউড | সেই মডিউলগুলোর ডিপ্লয় করা কপি, আপনার বোটের ডাটাবেস এবং মিনি অ্যাপের ফাইলগুলো |
tgcloud সিএলআই |
ব্রিজ (সেতু) — এটি আপনাকে পার্থক্যগুলো দেখায় এবং সিঙ্ক করে |
আপনাকে কখনো এসএসএইচ (SSH) করতে হবে না। আপনি স্থানীয়ভাবে ফাইল এডিট করবেন, npx tgcloud push চালাবেন, আর প্ল্যাটফর্ম বাকিটা সামলে নেবে। আপনার বোটের ট্রাফিক ডিপ্লয় করা মডিউল দ্বারা পরিচালিত হয়; আপনার ডাটাবেস ইনভোকশনের মাঝেও টিকে থাকে।
একটি প্রজেক্টে মাত্র চার ধরনের কোড থাকে, সব একটি tgcloud/ ফোল্ডারের ভেতরে:
tgcloud/
├─ endpoints/ # আপনার মিনি অ্যাপ যে ফাংশনগুলো কল করে
├─ handlers/ # টেলিগ্রাম আপডেট টাইপের প্রতিক্রিয়া জানায় এমন ফাংশন
├─ lib/ # যেকোনো জায়গা থেকে ইমপোর্ট করা শেয়ার্ড কোড
└─ schema.js # আপনার ডাটাবেস টেবিলসমূহ
যখন কোনো আপডেট আসে — একটি মেসেজ, বাটন প্রেস, ইনলাইন কোয়েরি — টেলিগ্রাম সেটিকে সংশ্লিষ্ট হ্যান্ডলারে (handlers/message.js, handlers/callback_query.js, …) পাঠিয়ে দেয় এবং তার ডিফল্ট এক্সপোর্টকে কল করে। সেই ফাংশনটি এসডিকে (SDK)-এর মাধ্যমে বোট এপিআই এবং ডাটাবেসের সাথে কথা বলে এবং রিটার্ন করে। এটাই পুরো লুপ। কোনো হ্যান্ডলার ছাড়া আপডেটগুলো উপেক্ষা করা হয়, তাই আপনার প্রয়োজন অনুযায়ী হ্যান্ডলার যুক্ত করুন।
মিনি অ্যাপ ঐচ্ছিক। যদি আপনার বোটের একটি থাকে, তবে এর ফ্রন্ট-এন্ড একই প্রজেক্টে tgcloud/-এর পাশে থাকে। সার্ভারলেস এর বিল্ড আউটপুট হোস্ট করে এবং এটি যে এন্ডপয়েন্টগুলোকে কল করে তা চালায়। দেখুন
মিনি অ্যাপ ফ্রন্ট-এন্ড
।
দ্রুত ডেমো
এখানে একটি পূর্ণাঙ্গ, কার্যকরী ডেমো বোট দেওয়া হলো। এটি প্রতিটি মেসেজের উত্তর দেয় এবং প্রতিটি চ্যাট থেকে কয়টি মেসেজ পেয়েছে তা মনে রাখে।
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: `হ্যালো! আমি আপনার থেকে ${row.seen}টি মেসেজ পেয়েছি।`,
});
}
ডিপ্লয় করুন:
npx tgcloud push # মডিউলগুলো আপলোড করুন
npx tgcloud migrate # `counters` টেবিল তৈরি করুন
এটি একটি লাইভ বোট যা স্টেট বজায় রাখে এবং কোনো সার্ভার লাগে না। এর ভেতরের সবকিছুই — api, db, table() ডিএসএল — নিচের সেকশনগুলোতে বর্ণনা করা হয়েছে।
সার্ভারলেস টেলিগ্রাম বোট এবং মিনি অ্যাপের জন্য একটি সাধারণ ব্যাকএন্ড, কোনো নির্দিষ্ট ধরণের অ্যাপের টেমপ্লেট নয়। এটি নিচের কাজগুলোর জন্য আদর্শ:
- কথোপকথনমূলক এআই (Conversational AI) বোট যেগুলোর ডাটাবেসে ব্যবহারকারীর স্টেট সংরক্ষণ করতে হয়।
- মিনি অ্যাপ ব্যাকএন্ড যা ব্যবহারকারীর ডাটা সংরক্ষণ করে এবং ডায়নামিক কন্টেন্ট প্রদান করে।
- গেমস এবং টুলস — লিডারবোর্ড, কুইজ এবং আরও অনেক কিছু।
- অটোমেশন এবং ইন্টিগ্রেশন যা থার্ড-পার্টি এইচটিটিপি এপিআই কল করে এবং চ্যাটে ফলাফল পাঠায়।
শুরু করা
এই গাইডটি আপনাকে একটি খালি ফোল্ডার থেকে এমন একটি লাইভ বোট পর্যন্ত নিয়ে যাবে যা মেসেজের উত্তর দেয় এবং ডাটা সংরক্ষণ করে। এটি ধরে নেওয়া হয়েছে যে আপনার পিসিতে
Node.js
১৮ বা তার পরবর্তী ভার্সন ইনস্টল করা আছে এবং
@BotFather
-এ একটি বোট রেজিস্টার করা আছে। শেষ পর্যন্ত আপনি আপনার দৈনন্দিন কাজের জন্য প্রয়োজনীয় সব কমান্ড ব্যবহার করবেন: push, migrate, run, এবং status।
অন্য কিছু করার আগে, সার্ভারলেস চালু করুন। @BotFather -এ আপনার বোট ওপেন করুন → Serverless এবং এটি অন করুন। এটি বোটটির জন্য ফিচারটি চালু করবে এবং এর সিএলআই অ্যাক্সেস টোকেন, হ্যান্ডলার, লাইব্রেরি এবং ডাটাবেস আনলক করবে।
১. একটি প্রজেক্ট তৈরি করুন
শুরু করার দ্রুততম উপায় হলো প্রজেক্ট ক্রিয়েটর, যা একটি প্রজেক্ট তৈরি করে এবং তাতে সিএলআই ইনস্টল করে:
npm create @tgcloud/bot example_bot
cd example_bot
আর্গুমেন্টটি হলো টার্গেট ফোল্ডার: বর্তমান ফোল্ডারে তৈরির জন্য . ব্যবহার করুন অথবা অন্য যেকোনো পাথ দিন। এটি বিদ্যমান ফোল্ডারেও কাজ করে এবং আপনার ফাইলগুলোকে ওভাররাইট করে না।
এটি আপনাকে এডিট করার জন্য প্রস্তুত একটি প্রজেক্ট দেবে:
example_bot/
├─ tgcloud/ # প্ল্যাটফর্ম যা কিছু চালায়
│ ├─ handlers/
│ │ └─ message.js # একটি স্টার্টার মেসেজ হ্যান্ডলার (টেক্সট ইকো করে)
│ ├─ lib/ # আপনার শেয়ার্ড মডিউলগুলো এখানে রাখুন (শুরুতে খালি)
│ └─ schema.js # আপনার ডাটাবেস টেবিলসমূহ
├─ docs/
│ └─ tgcloud-sdk.md # এসডিকে রেফারেন্স (আপনার এবং এআই টুলের জন্য)
├─ AGENTS.md # এআই কোডিং অ্যাসিস্ট্যান্টদের জন্য নির্দেশিকা
├─ package.json
└─ tgcloud.jsonc # প্রজেক্ট কনফিগারেশন
স্কাফোল্ড করা ফাইলগুলো নিজেই নিজের ব্যাখ্যা দেয় — প্রতিটিতে পরবর্তী কাজের জন্য কমেন্ট করা উদাহরণ রয়েছে।
সিএলআই প্রজেক্টে একটি লোকাল ডেভ-ডিপেন্ডেন্সি হিসেবে ইনস্টল হয়, তাই আপনি npx tgcloud <command> ব্যবহার করে এটি চালাবেন (npx আপনার প্রজেক্টের node_modules-এ থাকা কপিটি খুঁজে পাবে), অথবা package.json-এ যোগ করা শর্টকাট (npm run deploy, npm run status) ব্যবহার করতে পারেন। ডিফল্টভাবে আপনার PATH-এ কোনো গ্লোবাল tgcloud থাকে না।
আপনি চাইলে এটি গ্লোবালিও ইনস্টল করতে পারেন — npm install -g @tgcloud/cli — যদি আপনি যেকোনো জায়গা থেকে সরাসরি tgcloud টাইপ করতে চান। এটি যেকোনো খালি ফোল্ডারে tgcloud init করার জন্য সুবিধাজনক। যেভাবে ইচ্ছা ব্যবহার করুন, প্রজেক্ট একই থাকবে।
মিনি অ্যাপ তৈরি করছেন? এর ফ্রন্ট-এন্ড থেকে শুরু করুন। আপনার প্রচলিত টুল দিয়ে এটি তৈরি করুন, তারপর সেই ফোল্ডারে প্রজেক্ট ক্রিয়েটর চালান। দেখুন মিনি অ্যাপ ফ্রন্ট-এন্ড ।
২. আপনার বোট লিঙ্ক করুন
প্রতিটি প্রজেক্ট একটি বোটের সাথে যুক্ত। login কমান্ড দিয়ে সেগুলোকে সংযুক্ত করুন, যা আপনার সিএলআই অ্যাক্সেস টোকেন চাইবে (
@BotFather
→ আপনার বোট → Serverless → CLI Access → Access token — এটি আপনার
বোটের এপিআই টোকেন
থেকে আলাদা) এবং লোকালি সেভ করবে:
npx tgcloud login
টোকেনটি app<id>:<secret> ফরম্যাটে হয়। সিএলআই এটি .tgcloud/ ফোল্ডারে রাখে, যা গিট-ইগনোর (git-ignored) করা থাকে এবং সিক্রেট অংশটি কখনো প্রিন্ট করে না। শুধু লগইন করার সময়ই এটি প্রয়োজন হবে — সিআই (CI)-তে কীভাবে টোকেন কাজ করে তা জানতে দেখুন
অথেন্টিকেশন
।
৩. চারপাশ দেখুন
দুটি কমান্ড আপনাকে যেকোনো মুহূর্তে সবকিছুর অবস্থা জানাবে, যা পুরোপুরি অফলাইনে কাজ করে:
npx tgcloud status # লোকালি কী পরিবর্তিত হয়েছে বনাম ডিপ্লয় করা কপি
npx tgcloud diff # লাইন-বাই-লাইন পার্থক্য
init করার ঠিক পরেই সবকিছু নতুন এবং কিছুই ডিপ্লয় করা হয়নি। status দেখাবে যে স্টার্টার ফাইলগুলো আপলোড হওয়ার অপেক্ষায় আছে।
৪. ডিপ্লয়
আপনার মডিউলগুলো ক্লাউডে পাঠান:
npx tgcloud push
push প্রতিটি পরিবর্তিত মডিউলকে একটি অ্যাটমিক ব্যাচে আপলোড করে এবং ক্লাউডে বর্তমানে কী আছে তার লোকাল রেকর্ড আপডেট করে। আপনার বোট লাইভ: টেলিগ্রামে এটি ওপেন করুন এবং একটি মেসেজ পাঠান — স্টার্টার হ্যান্ডলারটি সেটি ইকো করবে।
ডিপ্লয় করা কখনোই আপনার ডাটাবেস স্পর্শ করে না। কোড পুশ করা এবং ডাটাবেস স্কিমা পরিবর্তন করা ইচ্ছাকৃতভাবে আলাদা রাখা হয়েছে, যাতে কোড ডিপ্লয় আপনার ডাটা মাইগ্রেশনের সাথে কোনো সমস্যা না করে। পরবর্তী ধাপটি সেই কাজের জন্য।
৫. একটি ডাটাবেস টেবিল যোগ করুন
আসুন বোটকে কিছু মনে রাখতে সাহায্য করি। 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 আপনাকে পরিবর্তনের ধাপগুলো দেখাবে এবং আপনার নিশ্চিতকরণের পর টেবিল তৈরি করবে। এই দুই-ধাপের মডেলটি — এবং ড্রপ করার মতো ঝুঁকিপূর্ণ পরিবর্তনের ক্ষেত্রে কী ঘটে — তা
মাইগ্রেশন
-এ কভার করা হয়েছে।
৬. ডাটা সংরক্ষণ এবং পড়ুন
এখন আপনার হ্যান্ডলার থেকে টেবিলটি ব্যবহার করুন। 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: `সংরক্ষিত হয়েছে। এই চ্যাট থেকে এখন পর্যন্ত ${count}টি মেসেজ জমা হয়েছে।`,
});
}
npx tgcloud push দিয়ে আপডেট করা হ্যান্ডলার ডিপ্লয় করুন, তারপর আপনার বোটকে কয়েকটি মেসেজ পাঠান এবং সংখ্যাটি বাড়তে দেখুন। ডাটাবেস ইনভোকশনের মাঝেও টিকে থাকে — এটাই আপনার বোটের স্মৃতি।
৭. ডিপ্লয় না করেই পরীক্ষা করুন
পরিবর্তন পরীক্ষা করার জন্য আপনাকে ডিপ্লয় করতে হবে না। npx tgcloud run আপনার লোকাল ফাইলগুলো ব্যবহার করে প্ল্যাটফর্মে হ্যান্ডলার চালায়, সেগুলো পাবলিশ না করেই:
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hello" }'
আর্গুমেন্টটি হলো আপনার হ্যান্ডলার যে পেলোড পায় — handlers/message এর জন্য, একটি
Message
— যা
JSON5
-এ লেখা (তাই কি-গুলোকে কোট না দিলেও চলে)। কমান্ডটি হ্যান্ডলারের console.* আউটপুট, রিটার্ন ভ্যালু এবং কত সময় লেগেছে তা প্রিন্ট করে। লজিক ইটারেট করার জন্য এটিই সবচেয়ে দ্রুত উপায় — কোনো ডিপ্লয় নেই, কোনো রিয়েল মেসেজের জন্য অপেক্ষা নেই।
৮. সিঙ্কে থাকুন
কাজ করার সময়, কয়েকটি কমান্ড আপনার লোকাল প্রজেক্ট এবং ক্লাউডকে এক সারিতে রাখে: npx tgcloud status কী পরিবর্তিত হয়েছে তা দেখায়, npx tgcloud push ডিপ্লয় করে, npx tgcloud pull আপনার লোকাল প্রজেক্টকে ক্লাউডের সাথে মেলানো হয়, npx tgcloud fetch আপনার ফাইল স্পর্শ না করেই কী ডিপ্লয় করা আছে তা পরীক্ষা করে, এবং npx tgcloud reset লোকাল পরিবর্তনগুলো বাতিল করে দেয়।
যদি দুইজন মানুষ (বা দুটি মেশিন) একই বোটে ডিপ্লয় করে, প্ল্যাটফর্ম দ্বন্দ্ব শনাক্ত করে এবং আপনাকে আগে
pullকরার সুযোগ দেয় — কেউ অন্য কারোর কাজ নীরবে ওভাররাইট করতে পারবে না। দেখুন Staying in sync ।
এআই (AI) দিয়ে তৈরি করা
এআই অ্যাসিস্ট্যান্ট দিয়ে তৈরি করতে পছন্দ করেন? আপনি এখনো বোট শিপ করতে পারেন। আমরা এআই এজেন্টকে প্রজেক্টে স্বাচ্ছন্দ্যবোধ করানোর জন্য প্রথম পদক্ষেপ নিয়েছি: প্রতিটি নতুন প্রজেক্টে একটি AGENTS.md এবং একটি docs/tgcloud-sdk.md রেফারেন্স থাকে যা এআই কোডিং টুলগুলো স্বয়ংক্রিয়ভাবে পড়ে।
একটি ছোট, স্বয়ংসম্পূর্ণ রানটাইমের সাথে — একটি এসডিকে, কোনো এনপিএম প্যাকেজ সামলানোর প্রয়োজন নেই — এটি অ্যাসিস্ট্যান্টকে সেই কনভেনশনগুলোতে একটি ভালো শুরু দেয় যা জেনেরিক কোডজেন মিস করে: .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 এবং আপনি সিএলআই যা যা ম্যানেজ করে, তার সব টাচস্ক্রিনে পাবেন:
- Handlers — আপডেট হ্যান্ডলার তৈরি, এডিট এবং টেস্ট-রান করুন; BotFather আপনার ডিপ্লয় করা হ্যান্ডলারের সাথে ওয়েবহুক সিঙ্কে রাখে।
- Endpoints — আপনার মিনি অ্যাপ যে ফাংশনগুলো কল করে।
- Library — আপনার শেয়ার্ড
lib/মডিউলগুলো। - Database — একই ড্রজল-লাইক (Drizzle-like) সিনট্যাক্সে
schema.jsএডিট করুন, পেন্ডিং পরিবর্তনগুলো রিভিউ করুন এবং প্রয়োগ করুন; Save মানেই ডিপ্লয়। - Mini App Files — আপনার ইউজাররা টেলিগ্রামে যে ফ্রন্ট-এন্ড ওপেন করে।
- CLI Access — কম্পিউটারের সামনে ফিরে এসে এখানে সিএলআই অ্যাক্সেস টোকেনটি নিন।
এটি একই ক্লাউড প্রজেক্ট, তাই আপনি ফোনে একটি হ্যান্ডলার শুরু করতে পারেন এবং পরে ল্যাপটপে npx tgcloud pull করতে পারেন — কিছুই একটি সিঙ্গেল ক্লায়েন্টের সাথে বাঁধা নয়। হ্যান্ডলার চালানোর সময় এর Console আউটপুট চ্যাটেই দেখা যায়, ঠিক
npx tgcloud run
-এর মতো।
প্রজেক্ট এবং মডিউল
একটি সার্ভারলেস প্রজেক্ট ভার্সন কন্ট্রোলের অধীনে একটি সাধারণ ফোল্ডার। প্ল্যাটফর্ম যা চালায় তা একটি ফোল্ডারে থাকে, tgcloud/; প্রজেক্টের বাকি অংশ আপনার — একটি মিনি-অ্যাপ ফ্রন্ট-এন্ড, কনফিগারেশন, ডক্স। বোটের জন্য কোনো বিল্ড স্টেপ নেই, রানটাইমে কোনো node_modules নেই, এবং কোনো সার্ভার এন্ট্রি পয়েন্ট নেই।
প্রজেক্টের গঠন
example_bot/
├─ tgcloud/ # ডিপ্লয় করা অংশ — অন্য কিছু নয়
│ ├─ handlers/ # আপডেট হ্যান্ডলার — ফ্ল্যাট, শুধুমাত্র এক লেভেল
│ │ ├─ message.js
│ │ └─ callback_query.js
│ ├─ endpoints/ # মিনি অ্যাপ যে ফাংশনগুলো কল করে — ফ্ল্যাট, শুধুমাত্র এক লেভেল
│ │ └─ getProfile.js
│ ├─ lib/ # শেয়ার্ড মডিউল; সাব-ডাইরেক্টরি অনুমোদিত
│ │ ├─ reply.js
│ │ └─ internal/util.js
│ └─ schema.js # ডাটাবেস স্কিমা — একটি ফাইল
└─ .tgcloud/ # সিএলআই স্টেট, গিট-ইগনোর করা
শুধুমাত্র tgcloud/ এর ভেতরের .js ফাইলগুলো — schema.js, lib/, handlers/, endpoints/ — ডিপ্লয় হয়। বাকি সবকিছু — মার্কডাউন, কনফিগ ফাইল, .tgcloud/ ফোল্ডার, tgcloud/ এর বাইরের যেকোনো কিছু — আপনার মেশিনে থেকে যায়। মডিউল নামগুলো tgcloud/ এর সাপেক্ষে। tgcloud/handlers/message.js ফাইলটি হলো handlers/message মডিউল, এবং এটিই সেই নাম যা npx tgcloud run গ্রহণ করে।
পুরানো প্রজেক্ট থেকে আপগ্রেড করছেন? সিএলআই ০.২.০ এর আগের প্রজেক্টগুলোতে
schema.js,lib/এবংhandlers/প্রজেক্টের মূলে থাকতো। সেগুলোকেtgcloud/-এ সরানোর জন্য একবারnpx tgcloud upgradeচালান।
schema.js — আপনার ডাটাবেস। এটি স্কিমা ডিএসএল ব্যবহার করে নেমড এক্সপোর্ট হিসেবে টেবিল ঘোষণা করে এবং 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/ — সিএলআই দ্বারা ম্যানেজ করা মেশিন-লোকাল স্টেট, যার মধ্যে আপনার সেভ করা ক্রেডেনশিয়াল থাকে। এটি গিট-ইগনোর করা, এবং আপনি কখনোই এটি হাতে পড়বেন না বা লিখবেন না — পরিবর্তে সিএলআই কমান্ড ব্যবহার করুন।
মডিউল সিস্টেম
রানটাইমে, একটি মডিউল ঠিক দুটি জিনিস দেখতে পায়: প্ল্যাটফর্ম এসডিকে (SDK) এবং আপনার প্রজেক্টের অন্য মডিউলগুলো। কোনো এনপিএম প্যাকেজ নেই, ফাইলসিস্টেম নেই, এবং এসডিকের fetch ছাড়া কোনো নেটওয়ার্ক নেই।
আপনার নিজস্ব মডিউলগুলো ইমপোর্ট করুন যেভাবে আপনি যেকোনো জাভাস্ক্রিপ্ট প্রজেক্টে করতেন — রিলেটিভ পাথ দিয়ে, .js এক্সটেনশনসহ — এবং এসডিকে তার নাম দিয়ে:
// tgcloud/handlers/message.js
import { users } from '../schema.js'; // স্কিমা মডিউল
import { addItem } from '../lib/cart.js'; // একটি লিভ মডিউল
import { format } from '../lib/internal/fmt.js'; // নেস্টেড লিভ মডিউল
import { db, api, fetch } from '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'; // কোনো এনপিএম প্যাকেজ নেই
প্রতিটি মডিউলের একটি নামও থাকে — tgcloud/ এর ভেতরে এর পাথ, এক্সটেনশন ছাড়া, যেমন schema, lib/cart বা handlers/message। সেই নামটিই npx tgcloud run এবং সিএলআই আউটপুটে ব্যবহৃত হয়, এবং মডিউলটি এটি দিয়েও ইমপোর্ট করা যায় (from 'lib/cart', কোনো এক্সটেনশন নেই)। আপনার কোডে রিলেটিভ ইমপোর্ট পছন্দ করুন। এগুলোই আপনার এডিটর বোঝে — জাম্প-টু-ডেফিনিশন, অটো-কমপ্লিট, রিনেম।
রানটাইমে যা পাওয়া যায় তা ঠিক দুটি জিনিস: sdk এবং এর সাব-মডিউল (sdk/db, sdk/api, sdk/fetch) — পুরো প্ল্যাটফর্ম সারফেস, দেখুন
এসডিকে
— এবং আপনার নিজের মডিউলগুলো tgcloud/ এ। এটাই সম্পূর্ণ লিস্ট। যদি আপনার কোড অন্য কিছু import করে, তবে তা রেজলভ হবে না। এই সীমাবদ্ধতাই মডিউলগুলোকে দ্রুত লোড হতে এবং রান করতে নিরাপদ রাখে।
হ্যান্ডলার
হ্যান্ডলার হলো handlers/ এ থাকা একটি মডিউল যার ডিফল্ট এক্সপোর্ট প্ল্যাটফর্ম কল করে যখন কোনো সংশ্লিষ্ট আপডেট আসে।
// handlers/message.js
import { api } from 'sdk';
export default async function (message) {
await api.sendMessage({
chat_id: message.chat.id,
text: `আপনি বলেছেন: ${message.text ?? '(টেক্সট নেই)'}`,
});
}
একটি হ্যান্ডলার তার আর্গুমেন্ট হিসেবে আপডেটের পেলোড পায় — প্ল্যাটফর্ম আপনার জন্য টেলিগ্রাম
আপডেট
আনর্যাপ করে দেয়। handlers/message.js
মেসেজ
পায় (যেমন update.message); handlers/callback_query.js
কলব্যাক কোয়েরি
পায়; ইত্যাদি। হ্যান্ডলারের দ্বিতীয় আর্গুমেন্ট হলো একটি প্রতি-ইনভোকশন কনটেক্সট অবজেক্ট, ctx। এটি ctx.update হিসেবে র (raw) আপডেট বহন করে — যখন আপনার পেলোডের বাইরে কিছু প্রয়োজন হয়, যেমন update_id, তখন এটি ব্যবহার করুন।
একটি হ্যান্ডলার async হতে পারে (সাধারণত হয়ই) এবং একটি ভ্যালু রিটার্ন করতে পারে। এটি এসডিকের মাধ্যমে বোট এপিআই, ডাটাবেস এবং আউটবাউন্ড এইচটিটিপি-তে পৌঁছায়।
হ্যান্ডলার পরীক্ষা করার জন্য আপনার রিয়েল আপডেটের প্রয়োজন নেই। 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('প্রোফাইল পাওয়া যায়নি', { userId: user.id });
return profile;
}
প্রথম আর্গুমেন্ট, input, হলো মিনি অ্যাপের পাঠানো জেএসওএন (JSON) অবজেক্ট। দ্বিতীয়, ctx, বহন করে ctx.initData — মিনি অ্যাপের
ইনিট ডাটা
(user, chat, start_param, auth_date, …), যা প্ল্যাটফর্ম দ্বারা ইতিমধ্যে ভেরিফাই করা হয়েছে। এটি ক্লায়েন্টের Telegram.WebApp.initDataUnsafe এর মতোই, কিন্তু এখানে আপনি একে বিশ্বাস করতে পারেন। আপনার কোড রান করার আগে ভ্যালিড ইনিট ডাটা ছাড়া কলগুলো 401 দিয়ে রিজেক্ট করা হয়, তাই ctx.initData.user সত্যিই সেই ব্যক্তি যিনি আপনার মিনি অ্যাপ ওপেন করেছেন। এন্ডপয়েন্ট শুধুমাত্র এইভাবেই কল করা যায় — আপনার বোটের মিনি অ্যাপ থেকে, কোনো টেলিগ্রাম ইউজার দ্বারা — তাই সুরক্ষা দেওয়ার জন্য কোনো পাবলিক এপিআই নেই এবং বিল্ড করার মতো কোনো লগইনও নেই।
এন্ডপয়েন্ট যা রিটার্ন করে তা জেএসওএন হিসেবে ফেরত পাঠানো হয়। একটি কল প্রত্যাখ্যান করতে, throw new EndpointError(description, parameters?) করুন। মিনি অ্যাপ ডেসক্রিপশন এবং parameters অবজেক্টটি পেয়ে যায়। যদি ফ্রন্ট-এন্ডে ত্রুটির ধরণের ওপর ভিত্তি করে ব্রাঞ্চ করতে হয়, তবে parameters এ একটি কোড রাখুন — { code: 'OUT_OF_STOCK' }। অন্য যেকোনো এক্সেপশন সাধারণ ইন্টারনাল এরর হিসেবে রিপোর্ট করা হয়, এবং আসল মেসেজ শুধু আপনার লগে যায়, তাই ইন্টারনাল বিষয় কখনো ক্লায়েন্টে ফাঁস হয় না।
মিনি অ্যাপ থেকে একটি এন্ডপয়েন্ট কল করা
মিনি অ্যাপ অফিশিয়াল
মিনি অ্যাপস এসডিকে
থেকে Telegram.WebApp.Serverless.call দিয়ে নাম ধরে এন্ডপয়েন্ট কল করে — যে স্ক্রিপ্টটি আপনার মিনি অ্যাপে ইতিমধ্যে আছে। এটি আপনার জন্য ইনিট ডাটা পাঠায় এবং একটি এরর বা এন্ডপয়েন্টের ফলাফল নিয়ে কলব্যাক করে:
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' যখন ইনিট ডাটা মিসিং বা ইনভ্যালিড থাকে।
এন্ডপয়েন্টের নামগুলো জাভাস্ক্রিপ্ট আইডেন্টিফায়ারের মতো — অক্ষর, ডিজিট এবং _, যা ডিজিট দিয়ে শুরু হতে পারবে না (getProfile, place_order)।
npx tgcloud add endpoints/<name>
দিয়ে একটি স্কাফোল্ড করুন, এবং
run
দিয়ে মিনি অ্যাপ ছাড়াই চেষ্টা করুন, নিজেই ইনিট ডাটা পাস করে:
npx tgcloud run endpoints/getProfile '{}' --ctx '{ initData: { user: { id: 1, first_name: "Ann" } } }'
যা ডিপ্লয় হয়
যখন আপনি npx tgcloud push করেন, সিএলআই schema.js, lib/, handlers/ এবং endpoints/ এর নিচের প্রতিটি .js ফাইল সংগ্রহ করে এবং সেই সেটটি আপনার প্রজেক্টের মডিউল স্পেস হিসেবে পাঠায় — মিনি অ্যাপের বিল্ড ফোল্ডারের সাথে, যদি tgcloud.jsonc এ কোনোটির নাম থাকে (নিচে দেখুন)। ক্লাউডে যা আছে কিন্তু আপনার প্রজেক্টে নেই তা সরিয়ে ফেলা হয়, তাই ডিপ্লয় করা স্টেট সবসময় আপনার ফোল্ডারের প্রতিফলন ঘটায় — ডিলিট করা ফাইলগুলোও। tgcloud/ এর বাইরে কোড হিসেবে কিছুই ডিপ্লয় হয় না — প্রজেক্ট রুটের বাকি অংশ আপনার। tgcloud/ এর ভেতর, একটি .js ফাইল যা এই লোকেশনগুলোর একটিতে নেই (tgcloud/util.js, tgcloud/helpers/x.js) তাকে ফ্ল্যাগ করা হয়, যাতে তা নীরবে ডিপ্লয় না হওয়া অবস্থায় না থাকে। মার্কডাউন, ডটফাইল এবং .tgcloud/ কখনোই ডিপ্লয় হয় না।
মিনি অ্যাপ ফ্রন্ট-এন্ড
প্রজেক্ট রুট আপনার, এবং এটি আপনার মিনি অ্যাপের ফ্রন্ট-এন্ডের জন্য প্রাকৃতিক জায়গা — যা রিয়্যাক্ট (React), ভিউ (Vue), স্ভেল্ট (Svelte) বা সাধারণ এইচটিএমএল দিয়ে তৈরি, ভাইট (Vite) বা অন্য কোনো বান্ডলার দ্বারা। সার্ভারলেস আপনার বোটের পাশেই এর বিল্ড আউটপুট হোস্ট করে। একটি push কোড এবং স্ট্যাটিক ফাইলগুলো একসাথে ডিপ্লয় করে, এবং অ্যাপটি https://app<app_id>.tgcloud.ai/ তে সার্ভ হয় (push সঠিক অ্যাড্রেসটি প্রিন্ট করে —
@BotFather
-এ আপনার মিনি অ্যাপের ইউআরএল হিসেবে এটি ব্যবহার করুন)।
অপব্যবহারের রিপোর্ট। 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) যাতে কমেন্ট এবং ট্রেইলিং কমা অনুমোদিত। যদি অন্য কোনো টুলের এটি সাধারণ জেএসওএন হিসেবে পড়ার প্রয়োজন হয়, তবে এর নাম tgcloud.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/ এর নিচে আপনার নিজস্ব ফাইল রাখবেন না।
ডাটাবেস
প্রতিটি বোটের নিজস্ব ডাটাবেস থাকে — একটি এসকিউলাইট-ভিত্তিক স্টোর যা ইনভোকশনের মাঝেও টিকে থাকে এবং db এর মাধ্যমে প্রতিটি মডিউলের জন্য উপলব্ধ। আপনি schema.js এ একটি ছোট, টাইপড ডিএসএল (DSL) দিয়ে আপনার টেবিল বর্ণনা করবেন; আপনি একটি ফ্লুয়েন্ট কোয়েরি বিল্ডার দিয়ে সেগুলো পড়বেন এবং লিখবেন; এবং আপনি রিভিউ করা মাইগ্রেশনের মাধ্যমে সেগুলোকে উন্নত করবেন।
Drizzle ORM চেনেন? তাহলে আপনি ইতিমধ্যে জানেন এখানে কীভাবে ডাটাবেসের সাথে কথা বলতে হয়। স্কিমা ডিএসএল এবং কোয়েরি বিল্ডার ড্রজল অনুসরণ করে — কলাম বিল্ডার,
select().from().where(), অপারেটরগুলো,onConflictDoUpdate,.returning(), এবংsqlট্যাগ সবই আপনি যা আশা করেন সেভাবেই কাজ করে, তাই ডাটা পড়া ও লেখা সেই পরিচিত এপিআই যা আপনি ইতিমধ্যে ব্যবহার করেন। আপনি শুধু এটিsdk/dbথেকে ইমপোর্ট করবেন, এবং প্ল্যাটফর্মের কিছু নির্দিষ্ট বিষয় (সবচেয়ে গুরুত্বপূর্ণভাবে ফরেন কি নেই ) যেখানে আসে সেখানে উল্লেখ করা হয়েছে।
টেবিল ঘোষণা করা
টেবিলগুলো 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হলো এসকিউএল টেবিলের নাম;columnsহলো জেএস প্রপার্টি → কলাম ডেফিনিশন এর ম্যাপ;extrasহলো একটি ঐচ্ছিক কলব্যাক(t) => ({ … })যেখানেtকলামগুলোকে উন্মুক্ত করে (t.createdসেই কলামের একটি রেফারেন্স) — এখানে ইনডেক্স এবং টেবিল-লেভেল কনস্ট্রেইন্ট ঘোষণা করুন।
কলাম টাইপ
| ফ্যাক্টরি | এসকিউলাইট টাইপ | নোটস |
|---|---|---|
text() |
TEXT | |
integer() |
INTEGER | |
real() |
REAL | alias float() |
numeric() |
NUMERIC | |
blob() |
BLOB | reads/writes Uint8Array |
boolean() |
INTEGER | 0/1 হিসেবে স্টোর হয়, true/false হিসেবে পড়া হয় |
json() |
TEXT | auto JSON.stringify / JSON.parse |
কলামের নাম আর্গুমেন্টটি ঐচ্ছিক — বাদ দিন এবং জেএস কি (key) ব্যবহৃত হবে।
modeঅপশনটি কন্ট্রোল করে কীভাবে এসকিউলাইট এবং জাভাস্ক্রিপ্টের মধ্যে মান কনভার্ট হয়।
mode |
স্টোর হয় | জেএস ভ্যালু |
|---|---|---|
boolean |
INTEGER 0/1 | boolean |
json |
TEXT (JSON) | any object/array |
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 পড়ে এবং লেখে — রানটাইমে কোনো নোড Buffer নেই (এবং একটি Buffer একটি Uint8Array সাবক্লাস, তাই এটি পোর্টেবল বেস টাইপ)। উপরের mode শুধুমাত্র গভর্ন করে কীভাবে একটি ভ্যালু এনকোড হয়, কলামের স্টোরেজ টাইপের ওপর স্বাধীনভাবে: তাই blob('col', { mode: 'json' }) হলো json() এর বিএলওবি (BLOB) প্রতিরূপ — একই জেএসওএন এনকোডিং, যা টেক্সট এর পরিবর্তে একটি বিএলওবি কলামে থাকে।
TLDR:
blob()ব্যবহার করেUint8Array।modeএনকোডিং নিয়ন্ত্রণ করে, স্টোরেজ নয়, তাইblob(..., { mode: 'json' })বিএলওবি হিসেবে জেএসওএন স্টোর করে, যখনjson()তা টেক্সট হিসেবে স্টোর করে।
কলাম মডিফায়ার
কলাম মডিফায়ারগুলো একটি কলামের সাথে চেইন হয়।
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('লগইন দ্বারা প্রতিস্থাপিত') // রিমুভ করার জন্য কলামটিকে মার্ক করে
একটি
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('কারণ')।
ফরেন কি নেই
রানটাইমটি PRAGMA foreign_keys অফ রেখে চলে। একটি ঘোষিত ফরেন কি নীরবে নিষ্ক্রিয় থাকবে — কোনো ক্যাসকেড নেই, কোনো অনাথ (orphan) সুরক্ষা নেই — যা কোনোটি না থাকার চেয়েও খারাপ, তাই ডিএসএল এটি অসম্ভব করে তোলে। মূলত, .references() এবং টেবিল-লেভেল foreignKey() ঘোষণা করলে থ্রো করে, এবং এমন স্কিমা ডিপ্লয় হবে না।
আপনার সম্পর্কগুলো সাধারণ কলাম (userId: integer('user_id')) দিয়ে মডেল করা উচিত এবং আপনার অ্যাপ্লিকেশন কোডে সততা (integrity) নিশ্চিত করা উচিত: বাচ্চাদের আগে বাবা-মাকে ইনসার্ট করুন, বাবা-মায়ের আগে বাচ্চাদের ডিলিট করুন, এররগুলো সঠিকভাবে হ্যান্ডেল করুন এবং আপনার যখন প্রয়োজন হয় তখন একটি LEFT JOIN … WHERE parent.id IS NULL দিয়ে অনাথদের সরিয়ে ফেলুন।
ফরেন কি-এর অনুপস্থিতি একটি ইচ্ছাকৃত সীমাবদ্ধতা, কোনো ভুল নয়। আপনার বোট প্ল্যান করার সময় শুরু থেকেই এটি মাথায় রাখুন।
কোয়েরি করা
db হলো একটি ফ্লুয়েন্ট কোয়েরি বিল্ডার। প্রতিটি কোয়েরি অসিঙ্ক্রোনাস — টার্মিনাল মেথডগুলো (.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(); // প্রথম রো বা নাল
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();
লক্ষ্য করুন যে একটি ব্যাচ ইনসার্ট হলো একটি স্টেটমেন্ট, তাই এটি এসকিউলাইটের ভেরিয়েবল লিমিট (
rows × columns) দ্বারা সীমাবদ্ধ; যদি এটি ছাড়িয়ে যান, ইনসার্ট এরর দেবে — নিজেই চঙ্ক করুন।
অপারেটরগুলো 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({ … }) প্রজেকশনের জন্য এসকিউএল ফ্র্যাগমেন্ট।
র (Raw) এসকিউএল — যখন বিল্ডার যথেষ্ট হয় না, র এসকিউএল ব্যবহার করুন। মোড মেথড দ্বারা নির্বাচন করা হয়: রাইটের জন্য 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 ক্লজ বহন করে যদি এসকিউএলের একটি থাকে। যখন একটি কোয়েরি একই নামের দুটি কলাম প্রজেক্ট করে (যেমন SELECT u.id, o.id এর মতো জয়েন) তখন db.values ব্যবহার করুন: db.all রো-গুলোকে কলামের নাম অনুযায়ী কি (key) করে, তাই দ্বিতীয় id প্রথমটিকে ওভাররাইট করবে।
sql`…` ট্যাগ ${value} কে একটি বাউন্ড প্যারামিটারে এবং ${table.column} কে একটি আইডেন্টিফায়ারে পরিণত করে, এবং নেস্টেড sql ফ্র্যাগমেন্টগুলোকে স্প্লাইস করে। কোনো প্যারামিটার ছাড়া লিটারালের জন্য sql.raw('…') ব্যবহার করুন।
র কোয়েরিগুলো কোনো টেবিলের সাথে আবদ্ধ নয়, তাই তাদের রো-গুলো মোড কনভার্সন ছাড়া ফিরে আসে — একটি বুলিয়ান হয়
0/1, একটি জেএসওএন কলাম হয় একটি স্ট্রিং, একটি টাইমস্ট্যাম্প হয় একটি নম্বর। শুধুমাত্র টেবিল-বাউন্ড বিল্ডার ভ্যালু কনভার্ট করে।
মাইগ্রেশন
আপনার বোট বড় হওয়ার সাথে সাথে আপনার ডাটাবেস পরিবর্তিত হয়। প্ল্যাটফর্ম কোড ডিপ্লয় করা থেকে ডাটা পরিবর্তন করাকে আলাদা রেখে এবং প্রতিটি স্কিমা পরিবর্তনকে কতটা ঝুঁকিপূর্ণ তার ভিত্তিতে ক্লাসিফাই করে সেটিকে নিরাপদ রাখে।
ডিপ্লয় করা কখনোই ডাটাবেস স্পর্শ করে না। যখন আপনি npx tgcloud push দিয়ে একটি পরিবর্তিত schema.js পাঠান, প্ল্যাটফর্ম নতুন স্কিমা রেকর্ড করে এবং ডাটাবেস কীভাবে পরিবর্তিত হতো তা জানায় — কিন্তু কিছু প্রয়োগ করে না:
npx tgcloud push # schema.js ডিপ্লয় করুন; পেন্ডিং ডিবি পরিবর্তন রিপোর্ট করে
npx tgcloud migrate # রিভিউ করুন এবং প্রয়োগ করুন
npx tgcloud migrate আপনার স্কিমা এবং লাইভ ডাটাবেসের মধ্যে পার্থক্য গণনা করে এবং আপনাকে ধাপগুলো দেখায়। কিছু প্রয়োগ করার আগে আপনাকে সবসময় জিজ্ঞাসা করা হবে। এর মানে হলো একটি সাধারণ কোড ডিপ্লয় দুর্ঘটনাক্রমে কোনো ডাটা মাইগ্রেশন ট্রিগার করতে পারে না।
প্রতিটি পেন্ডিং পরিবর্তনের একটি স্ট্যাটাস থাকে যা নির্ধারণ করে migrate একে কীভাবে ট্রিট করবে:
| স্ট্যাটাস | মানে | migrate এ |
|---|---|---|
| safe | অ্যাডিটিভ এবং নন-ব্লকিং — একটি নতুন টেবিল, কলাম, বা ইনডেক্স | নিশ্চিতকরণের পর, এক ধাপে প্রয়োগ করা হয় |
| warning | সম্ভাব্য ধ্বংসাত্মক বা ধীর — কিছু ড্রপ করা, অথবা একটি বিশাল টেবিলে ইনডেক্স | একটি একটি করে দেখানো হয়, প্রতিটি আলাদাভাবে নিশ্চিত করা হয় |
| manual | স্বয়ংক্রিয়ভাবে করা সম্ভব নয় — যেমন একটি কলামের টাইপ পরিবর্তন করা | নির্দেশনাসহ দেখানো হয়; আপনি হাতে করুন |
| undocumented | ডাটাবেসে আছে কিন্তু আপনার স্কিমাতে নেই | সচেতনতার জন্য দেখানো হয়; প্রয়োগ করা হয় না |
নিরাপদ পরিবর্তনগুলো দ্রুত এবং নীতিগতভাবে রিভার্সিবল, তাই সেগুলো একসাথে চলে যায়। প্রতিটি ওয়ার্নিং হলো একটি ইচ্ছাকৃত, ব্যক্তিগত নিশ্চিতকরণ — ধ্বংসাত্মক পরিবর্তনের জন্য কোনো “সবগুলো প্রয়োগ করো” নেই।
ম্যানুয়াল পরিবর্তনগুলো একটি কারণ এবং প্রস্তাবিত অ্যাকশনসহ আসে। migrate একটি সামারি দিয়ে শেষ হয়: কতগুলো পরিবর্তন প্রয়োগ করা হয়েছে, স্কিপ করা হয়েছে, ম্যানুয়াল ফিক্সের অপেক্ষায় আছে, অথবা আপনার স্কিমাতে নেই।
ফ্ল্যাগগুলোর (
--dry-run,--safe,--yes,--local) জন্য দেখুনmigrate।
জিনিস রিমুভ করা
schema.js থেকে একটি টেবিল বা কলাম ডিলিট করলে তা ড্রপ হয় না — তা করলে দুর্ঘটনাক্রমে ডিলিট করা বিপর্যয়কর হতো। কিছু রিমুভ করতে, ডেপ্রিকেটেড (deprecated) হিসেবে মার্ক করুন:
// একটি কলাম ড্রপ করুন
text('email').deprecated('লগইন দ্বারা প্রতিস্থাপিত')
// একটি পুরো টেবিল ড্রপ করুন
export const oldSessions = table('old_sessions', { /* … */ }).deprecated('অব্যবহৃত');
পরবর্তী migrate এ, ডেপ্রিকেটেড অবজেক্টটি একটি ওয়ার্নিং-স্ট্যাটাস ড্রপ হিসেবে দেখা যায় যা আপনি আলাদাভাবে নিশ্চিত করবেন। একবার চলে গেলে, ঘোষণাটি রিমুভ করুন।
কলামের টাইপ পরিবর্তন করা
টাইপ পরিবর্তনগুলো ম্যানুয়াল — এসকিউলাইট সবসময় সেগুলো জায়গা মতো করতে পারে না, এবং বিদ্যমান ভ্যালুগুলোকে জোর করা একটি বিচারের বিষয়। migrate পরিবর্তন এবং এর কারণ দেখাবে; নিজেই র এসকিউএল (db.run(...)) দিয়ে এটি করুন, সাধারণত একটি নতুন কলাম বা টেবিল তৈরি করে, ডাটা কপি করে এবং সোয়াপ (swap) করে।
এসডিকে (SDK)
রানটাইমে একটি মডিউলে একটি লাইব্রেরি থাকে: sdk। এটি একটি বোট ব্যাকএন্ডের প্রয়োজনীয় তিনটি জিনিসকে বান্ডল করে — একটি ডাটাবেস, টেলিগ্রাম বোট এপিআই, এবং আউটবাউন্ড এইচটিটিপি — ইনস্টল করার জন্য কিছুই নেই এবং কনফিগার করার মতো কোনো ক্রেডেনশিয়াল নেই। ডাটাবেস (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 |
ডাটাবেস — কোয়েরি বিল্ডার এবং স্কিমা ডিএসএল → ডাটাবেস |
api |
টেলিগ্রাম বোট এপিআই — api.sendMessage(...) →
নিচে
|
fetch |
আউটবাউন্ড এইচটিটিপি → নিচে |
InputFile |
আপলোড করার জন্য একটি ফাইল (বাইট + নাম) — api এবং fetch উভয় সাথেই কাজ করে →
ফাইল
|
EndpointError |
একটি মেসেজ দিয়ে মিনি অ্যাপের কল প্রত্যাখ্যান করুন যা এটি দেখাতে পারে → এন্ডপয়েন্ট |
আপনার নিজস্ব প্রজেক্ট মডিউলগুলো রিলেটিভ পাথ এবং .js এক্সটেনশন দিয়ে ইমপোর্ট করুন (from '../schema.js', from '../lib/cart.js')। দেখুন
মডিউল সিস্টেম
।
বোট এপিআই
api আপনাকে পুরো
টেলিগ্রাম বোট এপিআই
দেয়। যেকোনো মেথডকে api.<method>(params) হিসেবে কল করুন। প্রতিটি বর্তমান — এবং ভবিষ্যতের — বোট এপিআই মেথড কোনো এসডিকে আপডেট ছাড়াই কাজ করে।
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: 'সম্পন্ন' });
রেসপন্স এনভেলপটি আনর্যাপ করা হয়। বোট এপিআই সাধারণত ফলাফলগুলোকে { ok: true, result: … } এ র্যাপ করে। api সরাসরি result রিটার্ন করে — getMe() ইউজার অবজেক্টে রেজলভ হয়, কোনো র্যাপারে নয়। প্যারামিটারগুলো বোট এপিআই এর নিজস্ব snake_case নাম ব্যবহার করে (chat_id, message_id, reply_markup, …)।
ব্যর্থতাগুলো BotApiError থ্রো করে। যখন বোট এপিআই { ok: false } রিটার্ন করে, কলটি একটি BotApiError থ্রো করে পরিবর্তে একটি ফলসি (falsy) ভ্যালু রিটার্নের, যাতে আপনি দুর্ঘটনাক্রমে এটি উপেক্ষা করতে না পারেন। এররটি বহন করে .code (বোট এপিআই error_code), .description (মানুষের পড়ার যোগ্য মেসেজ), .method (কোন মেথড ফেইল করেছে), এবং .parameters (অতিরিক্ত ডাটা যেমন ৪২৯ এ 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_id নয় — নতুন ফাইল আপলোড করুন এবং বিদ্যমান ফাইল ডাউনলোড করুন।
আপলোড — InputFile। র বাইটগুলোকে একটি InputFile (একটি সাধারণ হোল্ডার: বাইট, একটি ফাইলনাম, এবং একটি ঐচ্ছিক মাইম type) এ র্যাপ করুন এবং যেখানে বোট এপিআই মেথড একটি ফাইল আশা করে সেখানে পাস করুন:
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' }) },
]});
বাইটগুলো টেলিগ্রামে একটি রিয়েল মাল্টিপার্ট আপলোড হিসেবে স্ট্রিম করা হয় — ফাইলটি পুরোটা প্ল্যাটফর্মে একটি সিঙ্গেল কলে বাফার করা হয় না — কিন্তু টেলিগ্রামের বোট এপিআই লিমিট এখনো প্রযোজ্য: একটি বোট বর্তমানে ফাইল প্রতি ৫০ এমবি পর্যন্ত আপলোড করতে পারে (এবং ফটোর জন্য ১০ এমবি)।
ডাউনলোড — file_id দ্বারা। দুটি হেল্পার ফাইলের বাইট ফিরিয়ে আনে। প্ল্যাটফর্ম আপনার জন্য file_id রেজলভ করে (এটি getFile চালায় এবং পর্দার আড়ালে কন্টেন্ট ফেচ করে), তাই আপনি বোট টোকেন বা কোনো ডাউনলোড ইউআরএল স্পর্শ করেন না:
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ডাউনলোডকে ২০ এমবি তে ক্যাপ করে, তাই দুটি হেল্পারের জন্যই এটি সীমা।
এইচটিটিপি (HTTP)
fetch হলো বাইরের জগতকে কল করার জন্য একটি fetch-মতো ক্লায়েন্ট — থার্ড-পার্টি এপিআই, ওয়েবহুক, এইচটিটিপি ওভার যেকোনো কিছু।
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 (২০০-২৯৯ এর জন্য ট্রু), 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', 'আমার বোট থেকে');
const res = await fetch('https://api.example.com/upload', { method: 'POST', body: form });
এটি অন্যথায় আপনার পরিচিত স্ট্যান্ডার্ড fetch এর মতোই আচরণ করে, দুটি সীমাবদ্ধতা সহ:
- রেসপন্স টেক্সট হিসেবে পড়া হয় —
await res.json()/await res.text(), অথবাres.bodyদিয়ে চঙ্ক-বাই-চঙ্ক স্ট্রিম করা হয়। - মোট রেসপন্স ৩০ এমবি তে ক্যাপ করা। সেই ক্যাপ পুরো রেসপন্সকে কভার করে —
res.bodyদিয়ে স্ট্রিমিং আপনাকে একটি বড় বডি ইনক্রিমেন্টালি প্রসেস করতে দেয়, কিন্তু এটি লিমিট বাড়ায় না।
লগিং (Logging)
স্ট্যান্ডার্ড গ্লোবাল console উপলব্ধ — ইমপোর্ট করার কিছু নেই, এটি যেকোনো জাভাস্ক্রিপ্টের মতোই আছে। এর আউটপুট npx tgcloud run দ্বারা ক্যাপচার এবং দেখানো হয়, যা এটিকে ডেভেলপমেন্টের সময় আপনার প্রাথমিক ডিবাগিং টুল করে তোলে।
console.log('প্রসেস করা হচ্ছে', { chatId: id }); // লগ / ডিবাগ — সাধারণ
console.info('শুরু হয়েছে'); // ইনফো — নীল
console.warn('রেট লিমিটেড'); // ওয়ার্ন — হলুদ
console.error(err); // এরর — লাল, স্ট্যাক ট্রেসসহ
প্রতিটি লাইন এটি যে [file:line] থেকে এসেছে তা দিয়ে ট্যাগ করা হয়। console.error এবং console.trace একটি পূর্ণ স্ট্যাক অ্যাপেন্ড করে, যখন console.warn তা করে না। যখন আপনি একটি মডিউল npx tgcloud run করবেন, এই লাইনগুলো লেভেল অনুযায়ী রঙিন প্রিফিক্স, রান শুরু হওয়ার পর থেকে সময় এবং উৎসসহ প্রিন্ট করা হয় — দেখুন
run
।
কমান্ড-লাইন ইন্টারফেস
tgcloud হলো আপনার প্রজেক্ট ফোল্ডার এবং ক্লাউডের মধ্যকার সেতু। এটি প্রজেক্ট স্কাফোল্ড করে, কী পরিবর্তিত হয়েছে তা দেখায়, ডিপ্লয় করে, মডিউল চালায় এবং ডাটাবেস মাইগ্রেশন প্রয়োগ করে। এটির জন্য
Node.js
১৮ বা নতুন প্রয়োজন। এটি পাওয়ার দুটি উপায়:
# প্রস্তাবিত — সিএলআই ইনস্টল করা একটি প্রজেক্ট তৈরি করুন:
npm create @tgcloud/bot example_bot
# অথবা সিএলআই গ্লোবালি ইনস্টল করুন এবং একটি খালি ফোল্ডার ইনিট করুন:
npm install -g @tgcloud/cli
tgcloud init
এনপিএম প্যাকেজটি হলো @tgcloud/cli, এবং যে কমান্ডটি এটি ইনস্টল করে তা হলো tgcloud। প্রতিটি কমান্ড আপনার প্রজেক্টের যেকোনো সাবফোল্ডার থেকে কাজ করে।
| কমান্ড | উদ্দেশ্য |
|---|---|
init
|
বর্তমান ফোল্ডারে একটি নতুন প্রজেক্ট স্কাফোল্ড করা |
add
|
একটি নতুন মডিউল স্কাফোল্ড করা (হ্যান্ডলার, এন্ডপয়েন্ট বা লিভ মডিউল) |
login
|
প্রজেক্টকে একটি বোটের সাথে লিঙ্ক করা (টোকেন সেভ করে) |
status
|
লোকালি বনাম ক্লাউড কী পরিবর্তিত হয়েছে তা দেখান |
diff
|
লাইন-বাই-লাইন পরিবর্তনগুলো দেখান |
push
|
পরিবর্তিত মডিউল এবং মিনি অ্যাপের বিল্ড ক্লাউডে ডিপ্লয় করা |
migrate
|
ডাটাবেসে স্কিমা পরিবর্তন প্রয়োগ করা |
run
|
ডিপ্লয় না করেই প্ল্যাটফর্মে একটি মডিউল এক্সিকিউট করা |
fetch
|
লোকাল ফাইল পরিবর্তন না করেই কী ডিপ্লয় করা আছে তা পরীক্ষা করা |
pull
|
লোকাল ফাইলগুলোকে ক্লাউডের সাথে মেলানো |
reset
|
লোকাল পরিবর্তন বাতিল করা; ক্লাউড স্টেট থেকে পুনরুদ্ধার করা |
webhook
|
প্ল্যাটফর্ম-ম্যানেজড ওয়েবহুক পরিদর্শন এবং পুনরায় সিঙ্ক করা |
completion
|
একটি শেল কমপ্লিশন স্ক্রিপ্ট প্রিন্ট করা (bash/zsh/fish) |
upgrade
|
একটি প্রাক-০.২.০ প্রজেক্ট লেআউটকে tgcloud/-এ সরান |
অথেন্টিকেশন
একটি প্রজেক্ট তার টোকেন দ্বারা একটি বোটের সাথে আবদ্ধ, যা app<id>:<secret> ফরম্যাটে হয়। app<id> অংশটি পাবলিক এবং প্রিন্ট করা হতে পারে; সিক্রেটটি কখনো লগ বা এররে উপস্থিত হয় না।
টোকেনটি এই ক্রমে রেজলভ হয়:
১. TGCLOUD_TOKEN এনভায়রনমেন্ট ভেরিয়েবল — সিআই-এর জন্য; কখনোই ডিস্কে লেখা হয় না।
২. .tgcloud/credentials —
npx tgcloud login
দ্বারা লেখা।
৩. কোনটিই নয় → একটি এরর যা আপনাকে npx tgcloud login এ নির্দেশ করে।
সিএলআই কখনোই কমান্ডের মাঝে টোকেন চাইবে না। লগইন সবসময় একটি স্পষ্ট login ধাপ। যদি একটি সেভ করা টোকেন ইনভ্যালিড হয়ে যায়, সিএলআই এটি রিমুভ করে এবং আপনাকে পুনরায় login করতে বলে।
init
npx tgcloud init
বর্তমান ডাইরেক্টরিতে একটি নতুন প্রজেক্ট স্কাফোল্ড করে: tgcloud/ ফোল্ডার যার মধ্যে schema.js, lib/, handlers/ এবং একটি স্টার্টার হ্যান্ডলার আছে, প্লাস 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
এরর: একটি নাম উল্লেখ করুন, যেমন "handlers/callback_query"। উপলব্ধ handlers/ টাইপগুলো: callback_query, inline_query, chat_member, …
<Tab>কমপ্লিশন একই সেট অফার করে — দেখুনcompletion।
জেনারেট করা ফাইলে একটি লাইভ export default থাকে, তাই ডিপ্লয় করার সাথে সাথেই হ্যান্ডলারটি সক্রিয় হয়ে যায় — আনকমেন্ট করার কিছু নেই।
push
দিয়ে নতুন মডিউল ডিপ্লয় করুন।
login
npx tgcloud login
আপনার সিএলআই অ্যাক্সেস টোকেনের জন্য প্রম্পট করে —
@BotFather
→ আপনার বোট → Serverless → CLI Access → Access token থেকে, আপনার
বোটের এপিআই টোকেন
থেকে আলাদা — প্ল্যাটফর্মের সাথে ভ্যালিডেট করে এবং .tgcloud/credentials এ সেভ করে।
loginহলো একমাত্র কমান্ড যা টোকেন চায়, এবং এর জন্য একটি ইন্টারঅ্যাক্টিভ টার্মিনাল প্রয়োজন। সিআই-তে, পরিবর্তেTGCLOUD_TOKENসেট করুন।
status
npx tgcloud status
প্রতি ফাইল, আপনার ওয়ার্কিং ডাইরেক্টরি এবং ডিপ্লয় করা কপির মধ্যে কী পরিবর্তিত হয়েছে তা দেখায়: মডিফাইড, নতুন, ডিলিট করা, অপরিবর্তিত। এটি অফলাইনে কাজ করে, আপনার শেষ push, pull বা fetch এর ক্লাউড স্টেটের সাথে তুলনা করে। এটি tgcloud/ এর ভেতরে স্ট্রে (stray) .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 — কনকারেন্সি চেক স্কিপ করতে এবং ক্লাউডে যা আছে তা ওভাররাইট করতে। এটি শুধু তখন ব্যবহার করুন যখন আপনি নিশ্চিত (দেখুন
Staying in sync
)।
যদি 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এর জন্য একটি মেসেজ), অথবা একটি এন্ডপয়েন্টেরinput, এবং এটি অবশ্যই একটি অবজেক্ট হতে হবে — অ্যারে বা বেয়ার স্ট্রিং নয়।--ctx <json5>— মডিউলের কনটেক্সট অবজেক্ট (এর দ্বিতীয় আর্গুমেন্ট), এটিও JSON5। আপনার কোডctxথেকে যা পড়ে তা সরবরাহ করতে এটি ব্যবহার করুন — হ্যান্ডলারের জন্য র আপডেট,--ctx '{ update: { update_id: 1 } }', অথবা এন্ডপয়েন্টের জন্য ইনিট ডাটা,--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 এর বর্তমান স্টেট দেখায়: ইউআরএল, allowed_updates লিস্ট, কতগুলো আপডেট পেন্ডিং আছে, শেষ ডেলিভারি এরর (যদি থাকে), এবং এটি আপনার ডিপ্লয় করা হ্যান্ডলারের সাথে সিঙ্কে আছে কি না।
“সিঙ্কে আছে” মানে ওয়েবহুক প্ল্যাটফর্মের দিকে পয়েন্ট করে এবং এর allowed_updates আপনার ডিপ্লয় করা হ্যান্ডলারের সাথে মেলে — তাই টেলিগ্রাম ঠিক সেই আপডেট টাইপগুলো ডেলিভার করে যা আপনি হ্যান্ডেল করেন, অন্য কিছু নয়। একটি নতুন হ্যান্ডলার ডিপ্লয় করা (বা একটি রিমুভ করা) ওয়েবহুককে রিফ্রেশ না হওয়া পর্যন্ত সিঙ্কের বাইরে রেখে যেতে পারে; npx tgcloud status এটিও ফ্ল্যাগ করে।
npx tgcloud webhook sync এটি ঠিক করে — এটি ওয়েবহুককে প্ল্যাটফর্মের দিকে পুনরায় পয়েন্ট করে এবং আপনার ডিপ্লয় করা হ্যান্ডলার থেকে allowed_updates পুনর্নির্মাণ করে। সিঙ্কের আগে টেলিগ্রাম যে আপডেটগুলো কিউ (queue) করেছিল তা বাতিল করতে --drop-pending যোগ করুন (অন্যথায় ওয়েবহুক সুস্থ হওয়ার পর সেগুলো ডেলিভার করা হয়)।
completion
নোট: ট্যাব-কমপ্লিশন শুধুমাত্র তখনই কাজ করে যখন
PATH-এ একটি বেয়ারtgcloudথাকে — তাই এটি গ্লোবালি ইনস্টল করুন (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 ০.২.০ এর আগে তৈরি প্রজেক্টের জন্য এককালীন মাইগ্রেশন, যা schema.js, lib/ এবং handlers/ প্রজেক্ট রুটে রাখতো। এটি সেগুলোকে tgcloud/-এ সরিয়ে ফেলে। বোটের সাথে আপনার লিঙ্ক রাখা হয়, এবং নতুন করে ডিপ্লয় করার প্রয়োজন নেই। --dry-run কিছু স্পর্শ না করেই পরিকল্পিত মুভগুলো প্রিন্ট করে। আপনি আপগ্রেড না করা পর্যন্ত অন্য কমান্ডগুলো পুরানো লেআউটে চলে না।
সিঙ্কে থাকা
প্রতিটি প্রজেক্টের ক্লাউডে একটি মোনোটোনিক্যালি ইনক্রিজিং রিভিশন থাকে, যা প্রতিটি ডিপ্লয়ে বাড়ে। সিএলআই সেই রিভিশনটি মনে রাখে যার সাথে এটি শেষ সিঙ্ক হয়েছিল এবং প্রতি push এ তা পাঠায়। যদি ক্লাউড এগিয়ে যায় — কারণ অন্য কোনো মেশিন বা সতীর্থ ডিপ্লয় করেছে — তবে পুশটি তাদের কাজ নীরবে ওভাররাইট করার পরিবর্তে রিজেক্ট করা হয়, এবং সিএলআই সামনে এগিয়ে যাওয়ার তিনটি উপায় অফার করে:
npx tgcloud fetch # ক্লাউডে কী পরিবর্তিত হয়েছে দেখুন, তারপর পুনরায় চেক করুন
npx tgcloud pull # সর্বশেষটিকে আপনার ওয়ার্কিং ফাইলে নিয়ে আসুন
npx tgcloud push --force # ক্লাউড স্টেট ওভাররাইট করুন (বিপজ্জনক)
এই অপ্টিমিস্টিক-কনকারেন্সি চেকই কারণ কেন আপনি লকস্টেপ ডিপ্লয় প্রসেস ছাড়া পুরো টিমে একটি বোট শেয়ার করতে পারেন। ব্যর্থতার সময় কমান্ডগুলো নন-জিরো এক্সিট হয় — একটি রিজেক্ট করা ডিপ্লয়, ফেইল করা মাইগ্রেশন, অথেন্টিকেশন এরর, একটি মডিউল যা run এর সময় থ্রো করেছে — তাই সেগুলো স্ক্রিপ্ট এবং সিআই পাইপলাইনে পরিষ্কারভাবে কম্পোজ হয়।