dg.run

ورود و پایگاه داده

برنامه شما فایل‌های ایستا است، اما باز هم می‌تواند کاربر و داده ذخیره‌شده داشته باشد. پلتفرم دو سرویس روی نشانی خود برنامه شما، زیر /_dg/ فراهم می‌کند:

  • ورود با dg.run: کاربران در dg.run وارد می‌شوند (یا ثبت‌نام می‌کنند) و برنامه شما را متصل می‌کنند. شما هرگز رمز عبور یا کد ایمیلی را مدیریت نمی‌کنید.
  • یک پایگاه داده NoSQL کوچک: اسناد JSON که در مجموعه‌ها گروه‌بندی می‌شوند، خصوصی برای هر کاربر یا مشترک با همه.

هیچ کلید یا تنظیمی وجود ندارد. پلتفرم از روی نشانی‌ای که برنامه روی آن اجرا می‌شود می‌داند کدام برنامه تماس می‌گیرد.

نصب

npm install @dgrun/sdk
import { createDg } from '@dgrun/sdk'

const dg = createDg()
await dg.ready // اگر کاربر همین حالا از dg.run برگشته، ورود را کامل می‌کند

if (!dg.user) {
  button.onclick = () => dg.signIn()
} else {
  console.log(dg.user.email)
}

signIn() مرورگر را به dg.run می‌فرستد. کاربر وارد می‌شود، می‌بیند برنامه شما چه می‌خواهد، اجازه دادن را می‌زند و وارد برنامه شما می‌شود. اگر قبلاً برنامه شما را متصل کرده باشد، بدون پرسش دوباره مستقیم برمی‌گردد.

dg.user برابر { id, email } است. id برای آن کاربر در برنامه شما ثابت و در هر برنامه دیگر متفاوت است.

ذخیره داده

const todos = dg.db.collection('todos') // خصوصی برای هر کاربر

const doc = await todos.put({ title: 'خرید شیر', done: false }) // سند جدید، کلید تصادفی
await todos.put({ title: 'تخم‌مرغ', done: false }, 'eggs')       // کلید خودتان: بساز یا جایگزین کن
await todos.patch('eggs', { done: true })                        // چند فیلد را تغییر بده
const { items, hasMore } = await todos.list({
  where: [{ field: 'done', value: false }],
  orderBy: 'createdAt',
  desc: true,
  limit: 20,
})
await todos.get('eggs')
await todos.delete('eggs')

هر سند به‌صورت { key, data, owner, createdAt, updatedAt } برمی‌گردد.

مجموعه‌های خصوصی و عمومی

collection('x') (پیش‌فرض) collection('x', 'public')
چه کسی می‌تواند بخواند فقط کاربری که آن را ساخته همه کاربران برنامه شما، واردشده یا ناشده
چه کسی می‌تواند بسازد کاربران واردشده کاربران واردشده
چه کسی می‌تواند تغییر دهد یا حذف کند مالک فقط مالک
کلید یکسان برای دو کاربر دو سند جداگانه یک سند، متعلق به کسی که اول آن را ساخته

از مجموعه‌های عمومی برای چیزهایی مثل تخته مشترک یا جدول امتیاز استفاده کنید. list({ mine: true }) فقط اسناد عمومی خود کاربر واردشده را برمی‌گرداند و owner روی هر سند به شما امکان می‌دهد نشان دهید چه کسی آن را نوشته است.

این قواعد را پلتفرم اجرا می‌کند، نه کد شما؛ پس کاربر نمی‌تواند با ابزارهای توسعه‌دهنده مرورگر دورشان بزند. به همین دلیل نقش «مدیر» وجود ندارد: اگر داده‌ای لازم دارید که فقط خودتان بتوانید تغییر دهید، هنوز در این چارچوب نمی‌گنجد.

پرس‌وجوها

where حداکثر ۵ شرط روی فیلدهای سطح بالای داده شما می‌پذیرد: eq (پیش‌فرض)، ne، gt، gte، lt، lte و in (با فهرستی از مقادیر). مقادیر رشته، عدد، بولین یا null هستند. orderBy برابر createdAt، updatedAt یا یک فیلد سطح بالاست. برای صفحه‌بندی از limit (حداکثر ۱۰۰) و skip استفاده کنید.

نقش‌ها و اجازه‌ها

به کاربران برچسب اجازه بدهید (مثل editor، admin یا billing.read) و برای هر مجموعه بگویید کدام برچسب‌ها می‌توانند بخوانند، بسازند، تغییر دهند و حذف کنند. سرور dg.run هر قاعده را در هر درخواست اجرا می‌کند، پس هیچ چیز به کد خود برنامه شما وابسته نیست و کاربر نمی‌تواند با ویرایش برنامه شما دورش بزند.

این را در برنامه‌های من ← داده‌ها و کاربران ← دسترسی تنظیم کنید:

  • اجازه‌های پیش‌فرض: برچسب‌هایی که هر کاربر می‌گیرد، مگر اینکه فهرست خودش را جداگانه تعیین کنید. زیر کاربران می‌توانید به یک نفر فهرست خودش را بدهید (فهرست خالی یعنی بدون برچسب)؛ «استفاده از پیش‌فرض برنامه» او را به آن برمی‌گرداند. تغییر یک پیش‌فرض به همه کسانی می‌رسد که از آن پیروی می‌کنند.
  • قواعد مجموعه: برای هر مجموعه، فهرستی از ذی‌نفعان برای هر عملیات: * (همه، حتی ناشده)، user (کاربر واردشده‌ای که برچسب user دارد: همه به‌طور پیش‌فرض آن را دارند، اما می‌توانید آن را از برچسب‌های پیش‌فرض برنامه یا از برچسب‌های یک نفر بردارید؛ بدون آن، کاربر واردشده می‌ماند و رکوردهای خودش را نگه می‌دارد اما با هیچ قاعده‌ای که user می‌دهد مطابقت ندارد)، @owner (سازنده همان رکورد) یا هر برچسبی. همان فهرست‌ها روی هر رکورد اعمال می‌شوند. مجموعه‌ای که قاعده ندارد مثل قبل رفتار می‌کند (مجموعه‌های خصوصی به هر کاربر تعلق دارند؛ مجموعه‌های عمومی برای همه قابل خواندن‌اند، هر کاربر واردشده می‌تواند بسازد و فقط سازنده می‌تواند تغییر دهد).
  • فیلدهای محافظت‌شده: فیلدهایی را نام ببرید که فقط برخی ذی‌نفعان می‌توانند در هر رکورد تعیین یا تغییر دهند، بدون دست زدن به تک‌تک رکوردها. مثلاً amount: admin به هر کسی که اجازه تغییر یک تراکنش را دارد امکان می‌دهد یادداشتش را تغییر دهد، اما فقط مدیر می‌تواند مبلغ را تغییر دهد. نوشتنی که بدون اجازه به فیلد محافظت‌شده دست بزند، کامل با field_forbidden شکست می‌خورد؛ فرستادن دوباره همان مقدار مشکلی ندارد.
  • اجازه‌های رکورد (اختیاری): آن را روشن کنید و هر رکورد می‌تواند acl خودش را داشته باشد: چه کسی می‌تواند آن را بخواند، تغییر دهد یا حذف کند: یک برچسب، *، user، یا یک نفر (user:<id>، همان idی که به‌صورت doc.owner می‌بینید).

کسی اجازه نوشتن یک رکورد را دارد وقتی دست‌کم یک لایه بله بگوید (قاعده مجموعه، یا acl خود رکورد)؛ سپس فیلدهای محافظت‌شده محدود می‌کنند کدام فیلدها را می‌تواند تغییر دهد. نمونه‌ها:

// مقالات: همه می‌خوانند، ویرایشگرها می‌نویسند، فقط مدیران حذف می‌کنند. یک‌بار در داشبورد تنظیم می‌شود:
//   read: * · create: editor · update: editor · delete: admin
const articles = dg.db.collection('articles', 'public')
await articles.put({ title: 'سلام' }, 'hello')        // 'forbidden' مگر اینکه کاربر ویرایشگر باشد

// تیکت‌های پشتیبانی: خصوصی برای نویسنده، اما کارکنان با برچسب `support` همه را می‌بینند و پاسخ می‌دهند.
//   read: @owner, support · update: @owner, support · delete: @owner
const tickets = dg.db.collection('tickets')
await tickets.list()                                   // support: تیکت‌های همه؛ بقیه: فقط تیکت‌های خودشان
await tickets.list({ mine: true })                     // فقط مال من
await tickets.patch('t1', { answer: 'انجام شد' }, { owner: ticket.owner })   // تیکت یک نفر دیگر، اگر قواعد اجازه دهند

// تراکنش‌ها: کاربران یادداشت خودشان را ویرایش می‌کنند، اما فقط مدیر می‌تواند مبلغ را تعیین یا تغییر دهد.
//   read: @owner, admin · update: @owner, admin · fields: amount -> admin
const tx = dg.db.collection('transactions')
await tx.patch('t1', { note: 'ناهار' })                // برای سازنده مشکلی ندارد
await tx.patch('t1', { amount: 1 })                    // DgError 'field_forbidden' (error.data.field === 'amount') مگر برای مدیر

// اشتراک یک رکورد: با یک نفر، با یک برچسب، با همه (مجموعه‌های دارای اجازه رکورد).
await docs.put({ text: '…' }, 'plan', { acl: { read: [`user:${friend.owner}`, 'reviewer'], update: ['reviewer'] } })

کسی که اجازه ندارد forbidden (واردشده) یا unauthorized (ناشده) می‌گیرد. رکوردی که اجازه خواندنش را ندارید دقیقاً مثل رکوردی است که وجود ندارد. کسی که رکوردی را به‌روزرسانی می‌کند هرگز آن را تصاحب نمی‌کند: سازنده‌اش مالک می‌ماند.

نمایش صفحه‌های درست، حتی آفلاین

dg.user.permissions برچسب‌های کاربر را فهرست می‌کند و await dg.can('editor') / await dg.permissions() آن‌ها را از یک توکن امضاشده که روی دستگاه با کلید عمومی سرور بررسی می‌شود می‌خوانند؛ پس بدون شبکه کار می‌کنند و روی دستگاه قابل ویرایش نیستند. برچسب‌ها در توکن‌های ورود، کنار id مخصوص برنامه کاربر (dg.user.id) امضا می‌شوند.

این فقط برای نمایش یا پنهان کردن رابط کاربری است. سرور هرگز به توکن اعتماد نمی‌کند: برچسب‌های کاربر را در هر درخواست از پایگاه داده خودش می‌خواند، پس برچسبی که بردارید فوراً از کار می‌افتد. روی دستگاهی که آفلاین می‌ماند، نسخه امضاشده برچسب‌های قدیمی را تا انقضا (حداکثر ۱۵ روز) یا تا اتصال بعدی دستگاه نشان می‌دهد.

محدودیت‌ها

اندازه سند ۱۶ کیلوبایت JSON
سند برای هر کاربر، در هر برنامه ۱٬۰۰۰
سند برای هر برنامه ۱۰٬۰۰۰
اندازه درخواست ۶۴ کیلوبایت
تودرتویی ۸ سطح؛ نام فیلدها نمی‌تواند با $ شروع شود یا . داشته باشد
نام‌ها مجموعه‌ها: حروف کوچک، ارقام، - و _ (حداکثر ۴۰)؛ کلیدها: حروف، ارقام، . _ : - (حداکثر ۱۲۸)

وقتی محدودیتی رد شود، فراخوانی با quota_exceeded یا doc_too_large شکست می‌خورد.

خطاها

هر فراخوانی ناموفق یک DgError با code پرتاب می‌کند: unauthorized (اول وارد شوید، یا نشست تمام شده)، forbidden (اجازه‌های شما آن را نمی‌دهد)، not_found، invalid_data، invalid_query، doc_too_large، quota_exceeded، app_not_found، و برای نقش‌ها acl_disabled / invalid_acl.

اگر کاربر برنامه شما را قطع کند یا مسدود شود، فراخوانی بعدی‌اش با unauthorized شکست می‌خورد و SDK او را محلی خارج می‌کند.

TypeScript

SDK نوع‌هایش را همراه دارد. آنچه ذخیره می‌کنید را توصیف کنید و هر فراخوانی بررسی می‌شود:

interface Note { text: string; done: boolean }
const notes = dg.db.collection<Note>('notes')

await notes.put({ text: 'خرید شیر', done: false })
await notes.list({ where: [{ field: 'done', value: false }], orderBy: 'updatedAt' }) // نام فیلدها با Note بررسی می‌شوند
await notes.put({ text: 1, done: false }) // خطای کامپایل

هم interface و هم aliasهای type کار می‌کنند. مجموعه‌ای بدون آرگومان نوع هر شیء JSON را می‌پذیرد. فراخوانی‌های ناموفق یک DgError پرتاب می‌کنند که code آن نوع‌دار است، پس e.code === 'quota_exceeded' خودکار تکمیل می‌شود.

دیدن و مدیریت داده‌ها و کاربران برنامه شما

هر چیزی که برنامه شما ذخیره می‌کند، و هر کسی که آن را متصل کرده، در یک جا است: برنامه‌های من ← داده‌ها و کاربران (از کارت برنامه خود بازش کنید). فقط شما، مالک برنامه، می‌توانید بازش کنید. مدیر dg.run هم می‌تواند برای کمک در یک پرونده پشتیبانی بازش کند، و آنچه مدیری باز، ویرایش یا حذف کند در ردپای حسابرسی ثبت می‌شود.

صفحه فقط برنامه منتشرشده را نشان می‌دهد. پیش‌نمایش‌های مدیر از داده جداگانه استفاده می‌کنند که هرگز اینجا نمایش داده نمی‌شود. تا وقتی برنامه شما منتشر نشده و کسی متصلش نکرده، صفحه همین را می‌گوید. سرصفحه تعداد کاربران متصل، اسناد و مشترکان فعال را می‌شمارد و سه زبانه دارد: داده‌ها، کاربران و دسترسی.

داده‌ها

هر مجموعه‌ای که برنامه شما استفاده می‌کند، با نوعش (خصوصی یا عمومی) و تعداد اسنادش، مجموعه‌های خصوصی هم شامل. یک مجموعه را برای فیلتر فهرست انتخاب کنید، یا همه اسناد را بزنید. هر ردیف کلید سند، مجموعه‌اش، مالکش (نشانی ایمیل او)، زمان آخرین تغییر و اندازه‌اش را نشان می‌دهد. فهرست‌ها در هر صفحه ۲۵ سند نشان می‌دهند، جدیدترین تغییر اول.

  • ویرایش JSON سند را باز می‌کند. ذخیره از همان قواعد قالب SDK پیروی می‌کند: یک شیء JSON، حداکثر ۱۶ کیلوبایت، بدون نام فیلدی که با $ شروع شود یا . داشته باشد. در مجموعه‌ای با اجازه‌های رکورد می‌توانید فهرست‌های acl خود سند (چه کسی بخواند، تغییر دهد و حذف کند) را هم ویرایش کنید.
  • حذف سند را برای همیشه برمی‌دارد (بازگردانی وجود ندارد).
  • ویرایش‌ها و حذف‌های شما فوراً به صفحه‌هایی می‌رسد که مجموعه را تماشا می‌کنند (بخش زمان واقعی را ببینید).
  • شما مالک هستید، پس ویرایش‌های اینجا نه با قواعد مجموعه و نه با فیلدهای محافظت‌شده مسدود نمی‌شوند. فقط محدودیت‌های قالب و اندازه اعمال می‌شوند.

کاربران

همه کسانی که برنامه شما را متصل کرده‌اند، جدیدترین اول. برای هر نفر می‌بینید:

ستون چیست
ایمیل نشانی‌ای که هنگام اجازه دادن با برنامه شما به اشتراک گذاشت. نشان مسدود یعنی dg.run حساب را مسدود کرده: فراخوانی‌هایش شکست می‌خورند.
شناسه در برنامه dg.user.id او، و owner هر سندی که می‌سازد. در هر برنامه دیگر متفاوت است، پس می‌توانید آنچه اینجا می‌بینید را با آنچه کدتان می‌بیند تطبیق دهید.
متصل زمانی که اول‌بار برنامه شما را اجازه داد.
اسناد چند سند در برنامه شما دارد.
طرح، زمان باقی‌مانده، تمدید خودکار اگر طرح می‌فروشید، چه چیزی دارد: نام طرح، روزهای باقی‌مانده (یا همیشگی)، و اینکه تمدید می‌شود یا نه. باز کردن فهرست هر چیز سررسیدشده را تمدید یا پایان می‌دهد، پس هرگز قدیمی نیست.
اجازه‌ها برچسب‌های اجازه او، یا «پیش‌فرض برنامه».

برای هر نفر می‌توانید:

  • دیدن داده: فقط اسناد او را فهرست کنید (و مثل هر سند دیگری ویرایش یا حذفشان کنید).
  • ویرایش اجازه‌ها: فهرست برچسب‌های خودش را به او بدهید، یا به برچسب‌های پیش‌فرض برنامه برگردانیدش. در درخواست بعدی‌اش به او می‌رسد.
  • قطع اتصال کاربر: نشست او را فوراً پایان می‌دهد و هر چیزی را که در برنامه شما ذخیره کرده پاک می‌کند. فراخوانی بعدی‌اش با unauthorized شکست می‌خورد و SDK او را روی دستگاهش خارج می‌کند. بعداً می‌تواند دوباره متصل شود، از صفر شروع می‌کند. این کار برگشت‌پذیر نیست.

دسترسی

برچسب‌های اجازه پیش‌فرض و قواعد هر مجموعه: چه کسی می‌تواند بخواند، بسازد، تغییر دهد و حذف کند، فیلدهای محافظت‌شده و اجازه‌های رکورد. در نقش‌ها و اجازه‌ها توضیح داده شده و یک تغییر در درخواست بعدی اعمال می‌شود.

تطبیق یک نفر با داده‌هایش

کاربری مشکلی را گزارش می‌کند و نشانی ایمیلش را می‌دهد. کاربران را باز کنید، او را پیدا کنید و دیدن داده را بزنید تا دقیقاً همان چیزی را که او می‌بیند ببینید. در کد خودتان، همان شخص dg.user.id است:

// مقدار «شناسه در برنامه»، و `owner` هر یک از اسنادش:
const mine = (await notes.list({ limit: 100 })).items // اسناد خصوصی خودش
console.log(dg.user.id === mine[0]?.owner)            // true

// مجموعه‌های عمومی: اسناد نویسنده را از بقیه جدا کنید
const guestbook = dg.db.collection<{ text: string }>('guestbook', 'public')
const { items } = await guestbook.list({ orderBy: 'createdAt', desc: true })
const own = items.filter((d) => d.owner === dg.user?.id)

برای اینکه کسی بتواند مدیریت کند (مثلاً پیام دیگران را حذف کند)، زیر کاربران برچسبی مثل moderator به او بدهید، آن را زیر دسترسی به قاعده حذف مجموعه اضافه کنید، و دکمه را فقط به کسانی نشان دهید که آن را دارند:

const moderator = !!dg.user?.permissions.includes('moderator') // تعیین می‌کند چه چیزی نمایش داده شود؛ سرور هنوز هر فراخوانی را بررسی می‌کند
await guestbook.delete(doc.key, { owner: doc.owner })          // پیام یک نفر دیگر؛ 'forbidden' مگر اینکه قواعد اجازه دهند

خوب است بدانید

  • در صفحه رضایت به کاربران گفته می‌شود که می‌توانید ببینید برنامه شما برایشان چه ذخیره می‌کند و برنامه شما نشانی ایمیلشان را دریافت می‌کند. فقط چیزی را ذخیره کنید که لازم دارید.
  • هنوز خروجی گرفتن یا دسترسی با کلید API وجود ندارد: همه‌چیز در این صفحه در داشبورد انجام می‌شود.
  • صفحه به هیچ کدی در برنامه شما و به هیچ چیزی برای روشن کردن نیاز ندارد.

نکته‌ها

  • ورود ۳۰ روز روی یک دستگاه دوام می‌آورد و تا وقتی کاربر برنامه را استفاده می‌کند خودکار تمدید می‌شود. SDK یک توکن دسترسی ۱۵ دقیقه‌ای فقط در حافظه نگه می‌دارد؛ بخش ماندگار یک کوکی httpOnly است که کد شما نمی‌تواند بخواند. هنگام بارگذاری صفحه پیش از خواندن dg.user، await dg.ready را انجام دهید. پس از ۳۰ روز بی‌استفاده بودن، signIn() کاربر را با یک لمس برمی‌گرداند. signOut() نشست را روی سرور هم پایان می‌دهد.
  • زمان واقعی. collection.live و collection.subscribe تغییرات را به صفحه شما می‌فرستند؛ بدون اتصال، live هر ۱۵ ثانیه دوباره فهرست می‌کند. زمان واقعی را ببینید.
  • آفلاین. بدون اتصال، فراخوانی‌ها شکست می‌خورند. اگر برنامه شما آفلاین کار می‌کند (نصب و کار کردن آفلاین را ببینید)، نسخه محلی از آنچه کاربر لازم دارد نگه دارید و هیچ چیز زیر /_dg/ را در service worker خود کش نکنید.
  • _dg/ رزرو شده است. بسته‌ای با پوشه _dg در ریشه رد می‌شود.
  • پیش‌نمایش‌های مدیر داده جداگانه و مخصوص خودشان را دارند، پس بررسی یک نسخه هرگز به داده کاربران منتشرشده شما دست نمی‌زند.
  • حریم خصوصی. برنامه شما نشانی ایمیل کاربر و هیچ چیز دیگری از حسابش را دریافت نمی‌کند. به‌عنوان توسعه‌دهنده می‌توانید هر چیزی را که برنامه شما ذخیره می‌کند، از جمله داده خصوصی، ببینید، ویرایش و حذف کنید؛ پس فقط چیزی را ذخیره کنید که لازم دارید و به کاربرانتان بگویید چه نگه می‌دارید. کاربران می‌توانند اتصال یک برنامه را قطع و داده‌هایش را پاک کنند.

می‌خواهید از برنامه‌تان پول بگیرید؟ طرح‌ها و اشتراک‌ها را ببینید.

زمان واقعی

collection.subscribe و collection.live بدون نظرسنجی مکرر به صفحه شما می‌گویند اسناد تغییر کرده‌اند. فقط تغییراتی فرستاده می‌شود که کاربر واردشده اجازه خواندنشان را دارد (همان قواعد get و list).

const notes = dg.db.collection<Note>('notes', 'public')

// فهرست را روی صفحه نگه دارید: همین حالا، پس از هر تغییر و پس از هر اتصال دوباره فراخوانی می‌شود.
const stop = notes.live({ orderBy: 'createdAt', desc: true, limit: 20 }, (page) => render(page.items))

// یا خودتان به هر تغییر واکنش دهید.
const off = notes.subscribe((e) => {
  if (e.type === 'put') upsert(e.doc)            // ساخته یا تغییر کرد
  else if (e.type === 'delete') remove(e.key)    // حذف شد
  else list()                                    // 'reset': دوباره متصل شد، ممکن است چیزی را از دست داده باشید
})

اتصال با اولین اشتراک باز می‌شود و کمی بعد از پایان آخرین اشتراک بسته می‌شود. اتصال‌ها تقریباً هر پنج دقیقه تمدید می‌شوند؛ SDK خودش دوباره متصل می‌شود و reset می‌فرستد تا دوباره فهرست کنید. اگر اتصالی برقرار نشود، live به فهرست کردن هر ۱۵ ثانیه برمی‌گردد و dg.realtime.status ('open' | 'connecting' | 'closed' | 'off'، با dg.realtime.onStatus(fn)) می‌گوید در کدام حالت هستید. حداکثر ۲۰ مجموعه را می‌توان همزمان تماشا کرد. برای خاموش کردن، realtime: false را به createDg بدهید.

نمونه‌ها

برنامه‌های کامل که می‌توانید بسازید، zip کنید و بارگذاری کنید (هر پوشه یک README با مراحل و یک فایل .zip آماده دارد):

  • basic-example: جزیره‌های React روی صفحه‌های HTML ساده در انگلیسی و فرانسوی. یک دفتر مهمان عمومی و یادداشت‌های خصوصی، نوع‌های TypeScript، collection.live با نشان Live، و نقش moderator که می‌تواند پیام هر کسی را حذف کند (در داده‌ها و کاربران ← دسترسی / کاربران تنظیم می‌شود).
  • pro-notes: یک سطح رایگان، یک آزمایش رایگان، طرح‌های ماهانه و سالانه و یک خرید یک‌باره، با dg.billing.subscribe() و entitlement() (طرح‌ها و اشتراک‌ها را ببینید).
  • market-pwa: یک شمارنده با دو صفحه متفاوت روی یک نشانی، برای فهرست بازار و برای برنامه نصب‌شده.
  • todo-pwa: یک برنامه کوچک قابل نصب با Vite + React و یک service worker، به‌عنوان نقطه شروع برای نصب و کار آفلاین (بدون ورود یا پایگاه داده).

basic-example و pro-notes از @dgrun/sdk وارد می‌کنند. در پروژه خودتان npm install @dgrun/sdk را اجرا کنید و آن را با Vite، esbuild یا هر باندلری بسته‌بندی کنید.