Brazen Framework - IndexedDB Storage

IndexedDB storage layer and repositories for Brazen user scripts

Este script não deve ser instalado diretamente. É uma biblioteca destinada a ser incluída por outros scripts através da diretiva de metadados // @require https://update.greasyfork.org/scripts/587030/1909462/Brazen%20Framework%20-%20IndexedDB%20Storage.js

Terá de instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

Terá de instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Terá de instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Terá de instalar uma extensão como Tampermonkey para instalar este script.

Terá de instalar uma extensão de gestão de scripts de utilizador para instalar este script.

(Já tenho um gestor de scripts de utilizador, deixe-me instalá-lo!)

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

(Já tenho um gestor de estilos de utilizador, deixe-me instalá-lo!)

Autor
brazenvoid
Versão
2.0.0
Criado
14/07/2026
Atualizado
22/08/2026
Tamanho
200 KB
Licença
GPL-3.0-only

Brazen Framework — IndexedDB Storage (developer guide)

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.

Version numbers (three independent constants)

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.jsonversion). 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.


When to use

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)

Quick start (script setup hook)

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).


Architecture

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)

Database stores (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.

Tag registry and isDiscovered

Each 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; truenull; 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.

Download manager state (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.


Public surface (via Configuration Manager)

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.


Ruleset pipeline

  1. Register fields — Configuration Manager addRulesetField with a template from RulesetTemplateRegistry (plain-line, default-tag, tag-blacklist/explore/ignore, substitution, bookmarks, …).
  2. Write rowsrulesetEntries 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.
  3. CompilecompileRulesetField(repos, fieldKey) → combo maps on the field document (getComplianceSpecForField, download ignore/substitution maps). RulesetFieldRepository.getCompiledField caches on revisionId + config revision stamp.
  4. Panel helpersRulesetEntryRepository.countForField(fieldKey, { query?, groupQuery? }) for header counts; listAllForField walks the full field (no 200-row truncation).
  5. ConflictslistRulesetRowConflicts / resolveRulesetFieldConflicts remove paired-field rows (e.g. filename ignore ↔ substitution subject) before interactive writes.
  6. OR in new rules — not stored; legacy | 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.


Migration progress

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 / restore

  • User export (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 safety export: separate zip with manifest version 3 (PRE_MIGRATION_BACKUP_VERSION) for downgrade/rollback before destructive migration — not the same as the live IDB schema version.
  • Import: current v5 zip; v3 IDB zip; v2 driver bundle; v1 flat JSON — handled by Configuration Manager adapters.
  • Ledger restore: merge by postId (newer claimedAt wins).

Cross-tab sync

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).


Related modules

  • Configuration Manager — field schema, initialize/save, backup UI
  • UtilitiescoerceBookmarkArray, reviveGmStoredValue, objectFromJSON (legacy GM clone revival)
  • Framework coreasync init(), IDB-blocked banner, compliance after compile