لایهٔ داده
دادهٔ مرورگر کجا و به چه شکلی مینشیند، چه کسی حقِ خواندن و نوشتنش را دارد، پایگاه چطور ارتقا مییابد، و پشتیبانِ کامل چیست. پیِ پنلِ مدیریتِ نسلِ بعد (فاز ۹ ِ نسل پنجم، ۹.۱، ۹.۲، ۹.۷). ذخیرهٔ خودکار، دو زبانه و
.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، برای نوارِ تکهٔ اولیه |
جدولها و پاکت
Section titled “جدولها و پاکت”پایگاه 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در ارتقای نسخهٔ ۴ رفت.
ارتقا و اتصال
Section titled “ارتقا و اتصال”- ارتقا (
upgrade.ts): ردیفِ قدیمی همانطور که هست بهdataمیرود — بی سنجش و بی دور ریختن؛ ردیفِ پاکتدار دست نمیخورد. آزموده روی پایگاهِ پرشدهٔ نسخههای ۱، ۲ و ۳ (data-layer.test.ts). گامِ نسخهٔ ۶ (upgradeAuditDetail) ردیفِ شکلِ ۱ ِ دفترِ رویداد را شکلِ ۲ میکند؛ آزموده روی پایگاهِ پرشدهٔ نسخهٔ ۵ با پیامِ بیلدِ نویسنده (audit-detail.dom.test.tsx). versionchange(زبانهٔ دیگری با ساختِ تازهتر ارتقا داد): اتصال بی بازگشاییِ خودکار بسته میشود — پیشفرضِ Dexie بازگشایی را روشن میگذاشت و فراخوانیِ بعدیVersionErrorمیگرفت — و نوارِ «نسخهٔ تازه در زبانهٔ دیگر» با «بارگذاری دوباره» میآید (components/DbConnectionBar.tsx، درApp.tsx).staleبرگشت ندارد.blocked(ارتقای این زبانه منتظرِ زبانهٔ کهنه است): نوارِ «درزساز در زبانهٔ دیگری با نسخهٔ قدیمی باز است»؛ با اولین باز شدنِ موفق (ready) برمیخیزد.
دفترِ رویداد
Section titled “دفترِ رویداد”{ at, projectId?, action, detail? } با action ∈ project.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.ts)؛ deleteProject در همان
تراکنش برش میدارد. امروز یک کلید: 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— پالت ← «پشتیبان کامل» ← دانلود ← پاک کردنِ همهٔ جدولها ← «بازگرداندن از پشتیبان» ← بارگذاریِ دوباره ← چکیدهٔ همهٔ جدولها (جز دفترِ رویداد و دستگیره) برابر.
برای پنلِ مدیریت (۹.۸)
Section titled “برای پنلِ مدیریت (۹.۸)”پنلِ مدیریتِ نسلِ بعد فقط به همین لایه وصل میشود، نه به 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).
هنوز نیست
Section titled “هنوز نیست”رابطی که کاتالوگِ ذخیرهشده را به پروژه وصل کند (catalogRef.base امروز فقط رجیستریِ baseCatalogData
را میشناسد، و CatalogRepo خوانندهای جز آزمون ندارد) — کارِ پنلِ مدیریت. دفترِ رویداد در رابط فقط فهرست و
پاک کردن دارد (تنظیمات ▸ «دفترِ رویداد»)؛ فیلتر بر شناسهٔ کنش و پروژه کارِ پنلِ مدیریت است.