API / @aionbuilders/nabu

createPersistedEditor

Function Stability: stable

Create a Nabu editor instance with automatic IndexedDB persistence.

Related guide: Persistence and sync →

Async counterpart of createEditor() — same { preset, extensions } signature, extended with a required docId for storage.

On first call, the document is empty (only the default paragraph from ParagraphExtension). On subsequent calls, the document state is restored from the last saved snapshot.

The bytes of the files (images, videos…) are NOT saved in the snapshot (ADR 014): they go to an IndexedDBAssetStore (base nabu, object store assets, shared by every document of the origin), unless files.store provides another one. The document only keeps references and a small metadata index; the store also records which files each document references. With a host (files.onBeforePersist) the uploads still to do are listed in an IndexedDBOutbox (object store outbox, unless files.outbox provides another one): an upload interrupted by a closed tab RESUMES when the document is opened again.

History (ADR 014 §3.6): the default history: 'full' NEVER compacts or trims anything (the whole evolution of the document is kept and saved, whatever the number of sessions). history: { mode: 'compact', retain, onBeforeCompact } opts in to a compaction when the document is OPENED (after loading, migration and upload reconciliation; never mid-session): history older than retain is dropped and the files only that history referenced are released. See CompactOptions and nabu.compact().

Timestamps of the changes are recorded (doc.setRecordTimestamp(true)) so a retention window can be honoured.

Robustness (ADR 010, theme 8): the autosave also happens at most every maxWait ms during a continuous burst, and immediately when the page is hidden (visibilitychange → hidden, pagehide). Every save keeps the snapshot it replaces as a BACKUP. A stored snapshot that cannot be read is moved aside (never deleted: the quarantine object store), the backup is tried, else the editor starts empty — it always boots, and nabu.persistence.recovery / onRecover tell the host what happened. When IndexedDB is not usable the editor boots IN MEMORY (nabu.persistence.available === false, status 'unavailable'): createPersistedEditor does not reject.

Several tabs (ADR 010, P-94): two tabs on the same docId never overwrite each other. EVERY save first merges what is stored (read and written in one IndexedDB transaction: the changes another tab saved are imported into the live document, not as undo steps, the caret stays on its character), and, with sync (default), the tabs also exchange their changes LIVE through a BroadcastChannel (nabu:<docId>): an edit made in one tab appears in the others at once.

The instance gains extra members:

  • nabu.persistence — reactive state: status ('saved' | 'pending' | 'saving' | 'error' | 'unavailable'), lastSavedAt, lastError, available, recovery, sync, and dispose() (stop autosave, remove the page listeners, close the sync channel and the connection)
  • nabu.saveNow() — force an immediate save (returns a Promise; rejects if the save fails)
  • nabu.compact({ retain, onBeforeCompact }) — explicit compaction (returns a CompactResult, never throws for a refusal)
  • nabu.clearPersistence() — delete the stored snapshot and release the document's files (returns a Promise)
  • nabu.isNew — true if no previous snapshot was found (fresh document, or nothing readable was left: persistence.recovery.kind === 'empty')

Signature

TypeScript
import { createPersistedEditor } from '@aionbuilders/nabu';
TypeScript
function createPersistedEditor(options?: object): Promise<Nabu>

Parameters

Parameters of createPersistedEditor
ParameterTypeDefaultDescription
optionsoptionalobject...n/a
options.debounceoptionalnumber2000

Auto-save debounce delay in ms (default: 2000)

options.docIdstringn/a

Unique identifier for this document in IndexedDB

options.extensionsoptionalExtension[][]

Additional extensions to append

options.filesoptionalFilesConfign/a

File handling; files.store replaces the default IndexedDB asset store

options.historyoptional'full' | { mode: 'full' } | { mode: 'compact'; onBeforeCompact?: (fullSnapshot: Uint8Array) => void | Promise<void>; retain: string | number }n/a

'full' (default): nothing is ever compacted. { mode: 'compact', retain, onBeforeCompact }: compact at open (retain: ms, '30d', '12h', or 0 = keep no past history).

options.langoptionalstringn/a

Forwarded to Nabu (BCP 47 tag of the content)

options.maxWaitoptionalnumbern/a

Longest time (ms) a continuous burst of edits goes without a save (default: 3000, never shorter than debounce; 0 / Infinity: no maximum, the autosave only happens after a pause)

options.onRecoveroptional(recovery: Recovery) => voidn/a

Called at boot when the stored document could not be read (see nabu.persistence.recovery)

options.onSaveErroroptional(error: unknown) => voidn/a

Called when an AUTOMATIC save fails (quota…); nabu.persistence.status is 'error' and the next change retries. saveNow() rejects for its awaiter instead (the hook is not called for it)

options.placeholdersoptionalPlaceholdersOptionn/a

Forwarded to Nabu (any other Nabu init option, e.g. files, is forwarded too; files.store replaces the default IndexedDB asset store)

options.presetoptionalExtension[]FullPreset

Extension preset (default: FullPreset)

options.spellcheckoptionalbooleann/a

Forwarded to Nabu (default true)

options.syncoptionalbooleantrue

Live sync between the tabs that edit the same docId through a BroadcastChannel (default true; false disables it; a browser without BroadcastChannel skips it silently). The merge on save does not depend on it. nabu.persistence.sync tells whether it runs.

Returns

Promise<Nabu>

Examples

TypeScript
// Fresh or restored editor, full preset
const engine = await createPersistedEditor({ docId: 'my-note' });
TypeScript
// Document preset with custom extension
const engine = await createPersistedEditor({
  docId: 'article-42',
  preset: DocumentPreset,
  extensions: [MyCustomExtension],
});

Source: src/lib/persistence/persistence.js:105