IndexedDB storage layer and repositories for Brazen user scripts
Hindi dapat direktang i-install ang script na ito. Ito ay isang library para sa iba pang mga script na isasama sa meta directive. // @require https://update.greasyfork.org/scripts/587030/1909462/Brazen%20Framework%20-%20IndexedDB%20Storage.js
Required for latest-stack apps that use IndexedDB persistence through Configuration Manager. One IndexedDB database per script (scriptPrefix with trailing dash stripped, e.g. brazen-r34xxx). Replaces localStorage/GM aggregate blobs for settings, tags, bookmarks, and download ledger.
Greasy Fork: IndexedDB Storage · Requires: Utilities · Load before: Configuration Manager
Apps do not open the database directly. Use BrazenConfigurationManager.initialize(), getRepos(), and field APIs wired by the configuration manager.
Do not conflate these — they change on different schedules:
| Constant | Current | What it versions |
|---|---|---|
IDB_SCHEMA_VERSION |
12 | IndexedDB on-disk layout (object stores, indexes, onupgradeneeded migrations). Exposed on meta.schemaVersion. Internal milestones (v4 queue indexes, v7 legacy store drops, v9/v10 ruleset defaults, v11 fieldKey_rawLine, v12 processor-field cleanup) are upgrade steps, not the backup format. |
CONFIG_BACKUP_VERSION |
5 | Toolbox / backup() zip export manifest (manifest.json → version). Includes ruleset stores, chunked large tables, etc. Restore accepts v5 and v3 IDB zips (see Configuration Manager). |
PRE_MIGRATION_BACKUP_VERSION |
3 | Pre-migration safety zip only (createPreMigrationSafetyBackup) — deliberately kept at v3 so a downgrade + restore path stays compatible; may still include legacy tagRules / tagRuleSets when present. |
User-facing “schema v12” means IDB_SCHEMA_VERSION. “Backup v3” in older notes refers to the safety-export manifest, not the live database schema.
| Scenario | This module |
|---|---|
New Brazen app with async initialize() + IDB persistence |
Yes — @require before Configuration Manager |
| Legacy app on driver-only Configuration Manager 3.x | No — keep local/GM drivers until migrated |
| IDB unavailable (private mode, blocked storage) | Initialization throws IDB_UNAVAILABLE; Framework shows fatal panel — script does not run |
| Installed DB schema newer than this module supports | getSchemaVersionConflict() returns { installed, supported }; Framework shows schema-too-new panel (upgrade script or reset database) |
Seed script-specific data during first-time setup via setScriptSetup() on the configuration manager:
this._configurationManager.setScriptSetup(async (repos, cm) => {
// Seed apis / tagTypes documents (example)
let apis = await repos.storage.get(IDB_STORE_APIS, 'apis')
if (!apis?.entries?.length) {
await repos.storage.put(IDB_STORE_APIS, {
id: 'apis',
entries: [{ entryId: 1, name: 'gelbooru', label: 'Gelbooru' }],
})
}
})
Legacy local/GM settings and bookmarks are imported automatically during setup by BrazenLegacyImporter when present. Download ledger is IndexedDB-only (no GM ledger import).
BrazenConfigurationManager
└── BrazenStorageRepositories(scriptPrefix, onRepositoryChange?, onRevisionBump?)
├── storage → BrazenIndexedDBStorage (open, close, CRUD, health, zip)
├── meta → MetaRepository (revision, domain seq, setup lock, entryId counters)
├── settings → SettingsRepository (non-tag field blobs)
├── tags → TagRepository (identity registry)
├── tagRuntime → TagRuntime (bounded session cache + compile helpers)
├── rulesetFields / rulesetEntries → ruleset compile + panel rows
├── bookmarks → BookmarkRepository (setup/import legacy store only)
├── ledger → LedgerRepository
├── downloadResolutionQueue → DownloadResolutionQueueRepository
├── downloadQueue → DownloadQueueRepository
├── downloadManagerState → DownloadManagerStateRepository
└── download → DownloadCoordinatorRepository (coordinator commit APIs)
IDB_SCHEMA_VERSION 12)| Store | Shape | Role |
|---|---|---|
meta |
singleton | revisionId, domainConfigSeq, domainTagsSeq, domainLedgerSeq, setup flags, per-store next*EntryId counters, rulesetMigrated, bookmarksMigrated, pending migration flags |
settings |
singleton document | Non-tag config fields (camelCase properties) |
apis |
singleton + entries[] |
Site/API entities; Gelbooru rows may carry querySyntaxKeys[] for ruleset colon routing |
tagTypes |
singleton + entries[] |
Canonical and alias tag types |
tags |
row per tag | Append-only registry (name immutable; typeEntryId; isDiscovered) |
rulesetFields |
row per field key | Template id + panel config; compiled output cached on field document |
rulesetEntries |
row per rule | Raw lines + metadata; compiled by compileRulesetField; indexes include fieldKey_rawLine (native dedup) |
bookmarks |
row per bookmark | Legacy store — runtime bookmarks live in rulesetEntries (bookmarks template) after meta.bookmarksMigrated |
ledgerEntries |
row per post id | Download duplicate ledger (postId unique) |
downloadResolutionQueue |
row per itemId |
Resolution pipeline (status, status_addedAt indexes; includes discoveryQueued) |
downloadQueue |
row per itemId |
Download pipeline (status, status_addedAt indexes) |
downloadManagerState |
singleton id: 'state' |
Shared DM flags, discovery panel, human-interaction lanes, processor tab — see createDefaultDownloadManagerState() |
Legacy tagRules / tagRuleSets stores are dropped at schema v7; compliance and download policy are ruleset-only.
isDiscoveredEach tags row is a TagEntry: { entryId, name, typeEntryId?, isDiscovered?, meta }. Compliance and download policy live in rulesetEntries, not on the tag row.
isDiscovered |
Meaning |
|---|---|
null (default) |
Undiscovered — tag discovery treats the tag as unknown |
true |
Discovered — confirmed in the discovery panel or discovery-off resolution |
Writers of isDiscovered: true: TagRuntime.ensureTag with source: 'discovery-confirm' or 'resolution'; one-time runPendingIsDiscoveredBackfill for legacy typed rows.
Does not set discovered: attribute toggles (may set typeEntryId only), register-on-seen (source: 'media' / 'sidebar').
Bulk undo: TagRepository.resetAllTagsDiscovered(onProgress?) — chunked cursor walk; true → null; types and rulesets unchanged; bumps revision when any row changed. Called from Configuration Manager / Framework Toolbox Reset Tag Discovery.
TagRuntime (session cache): warmCache() marks ready without loading all tags. Use ensureNames, ensureEntryIds, ensureComplianceLookups, and registerTypedTagGroups for bounded LRU loads (TAG_RUNTIME_CACHE_MAX). Tag writes can batch via MetaRepository.beginTagsRevisionBatch / endTagsRevisionBatch so bulk registration emits one tags.revision bump.
createDefaultDownloadManagerState)Singleton document id: 'state' in downloadManagerState:
| Field | Role |
|---|---|
paused |
Download queue paused (default true) |
resolutionBlocked / resolutionBlockedItemId |
Tag discovery gate |
humanInteraction |
Per-lane { resolution, download } — each null or { itemId, promptTabId, openUrl, at } (Download Manager migrates legacy single-slot rows) |
processingTabId / processingHeartbeatAt |
Processor tab + heartbeat |
lastResolutionInitiationAt / lastDownloadInitiationAt |
Initiation gap clocks |
tagDiscoveryEnabled / tagDiscoveryPanelTabId |
Discovery toggle + panel owner tab |
discoveryPanelTags / discoveryPanelKnownTags |
Tag discovery panel payloads |
discoveryReviewMode |
'unknown' \ |
discoveryLanePhaseActive |
When true, processor prefers discoveryQueued resolution rows |
completedResolutionCount / completedDownloadCount |
Dock progress totals |
Download queue stores do not bump meta.revisionId (avoids wiping unsaved settings edits on every processor step).
For large pending queues, prefer repository hot paths over listAll():
| Method | Role |
|---|---|
countActive() |
Non-terminal count via status index (no fat row bodies) |
listActiveItemIds() |
Primary keys for active statuses only |
peekNextQueued() |
Oldest queued row by addedAt (processor claim) |
peekNextDiscoveryQueued() |
Oldest discoveryQueued row (deferred discovery phase) |
Coordinator writes (repos.download): Download Manager routes enqueue/dequeue/claim/commit through DownloadCoordinatorRepository. A short dequeue tombstone (30s) prevents in-flight work from resurrecting rows the user deselected.
| CM method | Role |
|---|---|
async initialize() |
Open DB, safety backup when migration needed, setup/backfill/ruleset migration with onMigrationProgress; throws on failure |
createPreMigrationSafetyBackup() |
Auto-download pre-migration safety zip (PRE_MIGRATION_BACKUP_VERSION 3 — legacy rollback shape) before destructive migration |
getPreMigrationBackupFilenameHint() |
Filename from sessionStorage for failure UI |
getMigrationError() |
Last migration failure (when initialize threw) |
getRepos() |
BrazenStorageRepositories instance |
isStorageReady() / isIndexedDBAvailable() / canPersist() |
Runtime gates (canPersist === storage ready after init) |
toggleTagRule(fieldKey, tagName) |
Sidebar-style sole-tag toggle (writes rulesetEntries, recompiles) |
clearTagRules(fieldKey) |
Clear all rules in a ruleset field |
ensureRulesetFieldsCompiled(fieldKeys?) |
Compile registered ruleset fields |
removeTagRuleByLabel(fieldKey, ruleLabel) |
Delete matching ruleset row (Active Hide Rules) |
async save() / async backup() / async restore() |
Persistence + zip bundle (CONFIG_BACKUP_VERSION 5; restore also accepts v3 IDB zips and v2/v1 legacy JSON) |
Direct repository use (advanced, e.g. bookmark widget per-op):
let repos = this._configurationManager.getRepos()
await repos.bookmarks.add({ label: '…', tags: '…', url: '…', sortOrder: 0 })
let rows = await repos.bookmarks.listAll()
Schema probes (advanced): repos.storage.getInstalledSchemaVersion(), getSchemaVersionConflict(), BrazenIndexedDBStorage.isVersionError(error) — used before open when diagnosing schema-too-new failures.
addRulesetField with a template from RulesetTemplateRegistry (plain-line, default-tag, tag-blacklist/explore/ignore, substitution, bookmarks, …).rulesetEntries CRUD, sidebar toggle, backup import, or migrateRulesetFromLegacy. New rows dedupe in RulesetEntryRepository.add by trimmed rawLine within the field (case-sensitive; first wins). findByRawLine(fieldKey, rawLine) uses the fieldKey_rawLine index.compileRulesetField(repos, fieldKey) → combo maps on the field document (getComplianceSpecForField, download ignore/substitution maps). RulesetFieldRepository.getCompiledField caches on revisionId + config revision stamp.RulesetEntryRepository.countForField(fieldKey, { query?, groupQuery? }) for header counts; listAllForField walks the full field (no 200-row truncation).listRulesetRowConflicts / resolveRulesetFieldConflicts remove paired-field rows (e.g. filename ignore ↔ substitution subject) before interactive writes.| lines expand on import via expandOrRuleLine().Gelbooru query syntax: repos.getQuerySyntaxKeys() / getQuerySyntaxKeysSync() supply colon-routing keys from the apis document (seeded by site adapters). Ruleset warm/render uses these for operator/colon token lines.
Default tag operators (optional): templates may opt into DEFAULT_RULESET_TAG_OPERATORS for Gelbooru-style prefix/suffix operators in ruleset coloring.
Long-running setup/migration steps report structured progress via CM onMigrationProgress:
{ phase: 'ruleset-migration', label: 'Migrating tag rules…', detail: 'Scanning tag registry', current: 1200, total: 8500 }
Helpers: normalizeMigrationProgress, reportMigrationProgress. migrateRulesetFromLegacy, runPendingIsDiscoveredBackfill, and migrateRulesetUserConfigPostOpen (schema v9/v10 ruleset defaults) yield between chunks. MetaRepository.beginSetup records setupStartedAt and clears stale setupInProgress locks after five minutes.
backup()): zip bundle — manifest.json with CONFIG_BACKUP_VERSION (5); one JSON file per store (rulesetFields, rulesetEntries, tags, ledger, …) + meta.json / settings.json / apis.json / tagTypes.json. STORE method with IEEE CRC-32 per entry. Large stores may split into store.partNNNN.json sidecars inside the zip.PRE_MIGRATION_BACKUP_VERSION) for downgrade/rollback before destructive migration — not the same as the live IDB schema version.postId (newer claimedAt wins).meta.revisionId bumps on writes; bumpRevision() also increments domainConfigSeq (and domainTagsSeq when {tagsTouched:true}) or domainLedgerSeq (when {source:'ledger'}). Tag-only registry writes advance domainTagsSeq only when {configTouched:false} or {tagsTouched:true} without config. Configuration Manager listens to visibilitychange only (no BroadcastChannel) and reloads only the domains whose seq counters differ from local synced cursors. Same-tab focus after the script’s own writes is a no-op.
Download Manager may call repos.storage.close() on unload so a bfcache'd page cannot pin the database; the next CRUD path calls open() again (concurrent opens are coalesced).
coerceBookmarkArray, reviveGmStoredValue, objectFromJSON (legacy GM clone revival)async init(), IDB-blocked banner, compliance after compile