Skip to content

لایهٔ داده

This content is not available in your language yet.

دادهٔ مرورگر کجا و به چه شکلی می‌نشیند، چه کسی حقِ خواندن و نوشتنش را دارد، پایگاه چطور ارتقا می‌یابد، و پشتیبانِ کامل چیست. پیِ پنلِ مدیریتِ نسلِ بعد (فاز ۹ ِ نسل پنجم، ۹.۱، ۹.۲، ۹.۷). ذخیرهٔ خودکار، دو زبانه و .darz: storage.md. مدل و مهاجرتِ پرونده: data-model.md.

apps/web/src/data/ تنها جایی است که dexie وارد می‌شود — no-restricted-imports در eslint.config.js (آزمون: scripts/test/eslint-dexie.test.mjs). جزء، قلاب و خدمت از مخزن‌ها می‌خوانند؛ مخزن پاکتِ ردیف را با zod در نوشتن و خواندن می‌سنجد.

پرونده مخزن / کار
database.ts DarzDb (نسخه‌های Dexie)، db()، useDb() برای آزمون، versionchange و blocked
rows.ts اسکیمای zod ِ ردیفِ هر جدول و ROW_VERSION
upgrade.ts ارتقای نسخهٔ ۴: ردیفِ بی نسخه ← پاکت
projects.ts ProjectRepo: putProject، getProject، getRawProject، listProjects، deleteProject، replaceStoredProject
snapshots.ts SnapshotRepo: addSnapshot، listSnapshots (۳۰ تا برای هر پروژه)
settings.ts SettingsRepo: رجیستریِ تیپ‌دار — getSetting(key, fallback)، setSetting(key, value)؛ ردیفِ هر پروژه project:<id>getProjectSettings، updateProjectSettings
price-books.ts PriceBookRepo: listPriceBooks، putPriceBook، deletePriceBook (priceBook ِ هسته)
file-handles.ts FileHandleRepo: دستگیرهٔ پروندهٔ متصل
audit-log.ts AuditLog: appendAudit (درونِ تراکنشِ همان کار)، listAudit، clearAudit، auditProjectNames
assets.ts AssetRepo: putAssets، getAssets، flushAssets، holdAssets، unusedAssets، freeUnusedAssets
catalogs.ts CatalogRepo: putCatalog، getCatalog، listCatalogs، deleteCatalog
legacy.ts آوردنِ یک‌بارهٔ پایگاهِ نسلِ قبل
backup.ts پشتیبانِ کامل و بازگردانی (.darzsaz-backup)
connection.ts وضعیتِ اتصال (open/blocked/stale) — بی Dexie، برای نوارِ تکهٔ اولیه

پایگاه darzsaz-v3، نسخهٔ ۶ ِ Dexie — نسخهٔ ۴ همان «پایگاهِ نسخهٔ ۳» ِ پلن بود (عددِ Dexie یکی جلوتر است چون fileHandles در فاز ۱ نسخهٔ ۳ را گرفت)، نسخهٔ ۵ تصویر و کاتالوگ را آورد (پایینِ همین سند)، و نسخهٔ ۶ ردیفِ دفترِ رویداد را شکلِ ۲ کرد (شرحِ تیپ‌دار به‌جای پیام، «دفترِ رویداد» ِ پایین).

جدول کلید و نمایه data از نسخه
projects id، updatedAt { name, project } ۱
snapshots ++id، projectId، at { project, reason? } ۱
settings key مقدارِ همان کلید ۱
priceBooks id، capturedAt { name, vendor, city, book } ۲
fileHandles projectId { handle } ۳
auditLog ++id، at، projectId? { action, detail? } ۴
assets id { mime, blob, bytes } ۵
catalogs id { name, base, version, catalog } ۵

هر ردیف { ...ستون‌های کلید, schemaVersion, data } است. چرا ستونِ کلید بیرونِ data: IndexedDB کلیدِ اصلی را عوض نمی‌کند و نمایهٔ data.x روی ردیفِ پیش از ارتقا تهی می‌ماند؛ مخزن ستون‌ها را هر بار از خودِ داده می‌نویسد. schemaVersion شکلِ data ِ همان جدول است (امروز ۱، و دفترِ رویداد ۲)؛ تغییرِ شکل = نسخهٔ تازهٔ Dexie با گامِ upgrade که بالا می‌بردش.

  • پروژه در پاکت فقط «شیء» سنجیده می‌شود. اسکیمای پروژه مالِ هسته است: checkProject در نوشتن، loadProject ِ مصرف‌کننده در خواندن. ردیفی که پاکتش هم خراب است «آسیب‌دیده» با «دانلود خام» ِ کلِ ردیف است، نه «نیست».
  • فهرستِ نسخه‌ها، دفترها و رویدادها ردیفی را که پاکتش سنجش را رد کند نشان نمی‌دهند.
  • تنظیم فقط کلیدِ رجیستری (legacyMigrated، storageFragileWarned)؛ کلیدِ دیگر کامپایل نمی‌شود، مقدارِ بدشکل در خواندن پیش‌فرض است و در نوشتن پرتاب می‌کند. ترجیحِ رابط (پوسته، زبان) در localStorage ِ state/ui.ts است تا نخستین رنگ‌آمیزی پیش از باز شدنِ پایگاه درست باشد.
  • بندانگشتیِ خانه از خودِ پروژه ساخته می‌شود؛ ستونِ thumbnail در ارتقای نسخهٔ ۴ رفت.
  • ارتقا (upgrade.ts): ردیفِ قدیمی همان‌طور که هست به data می‌رود — بی سنجش و بی دور ریختن؛ ردیفِ پاکت‌دار دست نمی‌خورد. آزموده روی پایگاهِ پرشدهٔ نسخه‌های ۱، ۲ و ۳ (data-layer.test.ts). گامِ نسخهٔ ۶ (upgradeAuditDetail) ردیفِ شکلِ ۱ ِ دفترِ رویداد را شکلِ ۲ می‌کند؛ آزموده روی پایگاهِ پرشدهٔ نسخهٔ ۵ با پیامِ بیلدِ نویسنده (audit-detail.dom.test.tsx).
  • versionchange (زبانهٔ دیگری با ساختِ تازه‌تر ارتقا داد): اتصال بی بازگشاییِ خودکار بسته می‌شود — پیش‌فرضِ Dexie بازگشایی را روشن می‌گذاشت و فراخوانیِ بعدی VersionError می‌گرفت — و نوارِ «نسخهٔ تازه در زبانهٔ دیگر» با «بارگذاری دوباره» می‌آید (components/DbConnectionBar.tsx، در App.tsx). stale برگشت ندارد.
  • blocked (ارتقای این زبانه منتظرِ زبانهٔ کهنه است): نوارِ «درزساز در زبانهٔ دیگری با نسخهٔ قدیمی باز است»؛ با اولین باز شدنِ موفق (ready) برمی‌خیزد.

{ at, projectId?, action, detail? } با actionproject.save، project.replace، project.delete، backup.restore (detail: { createdAt, app } ِ پشتیبان)، assets.prune (آزاد کردنِ تصویرهای بی‌استفاده؛ detail: { count, bytes }). رویداد در همان تراکنشِ کار نوشته می‌شود. ذخیره‌های پیاپیِ یک پروژه در پنجرهٔ ۵ دقیقه یک ردیف‌اند که زمانش جلو می‌رود؛ دفتر آخرین ۱۰۰۰ ردیف را نگه می‌دارد.

شرح داده است، نه پیام (شکلِ ۲ ِ ردیف، schemaVersion: 2، نسخهٔ ۶ ِ Dexie). شکلِ ۱ message: Msg داشت و در بیلدِ تولیدی ref.id ِ آن شناسهٔ کوتاهِ همان بیلد بود (i18n.md، «شناسه داده نیست»): پس از بیلدی که پیامی بیفزاید ردیفِ دیروز متنِ پیامِ همسایه‌اش را نشان می‌داد. پیامِ شرح حالا در نمایش ساخته می‌شود (AuditLog.tsx، با شناسهٔ بیلدی که نشانش می‌دهد). ارتقا و بازگردانیِ پشتیبانِ کهنه ردیفِ شکلِ ۱ را با auditFromV1 (rows.ts) شکلِ ۲ می‌کنند — شرح از شکلِ مقدارها (moment/text؛ count ِ کیلوبایت یا decimal ِ مگابایت)، نه از شناسه — و پیام در هیچ حالتی نمی‌ماند. اسکیمای شرح شیءِ سخت است: کلیدِ ناشناخته (message) رد می‌شود، نه بی‌صدا دور ریخته، چون بازگردانی ردیف را خام می‌نویسد. نگهبان: test/rows-no-message.test.ts (هیچ جای مقدارِ هیچ اسکیمای ردیف پیام نمی‌پذیرد، و تیپِ هیچ ردیف Msg ندارد).

کاربر: تنظیمات ▸ دفترِ رویداد ▸ نشان دادنِ دفتر (components/settings/AuditLog.tsx) — تازه‌ترین بالا، زمان با fmt.moment، نامِ پروژه داده (<bdi>؛ پروژهٔ حذف‌شده «پروژهٔ حذف‌شده»)، نامِ کنش و شرح پیام؛ «پاک کردنِ دفتر…» با ConfirmDialog ِ کیت (danger). دفتر با کلیک خوانده می‌شود، نه با باز شدنِ تنظیمات. آزمون: audit-log.dom.test.tsx، audit-detail.dom.test.tsx (ردیفِ بیلدِ A در بیلدِ B همان متن؛ پایگاهِ نسخهٔ ۵ و پشتیبانِ قالبِ ۲ با ردیفِ شکلِ ۱).

ردیفِ ترجیح‌های هر پروژه

Section titled “ردیفِ ترجیح‌های هر پروژه”

settings با کلیدِ project:<id> و اسکیمای خودش (PROJECT_SETTINGS در settings.tsdeleteProject در همان تراکنش برش می‌دارد. امروز یک کلید: reprintNoticeDone — اعلانِ «پروژه به نسخهٔ تازه ارتقا یافت؛ … نقشهٔ برش را دوباره چاپ کن» (components/ReprintNotice.tsx، سوار در ProjectSession) برای پروژه‌ای با meta.migratedFrom و پیشرفتِ کارگاه، تا پاسخ («مرکز چاپ» یا «دیدم»). درونِ سند نیست: پاسخِ این مرورگر است، نه طرح — با .darz نمی‌رود و در تاریخچهٔ واگرد نمی‌نشیند. «مرکز چاپ» درخواستِ lib/print-request.ts است که ویرایشگر می‌خواند.

پشتیبانِ کامل — .darzsaz-backup

Section titled “پشتیبانِ کامل — .darzsaz-backup”

کاربر: پرونده ▸ پشتیبان کامل و پرونده ▸ بازگرداندن از پشتیبان… (app/data-commands.ts، lib/persistence/backup-actions.ts). تأییدِ بازگردانی پنجرهٔ کیت است، نه window.confirm: فرمان پرونده را برمی‌گزیند و در lib/persistence/restore-request.ts می‌گذارد، components/RestoreConfirm.tsx (تنبل؛ RestoreGate ِ App.tsx فقط وقتی پرونده‌ای در useRestoreRequest هست سوارش می‌کند — نه در تکهٔ اولیه) می‌پرسد و با «جایگزین کن» خدمت را تنبل بار می‌کند؛ تمرکز روی «انصراف».

zip با manifest.json (format: 'darzsaz-backup'، formatVersion: 3، app، createdAt، counts، skipped) و projects.json، snapshots.json، priceBooks.json، settings.json، auditLog.json — ردیف‌ها همان‌طور که نشسته‌اند، با پاکت — و تصویرها: assets.json (ستون‌ها) و هر بلاب عضوِ assets/<id>.<ext>.

  • نه در پشتیبان: دستگیرهٔ پروندهٔ متصل (بیرون از همین مرورگر معنایی ندارد) و جدولِ catalogs (CatalogRepo؛ بازگردانی هم دستش نمی‌زند). ردیفی که پاکتش سنجش را رد کند نمی‌رود و در skipped شمرده و به کاربر گفته می‌شود — پشتیبانِ ساخته‌شده همیشه بازگرداندنی است.

  • بازگردانی جایگزین است، همه یا هیچ: اندازهٔ پرونده (۵۱۲ مگابایت) پیش از باز کردنِ zip (confirmRestore کلِ پرونده را پیش‌تر خوانده) و اندازهٔ اعلام‌شدهٔ اعضا (۱ گیگابایت) پیش از باز کردن؛ formatVersion ِ تازه‌تر backup/newer (قالبِ ۳ دفترِ رویدادِ شکلِ ۲ دارد: بیلدِ پیشین «تازه‌تر» می‌گوید، نه «بخشِ خراب»؛ ردیفِ شکلِ ۱ ِ قالبِ ۱ و ۲ پیش از سنجش شکلِ ۲ می‌شود)؛ هر جدول با اسکیمایش (backup/bad-table) پیش از دست زدن به پایگاه؛ بعد پاک کردن و نوشتنِ همهٔ جدول‌ها، حذفِ دستگیره‌ی پروژه‌های رفته و رویدادِ backup.restore در یک تراکنش. پس از آن صفحه از نو بار می‌شود و زبانه‌های دیگر «جایگزین شد» ِ هر پروژه را می‌گیرند — نسخهٔ حافظهٔ ویرایشگر روی دادهٔ بازگردانده نمی‌نشیند.

  • آزمون: پشتیبان ← پاک کردنِ پایگاه ← بازگردانی ← چکیدهٔ همهٔ ردیف‌ها برابر (data-layer.test.ts).

  • آزمونِ راهِ کاربر: e2e/backup.spec.ts — پالت ← «پشتیبان کامل» ← دانلود ← پاک کردنِ همهٔ جدول‌ها ← «بازگرداندن از پشتیبان» ← بارگذاریِ دوباره ← چکیدهٔ همهٔ جدول‌ها (جز دفترِ رویداد و دستگیره) برابر.

پنلِ مدیریتِ نسلِ بعد فقط به همین لایه وصل می‌شود، نه به Dexie و نه به فروشگاهِ ویرایشگر:

موجودیت منبعِ حقیقت مخزن / خدمت شکلِ سنجیده
پروژه پروندهٔ نسخهٔ ۴ (core/io) ProjectRepo، loadProject/checkProject projectV4 + پاکت
نسخهٔ خودکار همان پروژه در لحظه SnapshotRepo پاکت، پروژه با هسته
دفترِ قیمت priceBook ِ هسته، با capturedAt PriceBookRepo پاکت + priceBook
تنظیم رجیستریِ کلیدِ تیپ‌دار SettingsRepo zod ِ هر کلید
رویداد { at, projectId?, action, detail? } AuditLog (listAudit) پاکت، شرحِ تیپ‌دار
پروندهٔ متصل دستگیرهٔ File System Access FileHandleRepo پاکت
کاتالوگِ پایه رجیستریِ baseCatalogData ِ هسته catalogRef { base, version } ِ پروژه parseCatalogData
تصویر بلابِ عکس و رندر با شناسهٔ محتوا (io/assets.ts ِ هسته) AssetRepo پاکت + شناسه از محتوا
کاتالوگِ ذخیره‌شده { name, base, version, catalog } CatalogRepo پاکت + parseCatalogData
  • اجراکنندهٔ مهاجرت دو تاست و جدا: پرونده با loadProject (هسته؛ هر نسخه با اسکیمای خودش، گامِ MIGRATIONS[n]، سنجشِ دوباره با اسکیمای آخر، مهرِ meta.migratedFrom)، پایگاه با نسخه‌های Dexie و گامِ upgrade (database.ts). ردیفِ پروژه در پایگاه به شکلی که نوشته شد می‌ماند و در خواندن مهاجرت می‌خورد؛ پنل برای «چند پروژه هنوز نسخهٔ ۲‌اند» getRawProject می‌خواند.
  • رویدادها برای فیلترِ پنل: AUDIT_ACTIONS در rows.ts؛ کنشِ تازه = مدخلِ تازه در همان فهرست، نه رشتهٔ آزاد.
  • شناسهٔ قطعه برای کارگاه و QR معنایی و پایدار است (data-model.md)؛ پنلی که پیشرفت را گزارش می‌کند کلیدِ workshop را مستقیم به قطعه می‌رساند.

تصویر و کاتالوگ (نسخهٔ ۵ ِ Dexie)

Section titled “تصویر و کاتالوگ (نسخهٔ ۵ ِ Dexie)”
  • AssetRepo (data/assets.ts، جدولِ assets): عکسِ دیوار و رندر، هر کدام یک Blob با شناسهٔ محتوا و { mime, bytes }. putAssets تصویر را اول در «نوشته‌نشده» می‌گذارد و نوشتن را پشتِ زنجیرِ قبلی؛ writeProject و addSnapshot پیش از سند flushAssets را صبر می‌کنند — سندی که به تصویری ارجاع دارد هرگز پیش از تصویر نوشته نمی‌شود و نوشتنِ ناموفقِ تصویر ذخیره را هم می‌اندازد. رابط از lib/persistence/assets.ts می‌خواند (useAssetUrl، keepImage، openStoredProject)، هر تصویر یک data URL ِ یک‌باره در حافظه.
  • تصویر هرگز خودکار پاک نمی‌شود (تصمیمِ مالک). deleteProject تصویرهای پروژه را در جدول می‌گذارد: پاک‌سازیِ خودکارِ پیشین در همان تراکنش قدمِ واگردِ زبانهٔ دیگر و تصویرِ پروژه‌ای را که هنوز ذخیره نشده بود هم برمی‌داشت، و .darz و پشتیبانِ بعدیِ آن پروژه بی تصویر ساخته می‌شد. unusedAssets فقط می‌خواند: تصویری که هیچ ردیفِ پروژه، هیچ نسخهٔ خودکار، هیچ تصویرِ نوشته‌نشده و هیچ قدمِ واگردِ همین زبانه (holdAssets) به آن ارجاع ندارد، با بایتِ ستونِ bytes (نه بلاب). freeUnusedAssets همان فهرست را درونِ تراکنشِ خودش از نو می‌سازد — میانِ شمردن و تأیید ذخیره‌ای ممکن است دوباره ارجاع داده باشد — پاک می‌کند و رویدادِ assets.prune می‌نویسد؛ اگر چیزی نرفت، رویدادی نه.
  • کاربر: تنظیمات ▸ تصویرهای بی‌استفاده ▸ شمردن (components/settings/UnusedImages.tsx) — «۳ تصویر، ۲٫۴ مگابایت» (زیرِ یک مگابایت کیلوبایتِ رو به بالا)، با کلیک و نه با باز شدنِ تنظیمات، چون هر پروژه و نسخهٔ خودکار را می‌خواند. «آزاد کردن…» پنجرهٔ ConfirmDialog ِ کیت است (danger، تمرکز روی «انصراف») و می‌گوید پروژه‌ای که در زبانهٔ دیگری باز است را اول ببند: واگردِ آنجا از این زبانه دیده نمی‌شود. جزء از countUnusedImages و freeUnusedImages ِ lib/persistence/assets.ts می‌خواند، نه مستقیم از مخزن: ثبتِ holdAssets ِ واگردِ این زبانه در همان ماژول است و جزئی که فقط مخزن را وارد می‌کرد رندرِ واگردشدنی را بی‌استفاده می‌شمرد.
  • هر راهِ بیرون رفتن تصویر را دارد: «ذخیره در پرونده»، پروندهٔ متصل و نجاتِ صفحهٔ خطا با projectAssets (حافظهٔ نوشته‌نشده و جدول) در .darz؛ پشتیبانِ کامل همهٔ ردیف‌های جدول؛ جلدِ برگهٔ چاپ با loadAssets پیش از coverOf (پیش‌نما، چاپ، دانلود و «فرستادن…» ِ مرکز چاپ، «خروجی‌ها»، «چاپ»). آزمون: assets-repo.test.ts (حذف ← جدول، .darz، پشتیبان و آوردن در پایگاهِ خالی؛ فهرستِ از نو در تراکنش)، export-images.test.ts، print-share.dom.test.tsx، unused-images.dom.test.tsx.
  • پشتیبان (از قالبِ ۲): هر بلاب عضوِ assets/<id>.<ext> و ستون‌هایش در assets.json؛ عضوی که با شناسه‌اش نمی‌خواند backup/bad-table. پشتیبانِ قالبِ ۱ (بی تصویرِ بیرونی) هنوز بازمی‌گردد.
  • CatalogRepo (data/catalogs.ts، جدولِ catalogs): { id, name, base, version, catalog }؛ نوشتن با parseCatalogData ِ هسته. catalogFor پایه را از catalogRef.base ِ پروژه برمی‌دارد (catalog.md).

رابطی که کاتالوگِ ذخیره‌شده را به پروژه وصل کند (catalogRef.base امروز فقط رجیستریِ baseCatalogData را می‌شناسد، و CatalogRepo خواننده‌ای جز آزمون ندارد) — کارِ پنلِ مدیریت. دفترِ رویداد در رابط فقط فهرست و پاک کردن دارد (تنظیمات ▸ «دفترِ رویداد»)؛ فیلتر بر شناسهٔ کنش و پروژه کارِ پنلِ مدیریت است.