TypeScript چیست؟ آموزش کامل TypeScript با ساخت SDK برای API هوش مصنوعی
در این آموزش TypeScript را از مفاهیم پایه تا Generic، Narrowing، Utility Type و تنظیمات Strict یاد میگیرید. سپس یک SDK تایپشده، قابل تست و کاربردی برای API هوش مصنوعی درواره میسازیم.
TypeScript یکی از مهمترین فناوریهای توسعه نرمافزار مدرن است. بسیاری از پروژههای Frontend، Backend، Full Stack، ابزارهای خط فرمان، Extensionها و SDKهای جدید با TypeScript توسعه داده میشوند.
TypeScript امکانات JavaScript را حفظ میکند و یک Type System ایستا به آن اضافه میکند. این سیستم میتواند بسیاری از خطاهای مربوط به نوع داده، Propertyهای اشتباه، پارامترهای ناقص و خروجیهای پیشبینینشده را پیش از اجرای برنامه شناسایی کند.
در این آموزش، TypeScript را فقط در حد تعریف چند Type ساده یاد نمیگیریم. ابتدا مفاهیم مهمی مانند Type Inference، Interface، Union، Literal Type، Narrowing، Generic، Utility Type، unknown، never و تنظیمات Strict را بررسی میکنیم. سپس یک SDK واقعی برای API هوش مصنوعی درواره میسازیم که ویژگیهای زیر را دارد:
- Typeهای مشخص برای Request و Response
- مدیریت خطای ساختیافته
- Timeout
- پشتیبانی از AbortSignal
- اعتبارسنجی Runtime پاسخ API
- قابلیت جایگزینی
fetchبرای تست - خروجی ESM
- فایلهای Declaration
- Unit Test بدون ارسال درخواست واقعی
- نگهداری امنتر API Key در متغیر محیطی
TypeScript چیست؟
TypeScript یک زبان برنامهنویسی مبتنی بر JavaScript است که Static Type Checking را به آن اضافه میکند.
کد TypeScript معمولاً قبل از اجرا به JavaScript تبدیل میشود. مرورگر یا Node.js در نهایت کد JavaScript تولیدشده را اجرا میکند.
مثال JavaScript:
function add(a, b) {
return a + b;
}
مثال TypeScript:
function add(a: number, b: number): number {
return a + b;
}
در نسخه TypeScript مشخص کردهایم:
aبایدnumberباشد.bبایدnumberباشد.- خروجی Function نیز
numberاست.
اگر بنویسیم:
add("10", 20);
TypeScript پیش از اجرای برنامه خطا میدهد؛ زیرا مقدار "10" از نوع string است، نه number.
طبق مستندات پایه TypeScript، Type System ایستا تلاش میکند قبل از Runtime درباره رفتار احتمالی کد و ناسازگاری Typeها هشدار دهد.
تفاوت TypeScript و JavaScript
JavaScript یک زبان Dynamically Typed است. Type متغیرها در Runtime مشخص میشود و یک متغیر میتواند در طول اجرای برنامه مقادیر متفاوتی بگیرد:
let value = 10;
value = "hello";
value = true;
TypeScript میتواند Type متغیر را محدود کند:
let value: number = 10;
value = "hello";
خطای TypeScript:
Type 'string' is not assignable to type 'number'.
تفاوتهای اصلی:
Type Checking
JavaScript بیشتر خطاهای Type را هنگام اجرا آشکار میکند. TypeScript میتواند تعداد زیادی از این خطاها را هنگام توسعه یا Build شناسایی کند.
تکمیل خودکار بهتر
وقتی Type داده مشخص باشد، ویرایشگر میتواند Propertyها، Methodها و پارامترهای معتبر را دقیقتر پیشنهاد دهد.
Refactoring مطمئنتر
تغییر نام Function یا Property در پروژه بزرگ با Type Information قابلردیابیتر است.
مستندسازی داخل کد
Typeها مشخص میکنند هر Function چه ورودی و خروجی دارد.
نیاز به Build
کد TypeScript معمولاً باید به JavaScript تبدیل شود، مگر اینکه Runtime مورد استفاده پشتیبانی خاصی برای اجرای مستقیم TypeScript ارائه دهد.
آیا TypeScript جایگزین JavaScript است؟
TypeScript روی JavaScript ساخته شده است. بیشتر سینتکس JavaScript در TypeScript معتبر است و TypeScript نیز در نهایت معمولاً به JavaScript تبدیل میشود.
برای یادگیری حرفهای TypeScript همچنان باید JavaScript را بشناسید:
- Variable
- Function
- Object
- Array
- Promise
- Module
- Class
- Closure
- Event Loop
asyncوawait- Error Handling
TypeScript ضعف درک JavaScript را بهطور کامل جبران نمیکند؛ بلکه لایهای برای تحلیل Type و بهبود Tooling فراهم میکند.
TypeScript چه مشکلاتی را حل میکند؟
در پروژههای کوچک JavaScript ممکن است Typeهای داده را بهسادگی در ذهن نگه دارید. با بزرگترشدن پروژه، تعداد Functionها، APIها و Objectها افزایش پیدا میکند.
یک خطای رایج:
function printUser(user) {
console.log(user.fullName.toUpperCase());
}
اگر API بهجای fullName مقدار name برگرداند، برنامه در Runtime خطا میدهد:
Cannot read properties of undefined
در TypeScript:
interface User {
fullName: string;
}
function printUser(user: User): void {
console.log(user.fullName.toUpperCase());
}
اگر Object ورودی fullName نداشته باشد، TypeScript هنگام توسعه هشدار میدهد.
آیا TypeScript تمام خطاها را حذف میکند؟
خیر. TypeScript میتواند خطاهای زیادی را پیش از اجرا پیدا کند، اما این موارد را تضمین نمیکند:
- صحت منطق تجاری
- درستی پاسخ API خارجی
- وجود واقعی فایل یا Network
- معتبر بودن داده پایگاه داده
- موفقبودن Request
- نبودن Race Condition
- عملکرد صحیح UI
- معتبر بودن ورودی کاربر
- نبودن خطای Runtime در Library
- صحت Type Assertionهای توسعهدهنده
Typeهای TypeScript هنگام اجرای JavaScript معمولاً وجود ندارند. بنابراین دادهای که از API، فایل، کاربر یا پایگاه داده دریافت میشود باید در Runtime نیز اعتبارسنجی شود.
این موضوع در ساخت SDK اهمیت زیادی دارد.
نصب TypeScript
برای شروع به Node.js و npm نیاز دارید.
بررسی نسخه:
node --version
npm --version
ساخت پروژه:
mkdir darvareh-typescript-sdk
cd darvareh-typescript-sdk
npm init -y
نصب TypeScript:
npm install --save-dev typescript
بررسی نسخه Compiler:
npx tsc --version
ساخت فایل تنظیمات:
npx tsc --init
فرمان tsc مخفف TypeScript Compiler است.
اولین برنامه TypeScript
فایل hello.ts:
function greet(name: string): string {
return `سلام ${name}`;
}
const message = greet("درواره");
console.log(message);
Type Check:
npx tsc hello.ts --noEmit
تبدیل به JavaScript:
npx tsc hello.ts
فایل hello.js ساخته میشود.
اجرا:
node hello.js
Type Annotation چیست؟
Type Annotation نوع یک Variable، Parameter یا خروجی را بهصورت صریح مشخص میکند:
const modelName: string = "example-model";
const maxTokens: number = 1000;
const streaming: boolean = false;
Function:
function calculateCost(
inputTokens: number,
outputTokens: number,
): number {
return inputTokens + outputTokens;
}
همیشه لازم نیست Type را صریح بنویسید. TypeScript در بسیاری از موارد Type را Infer میکند.
Type Inference چیست؟
TypeScript میتواند Type را از مقدار اولیه تشخیص دهد:
const modelName = "example-model";
Type متغیر بهصورت خودکار string تشخیص داده میشود.
const maxTokens = 1000;
Type آن number است.
این کد معمولاً بیش از حد توضیحی است:
const name: string = "Darvareh";
این نسخه نیز Type Safe است:
const name = "Darvareh";
Type Annotation بیشتر زمانی مفید است که:
- Type از مقدار اولیه روشن نیست.
- API عمومی طراحی میکنید.
- Function خروجی پیچیده دارد.
- Contract میان بخشها باید صریح باشد.
- میخواهید Type گستردهتر یا محدودتری تعریف کنید.
Typeهای پایه TypeScript
string
const prompt: string = "یک متن خلاصه بنویس";
number
const temperature: number = 0.3;
TypeScript برای عدد صحیح و اعشاری Type جداگانهای ندارد و هر دو number هستند.
boolean
const stream: boolean = true;
bigint
const largeValue: bigint = 9007199254740993n;
symbol
const requestId: symbol = Symbol("request-id");
null و undefined
const emptyValue: null = null;
const missingValue: undefined = undefined;
با فعالبودن strictNullChecks، null و undefined باید بهصورت مشخص مدیریت شوند.
Array در TypeScript
دو روش رایج:
const models: string[] = [
"model-a",
"model-b",
];
یا:
const models: Array<string> = [
"model-a",
"model-b",
];
Array از Object:
interface Model {
id: string;
title: string;
}
const models: Model[] = [
{
id: "model-a",
title: "Model A",
},
];
Tuple چیست؟
Tuple یک Array با طول و Typeهای مشخص در موقعیتهای مشخص است:
const usage: [number, number] = [
1200,
450,
];
نامگذاری اعضای Tuple خوانایی را بهتر میکند:
type TokenUsage = [
inputTokens: number,
outputTokens: number,
];
const usage: TokenUsage = [1200, 450];
برای دادههای پیچیده یا قابل توسعه، Object معمولاً از Tuple خواناتر است:
interface TokenUsage {
inputTokens: number;
outputTokens: number;
}
Object Type
تعریف مستقیم:
const model: {
id: string;
contextWindow: number;
supportsVision: boolean;
} = {
id: "model-a",
contextWindow: 128000,
supportsVision: true,
};
برای استفاده مجدد بهتر است Type یا Interface بسازید.
Interface در TypeScript
interface AIModel {
id: string;
contextWindow: number;
supportsVision: boolean;
}
استفاده:
const model: AIModel = {
id: "model-a",
contextWindow: 128000,
supportsVision: true,
};
Property اختیاری:
interface AIModel {
id: string;
contextWindow: number;
description?: string;
}
Property فقطخواندنی:
interface AIModel {
readonly id: string;
contextWindow: number;
}
بعد از ساخت Object نمیتوان id را از طریق TypeScript تغییر داد:
model.id = "new-model";
اما readonly بهتنهایی Immutableبودن عمیق Object در Runtime را تضمین نمیکند.
Type Alias
type ModelId = string;
Object Type:
type AIModel = {
id: string;
contextWindow: number;
};
Union:
type ResponseStyle =
| "short"
| "detailed"
| "bullet";
Function Type:
type Logger = (
message: string,
metadata?: Record<string, unknown>,
) => void;
تفاوت type و interface
هر دو میتوانند شکل Object را تعریف کنند.
Interface:
interface ChatRequest {
model: string;
messages: ChatMessage[];
}
Type:
type ChatRequest = {
model: string;
messages: ChatMessage[];
};
تفاوتهای مهم:
interfaceقابلیت Declaration Merging دارد.typeبرای Union، Tuple، Conditional Type و ترکیبهای پیشرفته انعطاف بیشتری دارد.- هر دو قابلیت Extension یا Composition دارند.
- برای بیشتر Objectهای معمول، هر دو قابل استفادهاند.
Extension در Interface:
interface BaseResponse {
id: string;
}
interface ChatResponse extends BaseResponse {
choices: ChatChoice[];
}
Intersection در Type:
type ChatResponse =
BaseResponse & {
choices: ChatChoice[];
};
بهتر است تیم یک Convention مشخص داشته باشد و Consistency را حفظ کند.
Union Type
Union یعنی مقدار میتواند یکی از چند Type باشد:
type ModelId = string | number;
Literal Union:
type Role =
| "system"
| "user"
| "assistant"
| "tool";
استفاده:
interface ChatMessage {
role: Role;
content: string;
}
مقدار نامعتبر هنگام توسعه رد میشود:
const message: ChatMessage = {
role: "customer",
content: "Hello",
};
Literal Type
وقتی با const مقدار ثابتی تعریف میکنید، TypeScript میتواند Type محدودتری Infer کند:
const role = "user";
Type این مقدار ممکن است Literal "user" باشد، نه string عمومی.
Objectها بهطور پیشفرض ممکن است Type گستردهتری بگیرند:
const message = {
role: "user",
content: "Hello",
};
برای حفظ Literal Type:
const message = {
role: "user",
content: "Hello",
} as const;
as const Propertyها را نیز Readonly میکند و Literal Typeها را حفظ میکند.
Intersection Type
Intersection چند Type را ترکیب میکند:
type Identifiable = {
id: string;
};
type Timestamped = {
createdAt: string;
};
type StoredMessage =
Identifiable &
Timestamped & {
role: "user" | "assistant";
content: string;
};
Object نهایی باید تمام Propertyهای Typeهای ترکیبشده را داشته باشد.
Optional Property
interface ChatRequest {
model: string;
temperature?: number;
}
علامت ? یعنی Property ممکن است وجود نداشته باشد.
این دو حالت از نظر رفتار Runtime همیشه یکسان نیستند:
{}
و:
{
temperature: undefined,
}
تنظیم exactOptionalPropertyTypes تفاوت میان «نبودن Property» و «وجود Property با مقدار undefined» را دقیقتر بررسی میکند. جزئیات این رفتار در مرجع TSConfig توضیح داده شده است.
Function در TypeScript
function createPrompt(
topic: string,
length: number,
): string {
return `درباره ${topic} در ${length} کلمه بنویس.`;
}
Arrow Function:
const createPrompt = (
topic: string,
length: number,
): string => {
return `درباره ${topic} در ${length} کلمه بنویس.`;
};
پارامتر اختیاری:
function greet(
name: string,
title?: string,
): string {
return title
? `سلام ${title} ${name}`
: `سلام ${name}`;
}
مقدار پیشفرض:
function createRequest(
prompt: string,
temperature = 0.3,
): object {
return {
prompt,
temperature,
};
}
Type خروجی void:
function logMessage(message: string): void {
console.log(message);
}
unknown در برابر any
any
any بررسی Type را عملاً غیرفعال میکند:
let value: any;
value.toUpperCase();
value.notExistingMethod();
value.foo.bar();
TypeScript روی این عملیاتها هشدار مؤثری نمیدهد.
unknown
unknown یعنی Type مقدار هنوز مشخص نیست:
let value: unknown;
قبل از استفاده باید آن را Narrow کنید:
if (typeof value === "string") {
console.log(value.toUpperCase());
}
برای پاسخ response.json()، داده فایل یا ورودی خارجی، unknown معمولاً نقطه شروع ایمنتری از any است.
Type Narrowing چیست؟
Narrowing یعنی TypeScript با بررسی مسیر اجرای برنامه، Type گسترده را به Type مشخصتری تبدیل کند.
function normalizeId(
id: string | number,
): string {
if (typeof id === "number") {
return id.toString();
}
return id.trim();
}
داخل شرط، Type مقدار number است. بعد از شرط، TypeScript میداند که مقدار باقیمانده string است.
طبق مستندات Narrowing در TypeScript، TypeScript از Type Guard و Control Flow Analysis برای محدودکردن Type استفاده میکند.
روشهای رایج Narrowing:
typeofinstanceofinArray.isArray- مقایسه با Literal
- Truthiness Check
- Type Predicate
- Discriminated Union
Type Guard سفارشی
function isStringArray(
value: unknown,
): value is string[] {
return (
Array.isArray(value) &&
value.every(
(item) => typeof item === "string",
)
);
}
استفاده:
const value: unknown = [
"model-a",
"model-b",
];
if (isStringArray(value)) {
value.map((item) => item.toUpperCase());
}
عبارت value is string[] یک Type Predicate است.
Discriminated Union
برای مدلکردن چند وضعیت مرتبط:
type RequestState =
| {
status: "idle";
}
| {
status: "loading";
}
| {
status: "success";
data: string;
}
| {
status: "error";
message: string;
};
استفاده:
function renderState(
state: RequestState,
): string {
switch (state.status) {
case "idle":
return "آماده";
case "loading":
return "در حال دریافت";
case "success":
return state.data;
case "error":
return state.message;
}
}
Property مشترک status نقش Discriminant را دارد و TypeScript بر اساس آن Type هر Branch را تشخیص میدهد.
never و بررسی Exhaustive
never وضعیتی را نشان میدهد که نباید مقدار معتبری داشته باشد.
function assertNever(value: never): never {
throw new Error(
`Unexpected value: ${String(value)}`,
);
}
استفاده در Switch:
function renderState(
state: RequestState,
): string {
switch (state.status) {
case "idle":
return "آماده";
case "loading":
return "در حال دریافت";
case "success":
return state.data;
case "error":
return state.message;
default:
return assertNever(state);
}
}
اگر بعداً وضعیت جدیدی به Union اضافه شود اما در Switch مدیریت نشود، TypeScript میتواند خطا را آشکار کند.
Generic چیست؟
Generic امکان ساخت Component، Function یا Type قابل استفاده برای دادههای مختلف را فراهم میکند، بدون آنکه ارتباط Type ورودی و خروجی از بین برود.
Function ساده:
function identity<T>(value: T): T {
return value;
}
استفاده:
const text = identity("hello");
const count = identity(10);
TypeScript معمولاً T را Infer میکند.
Generic برای پاسخ API:
interface APIResponse<T> {
data: T;
requestId: string;
}
پاسخ مدل:
interface Model {
id: string;
name: string;
}
type ModelsResponse = APIResponse<Model[]>;
طبق مستندات Generics در TypeScript، Generic Typeها ارتباط میان Typeهای ورودی، خروجی و اعضای ساختار را حفظ میکنند.
Generic Constraint
گاهی Generic باید Property مشخصی داشته باشد:
function getLength<T extends { length: number }>(
value: T,
): number {
return value.length;
}
این Function با string و Array کار میکند:
getLength("hello");
getLength([1, 2, 3]);
اما Object بدون length پذیرفته نمیشود.
keyof
keyof از کلیدهای یک Object Type یک Union میسازد:
interface Model {
id: string;
contextWindow: number;
active: boolean;
}
type ModelKey = keyof Model;
نتیجه:
"id" | "contextWindow" | "active"
Generic همراه keyof:
function getProperty<
T,
K extends keyof T,
>(
object: T,
key: K,
): T[K] {
return object[key];
}
استفاده:
const model = {
id: "model-a",
contextWindow: 128000,
};
const id = getProperty(model, "id");
کلید نامعتبر رد میشود:
getProperty(model, "price");
Utility Typeهای TypeScript
TypeScript چند Utility Type داخلی برای تبدیل Typeها ارائه میکند. فهرست آنها در مستندات Utility Types قرار دارد.
Partial
تمام Propertyها را اختیاری میکند:
interface ClientOptions {
baseURL: string;
timeoutMs: number;
}
type ClientOptionsUpdate =
Partial<ClientOptions>;
Required
تمام Propertyها را اجباری میکند:
type CompleteOptions =
Required<ClientOptions>;
Readonly
Propertyها را Readonly میکند:
type ReadonlyOptions =
Readonly<ClientOptions>;
Pick
بخشی از Propertyها را انتخاب میکند:
interface Model {
id: string;
name: string;
contextWindow: number;
description: string;
}
type ModelSummary = Pick<
Model,
"id" | "name"
>;
Omit
بعضی Propertyها را حذف میکند:
type PublicModel = Omit<
Model,
"description"
>;
Record
یک Object با Key و Value مشخص میسازد:
type HeadersMap =
Record<string, string>;
Literal Key:
type RoleLabel = Record<
"system" | "user" | "assistant",
string
>;
Awaited
Type داخل Promise را استخراج میکند:
type Result =
Awaited<Promise<string>>;
نتیجه string است.
Type Assertion
Type Assertion به TypeScript میگوید توسعهدهنده درباره Type مقدار اطلاعات بیشتری دارد:
const input =
document.querySelector(
"#message",
) as HTMLTextAreaElement;
Type Assertion داده را در Runtime تبدیل یا اعتبارسنجی نمیکند.
این کد خطرناک است:
const response =
value as ChatCompletionResponse;
اگر value ساختار درستی نداشته باشد، TypeScript در Runtime از شما محافظت نمیکند.
برای داده خارجی ابتدا Validation انجام دهید و سپس Assertion محدود و مستند استفاده کنید.
Non-null Assertion
const element =
document.querySelector("#app")!;
علامت ! میگوید مقدار null نیست. اگر Element وجود نداشته باشد، برنامه ممکن است در Runtime خطا بدهد.
روش صریحتر:
const element =
document.querySelector("#app");
if (!element) {
throw new Error(
"Element #app was not found",
);
}
Class در TypeScript
class ModelRegistry {
private readonly models =
new Map<string, string>();
add(id: string, name: string): void {
this.models.set(id, name);
}
get(id: string): string | undefined {
return this.models.get(id);
}
}
Access Modifierهای رایج:
publicprivateprotectedreadonly
Private در TypeScript میتواند محدودیت Compile-time ایجاد کند. برای Private Field واقعی JavaScript میتوان از # استفاده کرد:
class Counter {
#value = 0;
increment(): void {
this.#value += 1;
}
}
Promise و async/await
async function loadModels(): Promise<string[]> {
const response = await fetch("/models");
if (!response.ok) {
throw new Error(
`HTTP ${response.status}`,
);
}
const data: unknown = await response.json();
if (!Array.isArray(data)) {
throw new Error("Invalid response");
}
return data.filter(
(item): item is string =>
typeof item === "string",
);
}
Type خروجی Functionهای async یک Promise است.
تنظیم حرفهای tsconfig
برای پروژه SDK از تنظیمات Strict استفاده میکنیم.
فایل tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": [
"ES2022",
"DOM"
],
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"useUnknownInCatchVariables": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"noEmit": true,
"resolveJsonModule": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true
},
"include": [
"src/**/*.ts",
"examples/**/*.ts",
"tests/**/*.ts"
]
}
تنظیم Build جدا:
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": false,
"rootDir": "src",
"outDir": "dist",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noEmitOnError": true
},
"include": [
"src/**/*.ts"
],
"exclude": [
"examples",
"tests",
"dist"
]
}
این فایل را با نام زیر ذخیره کنید:
tsconfig.build.json
توضیح تنظیمات مهم
strict
مجموعهای از بررسیهای سختگیرانه Type را فعال میکند.
noUncheckedIndexedAccess
دسترسی به Array یا Object با Index را بهصورت احتمالی undefined در نظر میگیرد:
const items = ["a", "b"];
const first = items[0];
Type مقدار first میتواند string | undefined باشد.
exactOptionalPropertyTypes
Property اختیاری را دقیقتر مدیریت میکند.
useUnknownInCatchVariables
مقدار catch را بهجای any بهصورت unknown در نظر میگیرد.
declaration
فایلهای .d.ts برای مصرفکنندگان SDK تولید میکند.
noEmitOnError
در صورت وجود Type Error، خروجی Build تولید نمیشود.
پروژه عملی: ساخت SDK درواره با TypeScript
ساختار پروژه:
darvareh-typescript-sdk/
├── src/
│ ├── client.ts
│ ├── errors.ts
│ ├── index.ts
│ ├── parsers.ts
│ └── types.ts
├── examples/
│ └── basic.ts
├── tests/
│ └── client.test.ts
├── package.json
├── tsconfig.json
├── tsconfig.build.json
├── .env.example
└── .gitignore
نصب وابستگیها
npm install dotenv
وابستگیهای توسعه:
npm install --save-dev \
typescript \
tsx \
@types/node
تنظیم package.json
{
"name": "darvareh-ai-sdk-example",
"version": "1.0.0",
"private": true,
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"check": "tsc -p tsconfig.json",
"build": "tsc -p tsconfig.build.json",
"dev": "tsx examples/basic.ts",
"test": "tsx --test tests/*.test.ts"
},
"dependencies": {
"dotenv": "^17.0.0"
},
"devDependencies": {
"@types/node": "^24.0.0",
"tsx": "^4.0.0",
"typescript": "^5.0.0"
}
}
شماره نسخهها در طول زمان تغییر میکنند. پس از نصب و آزمایش، فایل Lock تولیدشده را نگه دارید تا نصب پروژه قابل تکرارتر باشد.
تعریف Typeهای SDK
فایل src/types.ts:
export type ChatRole =
| "system"
| "user"
| "assistant"
| "tool";
export interface ChatMessage {
role: ChatRole;
content: string;
}
export interface ChatCompletionRequest {
model: string;
messages: ChatMessage[];
temperature?: number;
max_tokens?: number;
stream?: false;
}
export interface ChatChoice {
index: number;
message: {
role: "assistant";
content: string | null;
};
finish_reason: string | null;
}
export interface TokenUsage {
prompt_tokens?: number;
completion_tokens?: number;
total_tokens?: number;
}
export interface ChatCompletionResponse {
id: string;
object?: string;
created?: number;
model: string;
choices: ChatChoice[];
usage?: TokenUsage;
}
export interface APIErrorBody {
error?: {
message?: string;
type?: string;
code?: string | number;
};
detail?: string;
}
export interface DarvarehClientOptions {
apiKey: string;
baseURL?: string;
timeoutMs?: number;
fetchImpl?: typeof fetch;
}
export interface RequestOptions {
signal?: AbortSignal;
}
نکته مهم این است که نام بعضی فیلدهای API مانند max_tokens و finish_reason با Convention رایج JSON نوشته شدهاند. در SDK میتوانید یک Mapping به نامهای Camel Case نیز طراحی کنید، اما در این نسخه Typeها را به Contract HTTP نزدیک نگه میداریم.
ساخت Errorهای اختصاصی
فایل src/errors.ts:
export class DarvarehSDKError extends Error {
constructor(message: string) {
super(message);
this.name = "DarvarehSDKError";
}
}
export class DarvarehAPIError
extends DarvarehSDKError {
readonly status: number;
readonly code?: string | number;
readonly responseBody: unknown;
constructor(options: {
message: string;
status: number;
code?: string | number;
responseBody: unknown;
}) {
super(options.message);
this.name = "DarvarehAPIError";
this.status = options.status;
this.responseBody = options.responseBody;
if (options.code !== undefined) {
this.code = options.code;
}
}
}
export class DarvarehTimeoutError
extends DarvarehSDKError {
constructor(message = "Request timed out") {
super(message);
this.name = "DarvarehTimeoutError";
}
}
export class DarvarehResponseError
extends DarvarehSDKError {
readonly responseBody: unknown;
constructor(
message: string,
responseBody: unknown,
) {
super(message);
this.name = "DarvarehResponseError";
this.responseBody = responseBody;
}
}
با Errorهای اختصاصی، مصرفکننده SDK میتواند خطای HTTP، Timeout و پاسخ نامعتبر را از یکدیگر تشخیص دهد.
ساخت Parser پاسخ API
فایل src/parsers.ts:
import {
DarvarehResponseError,
} from "./errors.js";
import type {
APIErrorBody,
ChatCompletionResponse,
} from "./types.js";
export function isRecord(
value: unknown,
): value is Record<string, unknown> {
return (
typeof value === "object" &&
value !== null &&
!Array.isArray(value)
);
}
export function parseAPIErrorBody(
value: unknown,
): APIErrorBody {
if (!isRecord(value)) {
return {};
}
const result: APIErrorBody = {};
if (
typeof value.detail === "string"
) {
result.detail = value.detail;
}
if (isRecord(value.error)) {
const error: NonNullable<
APIErrorBody["error"]
> = {};
if (
typeof value.error.message === "string"
) {
error.message = value.error.message;
}
if (
typeof value.error.type === "string"
) {
error.type = value.error.type;
}
if (
typeof value.error.code === "string" ||
typeof value.error.code === "number"
) {
error.code = value.error.code;
}
result.error = error;
}
return result;
}
export function parseChatCompletionResponse(
value: unknown,
): ChatCompletionResponse {
if (!isRecord(value)) {
throw new DarvarehResponseError(
"Response must be an object",
value,
);
}
if (typeof value.id !== "string") {
throw new DarvarehResponseError(
"Response id is missing or invalid",
value,
);
}
if (typeof value.model !== "string") {
throw new DarvarehResponseError(
"Response model is missing or invalid",
value,
);
}
if (!Array.isArray(value.choices)) {
throw new DarvarehResponseError(
"Response choices must be an array",
value,
);
}
for (const choice of value.choices) {
if (!isRecord(choice)) {
throw new DarvarehResponseError(
"A response choice is invalid",
value,
);
}
if (typeof choice.index !== "number") {
throw new DarvarehResponseError(
"Choice index is invalid",
value,
);
}
if (!isRecord(choice.message)) {
throw new DarvarehResponseError(
"Choice message is invalid",
value,
);
}
const content = choice.message.content;
if (
content !== null &&
typeof content !== "string"
) {
throw new DarvarehResponseError(
"Message content is invalid",
value,
);
}
}
return value as unknown as ChatCompletionResponse;
}
در انتهای Parser از Type Assertion استفاده شده است، اما تنها پس از بررسی فیلدهای اصلی پاسخ.
برای SDK عمومی و حساستر میتوانید تمام فیلدها را با Schema Validator یا Parser جامعتر بررسی کنید.
ساخت Client
فایل src/client.ts:
import {
DarvarehAPIError,
DarvarehSDKError,
DarvarehTimeoutError,
} from "./errors.js";
import {
parseAPIErrorBody,
parseChatCompletionResponse,
} from "./parsers.js";
import type {
ChatCompletionRequest,
ChatCompletionResponse,
DarvarehClientOptions,
RequestOptions,
} from "./types.js";
const DEFAULT_BASE_URL =
"https://api.darvareh.ir/v1";
const DEFAULT_TIMEOUT_MS = 60_000;
function normalizeBaseURL(
baseURL: string,
): string {
return baseURL.replace(/\/+$/, "");
}
async function readJSONSafely(
response: Response,
): Promise<unknown> {
const contentType =
response.headers.get("content-type") ?? "";
if (
!contentType
.toLowerCase()
.includes("application/json")
) {
const text = await response.text();
return {
raw: text,
};
}
try {
return await response.json();
} catch {
return null;
}
}
export class DarvarehClient {
readonly #apiKey: string;
readonly #baseURL: string;
readonly #timeoutMs: number;
readonly #fetch: typeof fetch;
constructor(
options: DarvarehClientOptions,
) {
const apiKey = options.apiKey.trim();
if (!apiKey) {
throw new DarvarehSDKError(
"apiKey must not be empty",
);
}
const timeoutMs =
options.timeoutMs ??
DEFAULT_TIMEOUT_MS;
if (
!Number.isFinite(timeoutMs) ||
timeoutMs <= 0
) {
throw new DarvarehSDKError(
"timeoutMs must be a positive number",
);
}
this.#apiKey = apiKey;
this.#baseURL = normalizeBaseURL(
options.baseURL ??
DEFAULT_BASE_URL,
);
this.#timeoutMs = timeoutMs;
this.#fetch = options.fetchImpl ?? fetch;
}
async createChatCompletion(
request: ChatCompletionRequest,
options: RequestOptions = {},
): Promise<ChatCompletionResponse> {
this.#validateChatRequest(request);
const responseBody =
await this.#request(
"/chat/completions",
{
method: "POST",
headers: {
"Authorization":
`Bearer ${this.#apiKey}`,
"Content-Type":
"application/json",
"Accept":
"application/json",
},
body: JSON.stringify(request),
},
options,
);
return parseChatCompletionResponse(
responseBody,
);
}
async #request(
path: string,
init: RequestInit,
options: RequestOptions,
): Promise<unknown> {
const controller =
new AbortController();
const onExternalAbort = (): void => {
controller.abort(
options.signal?.reason,
);
};
if (options.signal?.aborted) {
onExternalAbort();
} else {
options.signal?.addEventListener(
"abort",
onExternalAbort,
{
once: true,
},
);
}
const timeout = setTimeout(
() => {
controller.abort(
new Error("SDK_REQUEST_TIMEOUT"),
);
},
this.#timeoutMs,
);
try {
const response = await this.#fetch(
`${this.#baseURL}${path}`,
{
...init,
signal: controller.signal,
},
);
const body =
await readJSONSafely(response);
if (!response.ok) {
const parsed =
parseAPIErrorBody(body);
const message =
parsed.error?.message ??
parsed.detail ??
`API request failed with status ${response.status}`;
const errorOptions: {
message: string;
status: number;
code?: string | number;
responseBody: unknown;
} = {
message,
status: response.status,
responseBody: body,
};
if (
parsed.error?.code !== undefined
) {
errorOptions.code =
parsed.error.code;
}
throw new DarvarehAPIError(
errorOptions,
);
}
return body;
} catch (error: unknown) {
if (
error instanceof DarvarehAPIError
) {
throw error;
}
if (
controller.signal.aborted &&
!options.signal?.aborted
) {
throw new DarvarehTimeoutError();
}
if (
error instanceof Error &&
error.name === "AbortError"
) {
throw error;
}
if (error instanceof Error) {
throw new DarvarehSDKError(
`Network request failed: ${error.message}`,
);
}
throw new DarvarehSDKError(
"Network request failed",
);
} finally {
clearTimeout(timeout);
options.signal?.removeEventListener(
"abort",
onExternalAbort,
);
}
}
#validateChatRequest(
request: ChatCompletionRequest,
): void {
if (!request.model.trim()) {
throw new DarvarehSDKError(
"model must not be empty",
);
}
if (request.messages.length === 0) {
throw new DarvarehSDKError(
"messages must not be empty",
);
}
for (const message of request.messages) {
if (!message.content.trim()) {
throw new DarvarehSDKError(
"message content must not be empty",
);
}
}
if (
request.temperature !== undefined &&
(
request.temperature < 0 ||
request.temperature > 2
)
) {
throw new DarvarehSDKError(
"temperature must be between 0 and 2",
);
}
if (
request.max_tokens !== undefined &&
(
!Number.isInteger(
request.max_tokens,
) ||
request.max_tokens <= 0
)
) {
throw new DarvarehSDKError(
"max_tokens must be a positive integer",
);
}
}
}
ویژگیهای تخصصی این Client:
- از Private Field واقعی JavaScript استفاده میکند.
- API Key را در Property عمومی قرار نمیدهد.
- Base URL را Normalize میکند.
- ورودی را قبل از Request بررسی میکند.
- Timeout دارد.
- AbortSignal خارجی را پشتیبانی میکند.
- Errorهای HTTP را به Error اختصاصی تبدیل میکند.
- پاسخ JSON را بهعنوان
unknownدریافت میکند. - پاسخ را در Runtime Parse میکند.
fetchقابل جایگزینی دارد و بنابراین تست آن سادهتر است.
ساخت فایل ورودی SDK
فایل src/index.ts:
export {
DarvarehClient,
} from "./client.js";
export {
DarvarehAPIError,
DarvarehResponseError,
DarvarehSDKError,
DarvarehTimeoutError,
} from "./errors.js";
export type {
APIErrorBody,
ChatChoice,
ChatCompletionRequest,
ChatCompletionResponse,
ChatMessage,
ChatRole,
DarvarehClientOptions,
RequestOptions,
TokenUsage,
} from "./types.js";
استفاده از export type کمک میکند Type Importها از Value Importها مشخص باشند.
فایل تنظیمات محیطی
فایل .env.example:
DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID
فایل .gitignore:
.env
.env.*
!.env.example
node_modules/
dist/
coverage/
*.log
.DS_Store
فایل واقعی .env را بسازید و مقدارهای واقعی را داخل آن قرار دهید.
شناسه و قیمت مدلهای موجود را در صفحه مدلهای درواره بررسی کنید.
ساخت Example
فایل examples/basic.ts:
import "dotenv/config";
import {
DarvarehAPIError,
DarvarehClient,
DarvarehSDKError,
DarvarehTimeoutError,
} from "../src/index.js";
function getRequiredEnv(
name: string,
): string {
const value = process.env[name]?.trim();
if (!value) {
throw new Error(
`Environment variable ${name} is required`,
);
}
return value;
}
async function main(): Promise<void> {
const client = new DarvarehClient({
apiKey: getRequiredEnv(
"DARVAREH_API_KEY",
),
timeoutMs: 60_000,
});
const response =
await client.createChatCompletion({
model: getRequiredEnv(
"DARVAREH_MODEL_ID",
),
messages: [
{
role: "system",
content: (
"You are a helpful Persian assistant. " +
"Answer clearly and accurately in Persian."
),
},
{
role: "user",
content: (
"Generic در TypeScript را " +
"با یک مثال ساده توضیح بده."
),
},
],
temperature: 0.3,
max_tokens: 800,
stream: false,
});
const answer =
response.choices[0]?.message.content;
if (!answer) {
throw new Error(
"The model returned an empty answer",
);
}
console.log(answer);
if (response.usage) {
console.log(
"Token usage:",
response.usage,
);
}
}
main().catch((error: unknown) => {
if (
error instanceof DarvarehAPIError
) {
console.error(
"API error:",
{
status: error.status,
code: error.code,
message: error.message,
},
);
process.exitCode = 1;
return;
}
if (
error instanceof DarvarehTimeoutError
) {
console.error(
"Request timed out",
);
process.exitCode = 1;
return;
}
if (
error instanceof DarvarehSDKError
) {
console.error(
"SDK error:",
error.message,
);
process.exitCode = 1;
return;
}
if (error instanceof Error) {
console.error(
"Unexpected error:",
error.message,
);
process.exitCode = 1;
return;
}
console.error(
"Unknown error occurred",
);
process.exitCode = 1;
});
اجرای Type Check
npm run check
این فرمان Typeها را بررسی میکند، اما فایل JavaScript تولید نمیکند.
اجرای Example
npm run dev
در صورت صحیحبودن .env، پاسخ مدل در Terminal نمایش داده میشود.
Build SDK
npm run build
خروجی:
dist/
├── client.d.ts
├── client.d.ts.map
├── client.js
├── client.js.map
├── errors.d.ts
├── errors.js
├── index.d.ts
├── index.js
├── parsers.d.ts
├── parsers.js
├── types.d.ts
└── types.js
فایلهای .d.ts Typeهای عمومی SDK را در اختیار پروژه مصرفکننده قرار میدهند.
ساخت Unit Test بدون درخواست واقعی
ازآنجاکه Client اجازه تزریق fetchImpl میدهد، میتوانیم پاسخ API را شبیهسازی کنیم.
فایل tests/client.test.ts:
import assert from "node:assert/strict";
import test from "node:test";
import {
DarvarehAPIError,
DarvarehClient,
} from "../src/index.js";
test(
"returns a typed chat completion",
async () => {
const fakeFetch: typeof fetch =
async () => {
return new Response(
JSON.stringify({
id: "chatcmpl-test",
object: "chat.completion",
created: 1234567890,
model: "test-model",
choices: [
{
index: 0,
message: {
role: "assistant",
content: "پاسخ آزمایشی",
},
finish_reason: "stop",
},
],
usage: {
prompt_tokens: 10,
completion_tokens: 5,
total_tokens: 15,
},
}),
{
status: 200,
headers: {
"content-type":
"application/json",
},
},
);
};
const client = new DarvarehClient({
apiKey: "test-key",
fetchImpl: fakeFetch,
});
const response =
await client.createChatCompletion({
model: "test-model",
messages: [
{
role: "user",
content: "سلام",
},
],
});
assert.equal(
response.id,
"chatcmpl-test",
);
assert.equal(
response.choices[0]
?.message.content,
"پاسخ آزمایشی",
);
},
);
test(
"throws a structured API error",
async () => {
const fakeFetch: typeof fetch =
async () => {
return new Response(
JSON.stringify({
error: {
message:
"Invalid API key",
code:
"invalid_api_key",
},
}),
{
status: 401,
headers: {
"content-type":
"application/json",
},
},
);
};
const client = new DarvarehClient({
apiKey: "test-key",
fetchImpl: fakeFetch,
});
await assert.rejects(
() => {
return client
.createChatCompletion({
model: "test-model",
messages: [
{
role: "user",
content: "سلام",
},
],
});
},
(error: unknown) => {
return (
error instanceof
DarvarehAPIError &&
error.status === 401 &&
error.code ===
"invalid_api_key"
);
},
);
},
);
test(
"rejects invalid temperature",
async () => {
const client = new DarvarehClient({
apiKey: "test-key",
fetchImpl:
async () =>
new Response("{}", {
status: 200,
}),
});
await assert.rejects(
() => {
return client
.createChatCompletion({
model: "test-model",
messages: [
{
role: "user",
content: "سلام",
},
],
temperature: 3,
});
},
/temperature must be between 0 and 2/,
);
},
);
اجرای تست:
npm test
این تستها:
- Request واقعی ارسال نمیکنند.
- API Key واقعی نمیخواهند.
- پاسخ موفق را بررسی میکنند.
- خطای API را بررسی میکنند.
- Validation ورودی را آزمایش میکنند.
چرا Dependency Injection برای fetch مفید است؟
اگر fetch مستقیماً و بدون امکان جایگزینی استفاده شود، تست Client ممکن است به Network واقعی وابسته شود.
با این طراحی:
fetchImpl?: typeof fetch;
در Production از fetch واقعی استفاده میشود و در تست، Function شبیهسازیشده جای آن را میگیرد.
این الگو مزایای زیر را دارد:
- تست سریعتر
- تست پایدارتر
- حذف وابستگی به Network
- حذف هزینه درخواست مدل
- امکان شبیهسازی خطاهای مختلف
- امکان تست Timeout و پاسخ نامعتبر
اضافهکردن Abort به Request
const controller =
new AbortController();
const client = new DarvarehClient({
apiKey:
process.env.DARVAREH_API_KEY ?? "",
});
const promise =
client.createChatCompletion(
{
model:
process.env.DARVAREH_MODEL_ID ??
"",
messages: [
{
role: "user",
content:
"TypeScript چیست؟",
},
],
},
{
signal: controller.signal,
},
);
setTimeout(
() => controller.abort(),
1000,
);
await promise;
AbortSignal برای لغو درخواست از سمت مصرفکننده استفاده میشود. SDK علاوه بر آن Timeout داخلی نیز دارد.
در برنامه واقعی، Timer نمونه را بعد از تکمیل عملیات پاک کنید یا منطق Abort را در Lifecycle مناسب رابط قرار دهید.
Streaming در TypeScript
در SDK این مقاله stream عمداً روی false محدود شده است:
stream?: false;
زیرا پاسخ Streaming ساختار متفاوتی دارد و باید Chunkهای SSE را مرحلهای Parse کرد.
برای افزودن Streaming بهتر است Method جدا طراحی شود:
client.streamChatCompletion(...)
خروجی آن میتواند AsyncIterable باشد:
async function* streamChat(): AsyncIterable<string> {
yield "بخش اول";
yield "بخش دوم";
}
جداکردن API معمولی و Streaming باعث میشود Contract هر Method روشنتر باشد.
Runtime Validation چرا ضروری است؟
این Type فقط در زمان توسعه وجود دارد:
interface ChatCompletionResponse {
id: string;
choices: ChatChoice[];
}
اگر API پاسخ زیر را برگرداند:
{
"unexpected": true
}
TypeScript در Runtime بهصورت خودکار آن را رد نمیکند.
روش نامناسب:
const data =
await response.json()
as ChatCompletionResponse;
این Type Assertion بدون Validation است.
روش بهتر:
const data: unknown =
await response.json();
const parsed =
parseChatCompletionResponse(data);
در پروژههای بزرگ میتوانید از Schema Validation استفاده کنید، اما اضافهکردن Library باید بر اساس نیاز، اندازه Bundle و معماری SDK تصمیمگیری شود.
Versioning در SDK
برای SDK عمومی، تغییر Typeها بخشی از API محصول است.
تغییرهای احتمالی Breaking:
- تغییر نام Method
- حذف Property
- اجباریکردن Property اختیاری
- تغییر Type خروجی
- تغییر ساختار Error
- تغییر Module Format
- حذف Export
- تغییر رفتار Timeout
پیش از انتشار نسخه جدید:
- Type Check اجرا شود.
- Unit Test اجرا شود.
- Build تولید شود.
- مثالها اجرا شوند.
- فایل Declaration بررسی شود.
- Migration Note نوشته شود.
- نسخه متناسب انتخاب شود.
مدیریت API Key در TypeScript
API Key را داخل کد قرار ندهید:
const client = new DarvarehClient({
apiKey: "کلید واقعی",
});
برای Backend یا Script محلی:
const apiKey =
process.env.DARVAREH_API_KEY;
اگر SDK در مرورگر اجرا شود، هر کلیدی که به JavaScript Frontend برسد باید قابل مشاهده فرض شود. برای اپلیکیشن عمومی، API Key درواره را روی Backend نگه دارید:
Browser
│
▼
Backend شما
│
▼
API درواره
SDK ساختهشده در این مقاله برای محیط Server-side یا ابزارهای محلی مناسبتر است. برای Frontend باید Client جداگانهای بسازید که فقط به Backend خودتان متصل شود.
خطاهای رایج TypeScript
خطای implicitly has an any type
Parameter 'value' implicitly has an 'any' type
پارامتر Type ندارد:
function normalize(value) {
return value.trim();
}
اصلاح:
function normalize(
value: string,
): string {
return value.trim();
}
خطای possibly undefined
const first =
response.choices[0];
console.log(first.message.content);
با noUncheckedIndexedAccess ممکن است first وجود نداشته باشد.
اصلاح:
const first =
response.choices[0];
if (!first) {
throw new Error(
"No response choice",
);
}
console.log(
first.message.content,
);
خطای Object is possibly null
const element =
document.querySelector("#app");
element.textContent = "Hello";
اصلاح:
if (!element) {
throw new Error(
"Element not found",
);
}
element.textContent = "Hello";
خطای Type is not assignable
Typeهای دو مقدار سازگار نیستند. بهجای استفاده سریع از as any، Contract را بررسی کنید.
ممکن است یکی از این موارد عامل باشد:
- Property اشتباه
- Optional بودن مقدار
- Union ناقص
- Literal Type متفاوت
- Generic اشتباه
- پاسخ API نامعتبر
- Type تعریفشده قدیمی
خطای Cannot find module
بررسی کنید:
- Package نصب شده باشد.
- Extension فایل در ESM درست باشد.
moduleResolutionمناسب باشد.- مسیر Import صحیح باشد.
- نام فایل و حروف کوچک و بزرگ هماهنگ باشند.
در پروژه NodeNext از Importهایی مانند زیر استفاده کردهایم:
import {
DarvarehClient,
} from "./client.js";
اگرچه فایل Source با پسوند .ts است، Extension خروجی Runtime در Import برابر .js نوشته میشود.
خطای fetch is not defined
در Runtimeهای قدیمی ممکن است fetch سراسری وجود نداشته باشد. از نسخه پشتیبانیشده Node.js یا یک fetchImpl سازگار استفاده کنید.
تعریف DOM Library در tsconfig فقط Type مربوط به Fetch را اضافه میکند و قابلیت Runtime را به محیط قدیمی اضافه نمیکند.
اشتباهات رایج در TypeScript
استفاده گسترده از any
any خطا را حل نمیکند؛ بررسی Type را کنار میگذارد.
استفاده زیاد از Type Assertion
اگر در بیشتر نقاط کد از as استفاده میکنید، ممکن است Type Design یا Runtime Validation مشکل داشته باشد.
تایپکردن مستقیم پاسخ API
const data:
ChatCompletionResponse =
await response.json();
این کار پاسخ واقعی را در Runtime اعتبارسنجی نمیکند.
غیرفعالکردن strict
پروژهای که با تنظیمات بسیار آزاد شروع شود، مهاجرت بعدی به Strict Mode سختتر میشود.
ساخت Typeهای بسیار پیچیده
Type System قدرتمند است، اما Typeهای بیش از حد پیچیده میتوانند درک کد و پیام خطا را دشوار کنند.
تکرار Typeهای مشابه
از Utility Type، Generic و Composition استفاده کنید، اما خوانایی را قربانی کوتاهشدن کد نکنید.
استفاده از enum برای هر مقدار ثابت
در بسیاری از موارد Literal Union سادهتر است:
type Status =
| "pending"
| "success"
| "error";
Enum همچنان کاربرد دارد، اما باید رفتار Runtime تولیدشده آن را بدانید.
فرض ایمنبودن TypeScript در Frontend
TypeScript نمیتواند Secret قرارگرفته در Bundle مرورگر را مخفی کند.
بهترین روشهای TypeScript
strictرا فعال کنید.- از Inference مناسب استفاده کنید.
- مرزهای عمومی را صریح Type کنید.
- داده خارجی را
unknownدر نظر بگیرید. - پاسخ API را در Runtime اعتبارسنجی کنید.
- از
anyفقط با دلیل روشن استفاده کنید. - Unionها را با Narrowing مدیریت کنید.
- Switchهای مهم را Exhaustive کنید.
- Errorهای اختصاصی بسازید.
- Functionها را کوچک و تکهدف نگه دارید.
- Type و Value Import را جدا کنید.
- Unit Test بنویسید.
- Build Output را بررسی کنید.
- Secret را وارد Source یا Bundle نکنید.
- نسخههای وابستگی را با Lock File کنترل کنید.
- Typeهای عمومی SDK را بخشی از Contract محصول بدانید.
مسیر یادگیری TypeScript
مرحله اول
- Typeهای پایه
- Array
- Object
- Function
- Type Inference
- Type Annotation
مرحله دوم
- Interface
- Type Alias
- Union
- Literal Type
- Intersection
- Optional Property
مرحله سوم
- Narrowing
- Type Guard
unknownnever- Discriminated Union
مرحله چهارم
- Generic
- Constraint
keyof- Utility Type
- Mapped Type
- Conditional Type
مرحله پنجم
- Module
- ESM
- TSConfig
- Declaration File
- Source Map
- Project Reference
مرحله ششم
- API Client
- Runtime Validation
- Error Design
- Dependency Injection
- Unit Test
- Build و Package Publishing
پرسشهای متداول
TypeScript چیست؟
TypeScript زبانی مبتنی بر JavaScript است که Static Type Checking و Tooling پیشرفتهتری به آن اضافه میکند.
TypeScript مخفف چیست؟
TypeScript نام زبان است و معمولاً با TS کوتاه میشود.
آیا TypeScript از JavaScript سختتر است؟
یادگیری Typeها مفاهیم جدیدی اضافه میکند، اما در پروژههای بزرگ میتواند درک Contractها، Refactoring و کشف خطاها را سادهتر کند.
آیا قبل از TypeScript باید JavaScript یاد بگیریم؟
بله، شناخت مبانی JavaScript برای استفاده درست از TypeScript ضروری است.
آیا مرورگر TypeScript را اجرا میکند؟
مرورگر معمولاً JavaScript را اجرا میکند. TypeScript پیش از اجرا به JavaScript تبدیل میشود یا توسط ابزار Build پردازش میشود.
Interface بهتر است یا type؟
پاسخ مطلقی وجود ندارد. Interface برای Object Contractها و Extension مناسب است؛ Type برای Union و Typeهای ترکیبی انعطاف بیشتری دارد. Consistency پروژه مهمتر است.
تفاوت unknown و any چیست؟
any بررسی Type را کنار میگذارد. unknown توسعهدهنده را مجبور میکند قبل از استفاده Type را بررسی کند.
Generic چیست؟
Generic Type یا Functionی است که Type را بهصورت پارامتر میگیرد و ارتباط میان ورودی و خروجی را حفظ میکند.
آیا TypeScript پاسخ API را اعتبارسنجی میکند؟
خیر. Interfaceها و Typeها در Runtime حذف میشوند. پاسخ API باید جداگانه Parse یا Validate شود.
آیا SDK مقاله را میتوان در مرورگر استفاده کرد؟
از نظر فنی بخشهایی از آن ممکن است قابل Bundle باشند، اما قراردادن API Key خصوصی در Frontend مناسب نیست. برای وبسایت عمومی، SDK را در Backend استفاده کنید.
چگونه مدل را تغییر دهیم؟
مقدار زیر را تغییر دهید:
DARVAREH_MODEL_ID=YOUR_MODEL_ID
فهرست مدلها و قیمت جاری آنها در صفحه مدلهای درواره قرار دارد.
Base URL درواره چیست؟
https://api.darvareh.ir/v1
Endpoint Chat Completions چیست؟
https://api.darvareh.ir/v1/chat/completions
آیا TypeScript هزینه اجرای برنامه را افزایش میدهد؟
Typeهای TypeScript معمولاً از خروجی JavaScript حذف میشوند. هزینه Runtime بیشتر به JavaScript تولیدشده، Libraryها و معماری برنامه بستگی دارد.
فایل d.ts چیست؟
فایل Declaration ساختار Typeهای یک Package را برای TypeScript و ویرایشگرها تعریف میکند، بدون آنکه Implementation کامل JavaScript را در خود داشته باشد.
چکلیست پایان آموزش
پس از تکمیل مقاله باید بتوانید:
- تفاوت JavaScript و TypeScript را توضیح دهید.
- Typeهای پایه تعریف کنید.
- Interface و Type Alias بسازید.
- Union و Literal Type استفاده کنید.
- Optional و Readonly Property تعریف کنید.
- داده
unknownرا Narrow کنید. - Type Guard سفارشی بنویسید.
- Discriminated Union طراحی کنید.
- Generic Function بسازید.
- از
keyofو Utility Type استفاده کنید. - TSConfig سختگیرانه تنظیم کنید.
- Error اختصاصی طراحی کنید.
- پاسخ API را در Runtime بررسی کنید.
fetchرا برای تست تزریق کنید.- SDK را Build کنید.
- Unit Test بدون Network اجرا کنید.
- API هوش مصنوعی درواره را از TypeScript فراخوانی کنید.
جمعبندی
TypeScript فقط JavaScript همراه چند Annotation نیست. ارزش اصلی آن زمانی مشخص میشود که Typeها برای مدلکردن دقیق Contractها، وضعیتها، Errorها و ارتباط میان ورودی و خروجی استفاده شوند.
در پروژه عملی این مقاله، یک SDK واقعی برای API درواره ساختیم که شامل موارد زیر بود:
- Typeهای Request و Response
- Client اختصاصی
- Errorهای ساختیافته
- Timeout
- AbortSignal
- Generic Design
- Runtime Parser
- Dependency Injection
- Unit Test
- Build همراه فایل Declaration
SDK از Base URL زیر استفاده میکند:
https://api.darvareh.ir/v1
برای اجرای پروژه در درواره ثبتنام کنید، API Key بسازید و مدل مناسب را از صفحه مدلهای درواره انتخاب کنید.
منابع تکمیلی
- مبانی TypeScript
- Typeهای روزمره در TypeScript
- Object Typeها
- Type Narrowing
- Functionها در TypeScript
- Generics در TypeScript
- Utility Typeهای TypeScript
- ساخت Type از Typeهای دیگر
- مرجع تنظیمات TSConfig
مقالات مرتبط
- ساخت افزونه VS Code با TypeScript و API درواره
- ساخت API هوش مصنوعی با NestJS و TypeScript
- ساخت اپلیکیشن هوش مصنوعی با Angular و TypeScript
- ساخت REST API هوش مصنوعی با Node.js و Express
- ساخت اپلیکیشن هوش مصنوعی با SvelteKit
- ساخت اپلیکیشن هوش مصنوعی با Vue و Nuxt
- ساخت اپلیکیشن دسکتاپ هوش مصنوعی با Electron
- مقایسه SDKهای هوش مصنوعی
- راهنمای API سازگار با OpenAI
- اتصال API هوش مصنوعی به اپلیکیشن
برای مطالعه شرایط استفاده و محدودیتهای مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.