mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Fix documentation drift surfaced by the full-docs consistency review
This commit is contained in:
@@ -142,7 +142,7 @@ There are two ways to implement the split; pick per store:
|
||||
1. **Two object stores** (`datasets` for metadata, `dataset-payloads` keyed by the same id for `data`) — strongest separation; a `getAll` on metadata never touches payload bytes.
|
||||
2. **One store, lazy field** — keep `data` in the record but set it to `null` on the bulk list load and fetch it per-id on demand.
|
||||
|
||||
Astrolabe uses the **lazy-field** approach for datasets (one store, simpler), with `data === null` signalling "summary loaded, payload not yet."
|
||||
**Target, not yet built.** Datasets currently load in full via `loadDatasets()` (`infrastructure/dataset-store.ts`); the lazy-field approach below is the **intended direction** for when dataset size demands it, not a description of shipped code. The plan: keep `data` in the one store but set it to `null` on the bulk list load (`data === null` = "summary loaded, payload not yet") and fetch per-id on demand.
|
||||
|
||||
```ts
|
||||
// src/app/infrastructure/dataset-store.ts
|
||||
@@ -351,44 +351,46 @@ Astrolabe has three tiers with different capacities and risk profiles:
|
||||
| Tier | Backing | Holds | Budget & behavior |
|
||||
| -------------------- | -------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Snippet store** | IndexedDB `snippets` | All snippet records | Practical budget ~**5 MB**. A storage monitor estimates usage and surfaces a warning as it fills. Snippets are user-authored and irreplaceable, so we fail **loudly**. |
|
||||
| **Dataset store** | IndexedDB `datasets` | All dataset payloads | Separate, **high-capacity**; suited to large payloads. Lazily loaded (§3). |
|
||||
| **Dataset store** | IndexedDB `datasets` | All dataset payloads | Separate, **high-capacity**; suited to large payloads. Loaded in full today; lazy loading is a §3 target. |
|
||||
| **Settings & prefs** | localStorage | `UserSettings` + app/UI prefs (§5) | Small; effectively unbounded for this use. |
|
||||
|
||||
Splitting snippets and datasets into separate stores means a few large datasets can't crowd out the snippet budget, and the snippet monitor can report a meaningful "how full is my library" number without summing dataset bytes.
|
||||
|
||||
### Estimating usage
|
||||
|
||||
Use the Storage Manager API where available, with a manual byte-sum fallback for the snippet tier so the ~5 MB budget is always reportable.
|
||||
Read the origin's **Storage Manager** estimate (`navigator.storage.estimate()` → `usage`/`quota`) and derive a presentation-ready summary in the **pure core**, so the thresholds are unit-tested without a browser and the adapter only does I/O. When the API is missing or rejects, the summary is marked unusable rather than guessed.
|
||||
|
||||
```ts
|
||||
// src/app/infrastructure/storage-monitor.ts
|
||||
export interface StorageReport {
|
||||
snippetBytes: number; // estimated bytes used by the snippet tier
|
||||
snippetBudget: number; // 5 MB practical budget
|
||||
ratio: number; // snippetBytes / snippetBudget, clamped to >= 0
|
||||
warn: boolean; // ratio crossed the warning threshold
|
||||
// src/core/storage-estimate.ts — pure, unit-tested (no I/O)
|
||||
export type StorageLevel = 'ok' | 'warning' | 'critical';
|
||||
|
||||
export interface StorageSummary {
|
||||
usedBytes: number;
|
||||
quotaBytes: number;
|
||||
fraction: number; // usedBytes / quotaBytes, clamped 0..1
|
||||
level: StorageLevel; // escalates from `fraction`
|
||||
available: boolean; // false when usage/quota are missing/invalid
|
||||
}
|
||||
|
||||
const SNIPPET_BUDGET = 5 * 1024 * 1024;
|
||||
const WARN_AT = 0.8;
|
||||
export const WARNING_THRESHOLD = 0.8; // begin warning
|
||||
export const CRITICAL_THRESHOLD = 0.95; // escalate to "nearly full"
|
||||
|
||||
export async function reportSnippetUsage(snippets: Snippet[]): Promise<StorageReport> {
|
||||
// Cheap, deterministic estimate: serialize the records we hold.
|
||||
const snippetBytes = snippets.reduce((n, s) => n + new Blob([JSON.stringify(s)]).size, 0);
|
||||
const ratio = snippetBytes / SNIPPET_BUDGET;
|
||||
const report: StorageReport = {
|
||||
snippetBytes,
|
||||
snippetBudget: SNIPPET_BUDGET,
|
||||
ratio,
|
||||
warn: ratio >= WARN_AT,
|
||||
};
|
||||
if (report.warn) {
|
||||
console.warn(`[storage] snippet tier ${(ratio * 100).toFixed(0)}% of ${SNIPPET_BUDGET} bytes`);
|
||||
}
|
||||
return report;
|
||||
export function summarizeStorage(input: { usage?: number; quota?: number }): StorageSummary {
|
||||
// Clamp the fraction, derive the level, and mark `available: false` when
|
||||
// usage/quota are absent or non-finite — we don't warn on data we lack.
|
||||
}
|
||||
|
||||
// src/app/infrastructure/storage-estimate.ts — the only browser-touching part
|
||||
export async function readStorageEstimate(): Promise<StorageSummary> {
|
||||
const storage = typeof navigator !== 'undefined' ? navigator.storage : undefined;
|
||||
if (!storage || typeof storage.estimate !== 'function') return summarizeStorage({});
|
||||
const { usage, quota } = await storage.estimate();
|
||||
return summarizeStorage({ usage, quota });
|
||||
}
|
||||
```
|
||||
|
||||
> **Open divergence (spec §02 ↔ code).** Spec §02 frames the monitor as the **snippet** storage budget specifically ("datasets are stored separately with far greater capacity"). The shipped `readStorageEstimate` instead reports **whole-origin** `usage`/`quota` (snippets + datasets + everything the origin holds). Reconcile deliberately — either scope the estimate to the snippet store, or update spec §02 to describe the whole-origin reading.
|
||||
|
||||
### Fail loudly, never silently lose data
|
||||
|
||||
When a write would exceed quota, IndexedDB rejects with a `QuotaExceededError`. The adapter must **propagate** this so the UI can tell the user to export and prune — it must never swallow the error and pretend the save succeeded.
|
||||
|
||||
Reference in New Issue
Block a user