معماری
مسیر دادهٔ تقویمم این است:
Android UI → OfflineRepository → Room + outbox ─┐
├→ sync API → Prisma → PostgreSQL
Web UI → local actions → Dexie + outbox ───────┘
apps/api: Express، احراز هویت و push/pull؛ مالک دادهٔ پذیرفتهشدهٔ سرور.apps/calendar-web: React/Vite؛ مالک تغییرات محلیِ ارسالنشده در Dexie.apps/mobile: کلاینت اندروید Kotlin (هدف Wasm حذف شد؛ وبِ محصول React است)؛ ذخیرهٔ محلی Room و همان قرارداد wire.packages/contracts: شکل wire با Zod؛ OpenAPI و SDK Kotlin از آن تولید میشوند.packages/design-tokens: توکنهای مشترک رابط؛docs: سایت Docusaurus.
سه مفهوم این تصویر که در همهٔ تصمیمها تکرار میشوند:
- رابط کاربری همیشه از ذخیرهٔ محلی میخواند؛ «ویرایش محلی موفق شد» به معنی پذیرش سرور نیست.
outboxصف نیتهای ارسالنشده است (توضیح کامل در همگامسازی).canonicalآخرین وضعیت شناختهشدهٔ سرور است و جدا از پیشنویس کاربر نگه داشته میشود؛ این دو میتوانند متفاوت باشند.
کجا را تغییر بدهم؟
| مسیر | مسئولیت |
|---|---|
apps/mobile/feature | صفحهها و جریانهای اندروید؛ برای auth، ویرایش و sync به core/data، برای Room به core/database بروید |
apps/calendar-web/src/features | رفتار محصول در وب؛ src/lib/sync و src/lib/db مخصوص sync و ذخیرهسازیاند |
apps/api/src/routes و src/services | احراز هویت، اعتبارسنجی و اعمال تغییرات؛ prisma/ برای schema و migrationها |
packages/contracts | قرارداد Zod؛ منبع تولید OpenAPI و SDK Kotlin |
packages/design-tokens | منبع مشترک توکنهای وب، اندروید و docs |
docs | همین سایت؛ نصب و build مستقل از workspace اصلی |
سه قاعدهٔ اصلی
-
همهٔ تغییرا ت از sync میگذرد. چهار موجودیت CRUD جداگانه ندارند و فقط از
/api/sync/*خوانده/نوشته میشوند. JWT مالک داده را تعیین میکند؛uidهویت رکورد است — نه شناسهٔ داخلی DB و نه شناسهٔ provider. -
سرور با
baseVersionتصمیم میگیرد. پذیرش تغییر، تعارض نسخه و شکست شبکه سه چیز متفاوتاند؛ هرmutationIdرسید مستقل خودش را دارد. تفکیک کامل اینها در قرارداد sync. -
ذخیرهسازی اتمیک و جداسازی حساب اجباری است. تغییر محلی همراه نیت آن، و صفحهٔ pull همراه cursor، باید هر کدام در یک تراکنش ذخیره شوند؛ پاسخ دیررس حساب قبلی نباید وارد حساب جدید شود. هر دو موتور اندروید و وب این قواعد را پیاده کردهاند.
برای تغییر قرارداد wire: اول packages/contracts را عوض کنید، بعد خروجیها را بازتولید کنید؛ فایل تولیدشده را دستنویس نکنید. برای تغییر DB: migration لازم است؛ db:push جایگزین triggerها و قیود sync نمیشود. ادامهٔ خواندن: روابط داده و فرمانهای پروژه.