اگر از جنگ دائمی بین امنیت تایپی (Type Safety) و انعطافپذیری SQL خام در پروژههای تایپاسکریپت خسته شدهاید، زمان آن رسیده که با لایههای میانی خداحافظی کنید. SQLBraid با رویکردی متفاوت، SQL را نه به عنوان یک هدف ثانویه، بلکه به عنوان زبان اصلی تعامل با دادهها تعریف میکند و به توسعهدهندگان اجازه میدهد کوئریهای خود را به صورت شفاف حفظ کنند و در عین حال قراردادهای سختگیرانهای برای نتایج اعمال نمایند.
به نقل از مستندات رسمی این پروژه، اکوسیستم تایپاسکریپت سالهاست که میان ORMهای سنگین — که دیتابیس را پشت لایههای انتزاعی پنهان میکنند — و درایورهای خام که هیچ امنیتی ندارند، تقسیم شده است. این تنش اغلب منجر به ایجاد «لایههای ترجمه» میشود؛ جایی که توسعهدهنده کدی مینویسد که یک کتابخانه سپس آن را به SQL تبدیل میکند و این فرآیند غالباً کوئریهای ناکارآمدی ایجاد میکند. همانطور که در تحلیلهای قبلی ما درباره بهینهسازی لایههای دسترسی به داده اشاره کردیم، حذف این انتزاعهای غیرضروری، کلید رسیدن به عملکرد حداکثری است.
SQLBraid در نسخه ۱.۰.۰ خود به عنوان یک ابزار SQL-First معرفی شده است. هدف این ابزار پر کردن شکاف میان امنیت و انعطافپذیری است، بدون اینکه نیاز باشد کد شما را بازنویسی کند. رسیدن به وضعیت GA (دسترسی عمومی) به این معناست که قراردادهای عمومی آن پایدار شدهاند، هرچند تمام درایورها لزوماً تمام قابلیتهای جهانی را ندارند. در این سیستم، رکوردهای پشتیبانی نسخهبندی شده، هر ترکیب از دیتابیس، درایور، پروفایل و محیط اجرا (Runtime Tuple)، بازبینیهای پیادهسازی و شواهد گردش کار را شناسایی میکنند. هر بازبینی تغییر یافته نیازمند گیتهای جدید و دقیق برای Runtime، مستندات و انتشار (Release) است و نسخههای مجاور، گواهینامههای یکدیگر را به ارث نمیبرند.
مثال سریع برای شروع
برای شروع کار در Node.js ۲۲.۱۸ یا نسخههای جدیدتر، میتوانید یک اسکریپت را با نام quickstart.mts ذخیره کرده و آن را با دستور node quickstart.mts اجرا کنید. در مثال زیر، یک دیتابیس در حافظه ایجاد شده و یک کوئری تایپشده اجرا میشود:
import { createNodeSqliteDatabase, sql } from "sqlbraid/node-sqlite";
import { DatabaseSync } from "node:sqlite";
interface UserRow { id: string; name: string; }
const native = new DatabaseSync(":memory:");
try {
native.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)");
native.prepare("INSERT INTO users (name) VALUES (?)").run("Ada");
const db = createNodeSqliteDatabase(native);
const userId = 1;
const users = await db.all(sql.rows<UserRow>` SELECT id, name FROM users WHERE id = ${userId} `);
console.log(users); // [{ id: "1", name: "Ada" }]
} finally {
native.close();
}
مکانیزم اصلی
مکانیزم اصلی SQLBraid بر یک اصل ساده استوار است: هرگونه درونیسازی (Interpolation) در تگهای آن، همیشه به عنوان یک مقدار متصل (Value Bind) در نظر گرفته میشود. این رویکرد بهطور پیشفرض از حملات تزریق SQL (SQL Injection) جلوگیری میکند، بدون اینکه نیاز باشد شما یک زبان کوئری اختصاصی یاد بگیرید.
زمانی که توسعهدهندگان به تغییرات ساختاری نیاز دارند — مانند نامهای پویا برای جداول یا لیستها — از کمککنندههای صریح استفاده میکنند. این ابزارها عبارتند از:
sql.identبرای شناسهها (Identifiers)sql.fragmentبرای بخشهای ناقص کوئریsql.listبرای آرایههاsql.joinبرای اتصال قطعاتsql.rawبرای SQL بدون پاکسازی (Unescaped)sql.emptyبرای قطعات بدون عملیات (Null-op)
SQL پویا و دستورات @braid
یکی از قدرتمندترین ویژگیهای این ابزار، معرفی دستورات @braid است. این دستورات اجازه میدهند SQL پویا را به صورت خوانا مستقیماً درون رشتههای تگدار بنویسید.
توسعهدهندگان میتوانند از دستوراتی مانند /*@braid if ${condition}*/ برای گنجاندن شرطی بخشهایی از کوئری استفاده کنند. سایر دستورات موجود شامل choose ،when ،otherwise ،where ،set و trim هستند. کامپایلر این دستورات را کاهش (Lower) میدهد و تضمین میکند که شاخههای غیرفعال به صورت Lazy باقی بمانند و تأثیری بر عملکرد برنامه نگذارند.
برای مثال، یک کوئری میتواند به این شکل ساخته شود:
const query = sql.rows<UserRow>`
SELECT id, name FROM users
/*@braid where*/
/*@braid if ${teamId != null}*/
AND team_id = ${teamId}
/*@braid end*/
/*@braid end*/
`;
قراردادهای نتیجه و نگاشت (Mapping)
SQLBraid حدس و گمان در مورد تایپهای نتیجه را با قراردادهای صریح جایگزین کرده است. رندرکننده یک RenderedStatement منطقی و تغییرناپذیر تولید میکند که در آن segments.length === parameters.length + 1 است. یک پارامتر رندر شده هرگز SQL، شناسه، کوئری تودرتو یا قطعه درایور نیست. مسئولیت متریالیزه کردن جایگاهها (Placeholders) بر عهده آداپتور منتخب است؛ نمادهایی مانند $1 ،? ،:1 ،@p1 و سینتکس native value-template جزئیات انتقال (Transport) هستند، نه هویت شکل منطقی.
شما میتوانید دقیقاً تعیین کنید که یک کوئری چه چیزی برگرداند:
sql.rows<UserRow>برای بازگرداندن چندین ردیف.sql.commandبرای عملیات بهروزرسانی (Update) یا حذف (Delete).sql.callبرای رویههای ذخیره شده (Stored Procedures) که خروجیها، مجموعههای نتیجه ناهمگون مرتب شده (resultSets) و یک مقدار بازگشتی اختیاری (returnValue) را برمیگرداند.sqlبرای SQLهای خاص درایور با نوع نتیجه نامشخص.
بر اساس مستندات، این ابزار از نگاشت نتایج Standard Schema پشتیبانی میکند. شما میتوانید یک اسکیما را به کوئری ردیفی متصل کنید یا در هر اجرا یکی ارسال کنید: const event = await db.one(eventQuery, { schema: EventSchema });. این امر تضمین میکند که برنامه هرگز ردیفهای بدشکل را پردازش نمیکند.
نگاشت دقیقاً یک ردیف به یک مقدار برنامه است. SQLBraid روابط را بازسازی (Hydrate) نمیکند، نقشههای شناسیت (Identity Maps) را نگه نمیدارد و تایپهای نتیجه SELECT/JOIN دلخواه را استنتاج نمیکند. عدم تطابق در نوع نتیجه، پس از اجرا خطای BRAID_RESULT_KIND را ایجاد میکند، هرچند نمیتواند اثرات جانبی ریشه (Root side effect) را خنثی کند. کرسرها و مجموعههای نتیجه صادر شده پیش از نگاشت غیرهمزمان، متریالیزه و بسته میشوند؛ کرسرهای خام، پورتالها، درخواستها و ردیفهای حامل (Carrier rows) هرگز از این لایه خارج نمیشوند.
پشتیبانی گسترده از درایورها
برخلاف بسیاری از ابزارها که شما را به یک دیتابیس خاص محدود میکنند، SQLBraid یک نما (Facade) دانهبندی شده ارائه میدهد. نمای ریشه هیچ دیالکت پیشفرض ضمنی ندارد و فقط قراردادهای مشترک زمان اجرا را صادر میکند. این ابزار از طیف وسیعی از درایورها و دیالکتها از طریق زیرمسیرهای خاص پشتیبانی میکند:
- PostgreSQL از طریق
sqlbraid/pg - MySQL و MariaDB از طریق
sqlbraid/mysql2وsqlbraid/mariadb - SQLite با گزینههای متنوع شامل
sqlbraid/node-sqlite،sqlbraid/better-sqlite3،sqlbraid/libsql،sqlbraid/sqlite-wasmوsqlbraid/d1 - Oracle از طریق
sqlbraid/oracledb - SQL Server از طریق
sqlbraid/tedious - Bun.SQL از طریق
sqlbraid/bun-sql(یک آداپتور چند-دیالکتی که نیازمند انتخاب صریح دیالکت است: "postgres", "mysql", "mariadb", یا "sqlite")
قابلیتهای پیشرفته زمان اجرا
API اجرا:
سطح عمومی زمان اجرا تعمداً کوچک طراحی شده است. تمام متدهای اجرا، گزینهها (Options) را در جایگاه انتهایی میپذیرند. اینترفیس ExecutionOptions شامل یک AbortSignal اختیاری است. اگر سیگنال از قبل لغو شده باشد، عملیات با دلیل آن رد (Reject) میشود. در صورت فعال بودن، این قابلیت نیازمند توانایی statement.cancel در آداپتور است؛ در غیر این صورت با خطای UnsupportedFeatureError (BRAID_CANCEL_UNSUPPORTED) مواجه میشوید.
متدهای کلیدی عبارتند از:
db.execute(query, options?): کوئریهای ردیفی، دستوری یا نامشخص را میپذیرد.db.all،db.one،db.maybeOne،db.stream: نیازمندsql.rowsهستند.db.call(call, options?):sql.callرا میپذیرد و خروجیها و مجموعههای نتیجه را برمیگرداند.db.batch(queries, options?)وdb.bulk(inputs, factory, options?)برای عملیاتهای گروهی.
نشستها و اجارهها (Sessions and Leases):
این ابزار مالکیت اتصال فیزیکی و اجارهها را با دقت مدیریت میکند. یک دیتابیس مستقیم، یک مجری فیزیکی (Physical Executor) را در بر میگیرد، در حالی که یک دیتابیس استخری (Pooled)، یک ConnectionProvider را پوشش میدهد. این تامینکننده منبعی برای اشیاء ConnectionLease است؛ خودش یک اتصال فیزیکی نیست و نباید به عنوان یک مجری جعلی مدلسازی شود.
هر عملیات ریشه در حالت استخری، یک اجاره میگیرد، I/O فیزیکی را انجام میدهد، آن را آزاد میکند و سپس نتایج متریالیزه شده را نگاشت میکند. یک استریم (Stream) اجاره خود را تا زمان بسته شدن منبع درایور حفظ میکند. برای عملیاتهای پیچیده، db.session به توسعهدهنده اجازه میدهد یک اجاره فیزیکی واحد را به دست آورده و در عملیاتهای تودرتو از آن استفاده کند. این موضوع برای حفظ وضعیت بین چندین کوئری بدون هزینه دریافت مجدد اتصال از استخر حیاتی است. اگر Primitive مربوط به نشست در دسترس نباشد، خطای BRAID_SESSION_UNSUPPORTED صادر میشود.
مدیریت تراکنشها:
تراکنشها از طریق db.tx مدیریت میشوند که از سطوح جداسازی مانند read-uncommitted ،read-committed ،repeatable-read و serializable پشتیبانی میکند. اگر یک درایور دیتابیس از یک سطح جداسازی خاص یا پرچم readOnly پشتیبانی نکند، SQLBraid صراحتاً با خطای UnsupportedFeatureError (مثلاً BRAID_TX_OPTION_UNSUPPORTED) شکست میخورد.
تراکنشهای تودرتو در صورتی که مجری آنها را ارائه دهد، از Savepoints استفاده میکنند. با این حال، فراخوانیهای تودرتوی tx(options, callback) برای جلوگیری از تغییر بیصدای یک تراکنش فعال، با خطای BRAID_TX_OPTIONS_NESTED رد میشوند. در PostgreSQL و Bun PostgreSQL، موفقیت callback کافی نیست؛ اگر سرور گزارش دهد که COMMIT در واقع Rollback شده است، خطای BRAID_TX_NOT_COMMITTED صادر میشود. عدم قطعیت در کنترل تراکنش، منبع مستقیم را مسموم کرده یا اجاره استخری را دور میاندازد.
کوئریهای آمادهشده و عملکرد
SQLBraid مفهوم پایدار «شکل آمادهشده» (Prepared Shape) را معرفی میکند. وقتی از db.prepare استفاده میکنید، ابزار شکل منطقی کوئری — شامل نوع نتیجه، قطعات کانونی و متادیتای ترتیب/جهت/خروجی — را قفل میکند.
const byId = db.prepare(
"user-by-id",
(id: string) => sql.rows<UserRow>` SELECT id, name FROM users WHERE id = ${id} `,
);
await byId.all("u_1", { schema: UserSchema });
در حالی که مقادیر میتوانند بین اجراها تغییر کنند، هرگونه تغییر در ساختار SQL باعث ایجاد خطای BRAID_PREPARED_SHAPE پیش از رسیدن کوئری به درایور میشود. کوئریهای آمادهشده میتوانند با ورودیهای اجباری (پیشفرض) یا بدون ورودی ({ input: "none" }) تعریف شوند. «آمادهشده» بودن به معنای یک شکل پایدار در اپلیکیشن SQLBraid است، نه لزوماً یک کش آمادهشده در سطح سرور یا درایور.
مشاهدهپذیری و تشخیص (Diagnostics)
برای کمک به عیبیابی، یک سیستم مشاهدهگر (Observer) قدرتمند تعبیه شده است. DatabaseOptions مشاهدهگرانی را میپذیرد که رویدادهایی مانند query:ready ،query:result ،query:mapped ،query:error ،bulk:ready ،bulk:result ،stream:start ،stream:end و transaction را منتشر میکنند.
رویداد query:ready پس از اتصال خالص (Pure Binding) و پیش از دریافت اجاره صادر میشود و آداپتور موثر، دیالکت و طرح انتقال (Transport Plan) را نمایش میدهد. توجه داشته باشید که مقادیر Bind توسط SQLBraid لاگ نمیشوند و مسئولیت حذف دادههای حساس (Redaction) بر عهده اپلیکیشن است. مشاهدهگران فقط میتوانند مشاهده کنند یا باعث شکست عملیات شوند؛ آنها نمیتوانند SQL را بازنویسی کنند یا کوئریها را مجدداً اجرا نمایند. فیلدهای زمانبندی به صورت durationMs ارائه میشوند.
برای کاربران سازمانی، بسته اختیاری @sqlbraid/opentelemetry اسپانهای کلاینت DB و متریک db.client.operation.duration را اضافه میکند. این بسته مالکیت SDK را نزد اپلیکیشن نگه میدارد و مقادیر Bind و SQLهای لیترال شده را حذف میکند.
تفاوتهای خاص درایورها
SQLBraid اذعان دارد که محیطهای اجرا و درایورهای مختلف محدودیتهای متفاوتی دارند:
- SQLite: درایورهای
node:sqliteوbetter-sqlite3در مرز فیزیکی به صورت همزمان (Synchronous) اجرا میشوند، هرچند API عمومی async باقی میماند.libSQLبرای رشتههای دقیق INTEGER به{ intMode: "string" }نیاز دارد و استریمینگ را رد میکند. آداپتورهای SQLite به دلیل محدودیتهای API ازdb.callپشتیبانی نمیکنند. - Bun: نسخه ۱.۳.۱۴ Bun هیچ قابلیت لغو فعال پشتیبانی شدهای ندارد (
BRAID_CANCEL_UNSUPPORTED). برای Bun MySQL/MariaDB، دستورات SELECT خالی یا DMLهایی که هیچ ردیفی را تحت تاثیر قرار ندادهاند، با علامتBRAID_RESULT_KIND_AMBIGUOUSمشخص میشوند زیرا درایورcommand: nullوaffectedRows: 0را گزارش میکند. همچنین Bun.SQL MySQL/MariaDB گزینههایreadOnlyرا با خطایBRAID_TX_OPTION_UNSUPPORTEDرد میکنند. - کانالهای روتین:
mysql2از مجموعههای نتیجه صادر شده CALL پشتیبانی میکند اما از حاملهای OUT/INOUT پشتیبانی نمیکند. refcursorهای PostgreSQL نیازمند یکdb.txموجود هستند. خروجی مستقیم کرسر در SQL Server پشتیبانی نمیشود وcallStreamرزرو شده اما پیادهسازی نشده است.
آنچه SQLBraid نیست
SQLBraid صراحتاً یک ORM نیست. این ابزار روابط را بازسازی نمیکند، نقشههای شناسیت را نگه نمیدارد و تایپهای نتیجه JOIN دلخواه را استنتاج نمیکند. این ابزار از بازنویسی SQL یا شبیهسازی ویژگیهای مفقود دیتابیس مانند کرسرها، تراکنشها یا لغو عملیات خودداری میکند. اگر ویژگیای توسط درایور زیرین پشتیبانی نشود، SQLBraid آن را به عنوان «عدم پشتیبانی» گزارش میکند، به جای اینکه تظاهر کند از طریق یک لایه نرمافزاری (Shim) آن را فراهم کرده است.
همچنین این ابزار یک پارسر کامل SQL، کامپایلر جهانی SQL یا پیادهسازی استخر اتصال (Connection Pool) نیست. دقت عددی به طور سختگیرانه مدیریت میشود: اعداد دقیق دیتابیس به صورت رشتههای کانونی (Canonical Strings) و مقادیر تقریبی IEEE به صورت number هستند. مقدار null به عنوان SQL NULL در نظر گرفته میشود، در حالی که undefined در Bindها پیش از دریافت اجاره با خطای BRAID_BIND_VALUE_UNSUPPORTED مواجه میشود.
معماری بستهها
اکوسیستم به بستههای تخصصی تقسیم شده تا وابستگیهای زمان اجرا سبک بمانند:
sqlbraid: نمای کانونی زمان اجرا.@sqlbraid/core: قراردادهای عمومی و دستورات رندر شده.@sqlbraid/template: تگها و دستورات مستقل از دیالکت.@sqlbraid/runtime: اجرا، نشستها و شکلهای آمادهشده.@sqlbraid/postgres,@sqlbraid/mysql,@sqlbraid/mariadb,@sqlbraid/sqlite,@sqlbraid/oracle,@sqlbraid/mssql: سیاستها و بازرسان خاص هر دیالکت.@sqlbraid/bun-sql: آداپتور درایور چند-دیالکتی.@sqlbraid/compilerو@sqlbraid/vite: ابزارهای کاهش و پیش-تبدیل.@sqlbraid/metadata,@sqlbraid/codegen,@sqlbraid/cli: ابزارهایی برای اسنپشاتها و تولید کد.
این انتخاب طراحی، مسئولیت صحت SQL را به توسعهدهنده منتقل میکند اما «جادویی» را که اغلب ORMها را در محیطهای عملیاتی دشوار میکند، حذف مینماید.
این تغییر رویکرد نشاندهنده روندی رو به رشد به سمت لایههای داده «شفاف» است. با حذف انتزاعِ Query Builder، SQLBraid به توسعهدهندگان اجازه میدهد از تمام قدرت ویژگیهای بومی دیتابیس خود استفاده کنند و در عین حال مزایای امنیت تایپی تایپاسکریپت را حفظ نمایند.
برای یک توسعهدهنده معمولی، این به معنای زمان کمتر برای جنگیدن با API یک کتابخانه و زمان بیشتر برای نوشتن SQL بهینه است. این ابزار در واقع با دیتابیس به عنوان یک شهروند درجه یک برخورد میکند، نه یک سطل ذخیرهسازی که باید پشت یک رابط شیءگرا پنهان شود.
اگر یک بکاند تایپاسکریپت با عملکرد بالا مدیریت میکنید، باید ارزیابی کنید که آیا ORM فعلی شما سربار غیرضروری ایجاد میکند یا خیر. میتوانید با تست SQLBraid روی یک دیتابیس SQLite در حافظه شروع کنید تا ببینید آیا گردش کار SQL-First با نیازهای تیم شما سازگار است یا خیر.
گام بعدی شما
- اگر از ORMهای سنگین استفاده میکنید، یک ماژول کوچک از دیتابیس خود را با SQLBraid بازنویسی کنید تا تفاوت در شفافیت کوئریها را ببینید.
- برای پروژههایی که نیاز به کوئریهای پویا و پیچیده دارند، دستورات
@braidرا جایگزین منطقهای شرطی پیچیده در جاوااسکریپت کنید. - در محیطهای تست، از SQLite در حافظه برای اعتبارسنجی سریع قراردادهای تایپی نتایج استفاده کنید.
اما داستان بهینهسازی لایههای داده به اینجا ختم نمیشود؛ برای درک چگونگی مدیریت حافظه در مقیاس بالا، تحلیل ما درباره کشینگ در سطح CPU را بخوانید.




گفتگو