|
1 | 1 | --- |
2 | 2 | title: Storage Module |
3 | | -description: A persistent storage module that saves data to the user's local storage, falling back to an in-memory provider when persistent storage is not permitted. |
4 | | -keywords: jodit, storage, persistent storage, local storage, memory storage, data persistence |
| 3 | +description: Persistent and asynchronous key/value storage for Jodit. Saves data to localStorage, sessionStorage or IndexedDB and transparently falls back to an in-memory provider when persistent storage is not permitted. |
| 4 | +keywords: jodit, storage, persistent storage, local storage, session storage, indexeddb, memory storage, data persistence, async storage |
5 | 5 | --- |
6 | 6 |
|
7 | | -# Persistent storage |
| 7 | +# Storage |
8 | 8 |
|
9 | | -The module is designed to save information to the user's local storage. |
10 | | -At startup, it is checked whether the user has allowed saving to persistent storage. |
| 9 | +The storage module is a small key/value abstraction used across the editor to persist |
| 10 | +things like the current editing mode, the file browser view, panel sizes, and any state |
| 11 | +saved through the [[persistent]] decorator. |
11 | 12 |
|
12 | | -> If not allowed, the module will use the [[MemoryStorageProvider]] strategy. |
| 13 | +It ships with two facades and three underlying providers (engines): |
| 14 | + |
| 15 | +| Facade | API | Providers it can pick | |
| 16 | +| --- | --- | --- | |
| 17 | +| [[Storage]] | **synchronous** | [[LocalStorageProvider]] (local/session), [[MemoryStorageProvider]] | |
| 18 | +| [[AsyncStorage]] | **Promise-based** | [[IndexedDBProvider]], [[LocalStorageProvider]], [[MemoryStorageProvider]] | |
| 19 | + |
| 20 | +At construction time each facade probes whether the requested persistent engine is actually |
| 21 | +usable (e.g. the browser may block `localStorage` in private mode, or `IndexedDB` may be |
| 22 | +disabled). If it is not, the facade silently falls back to [[MemoryStorageProvider]], so |
| 23 | +`set`/`get` never throw — they just stop surviving reloads. |
| 24 | + |
| 25 | +## Using it from a Jodit instance |
| 26 | + |
| 27 | +Every `Jodit`/`View` instance exposes three ready-made storages (see `core/view/view.ts`): |
13 | 28 |
|
14 | 29 | ```js |
15 | 30 | const jodit = Jodit.make('#editor'); |
16 | | -jodit.storage.set('someKey', 1); |
17 | 31 |
|
18 | | -// reload page |
| 32 | +// Persistent, synchronous, scoped to this editor instance (suffix = jodit.id) |
| 33 | +jodit.storage.set('height', 300); |
| 34 | +jodit.storage.get('height'); // 300 (survives page reload) |
| 35 | + |
| 36 | +// Persistent, asynchronous (IndexedDB when available), same scope |
| 37 | +await jodit.asyncStorage.set('draft', { html: '<p>hi</p>' }); |
| 38 | +await jodit.asyncStorage.get('draft'); // { html: '<p>hi</p>' } |
| 39 | + |
| 40 | +// Non-persistent scratch space, shared process-wide (always memory-backed) |
| 41 | +jodit.buffer.set('copyFormat', style); |
| 42 | +jodit.buffer.get('copyFormat'); |
| 43 | +``` |
| 44 | + |
| 45 | +- `jodit.storage` — `Storage.makeStorage(true, jodit.id)`, **deprecated** in favour of `asyncStorage`. |
| 46 | +- `jodit.asyncStorage` — `AsyncStorage.makeStorage(true, jodit.id, jodit.o.asyncStorage)`; prefer this for new code. |
| 47 | +- `jodit.buffer` — `Storage.makeStorage()` (no persistence), for transient data. |
| 48 | + |
| 49 | +### Choosing the provider via editor config |
| 50 | + |
| 51 | +The provider that backs `jodit.asyncStorage` can be configured with the `asyncStorage` |
| 52 | +editor option. By default it is persistent `IndexedDB` (with a memory fallback); set |
| 53 | +`defaultProvider` to override it: |
| 54 | + |
| 55 | +```js |
| 56 | +// Persist in localStorage instead of IndexedDB |
| 57 | +Jodit.make('#editor', { asyncStorage: { defaultProvider: 'local' } }); |
| 58 | + |
| 59 | +// Keep everything in memory (nothing survives a reload) |
| 60 | +Jodit.make('#editor', { asyncStorage: { defaultProvider: 'memory' } }); |
| 61 | + |
| 62 | +// Plug in your own backend (any object implementing IAsyncStorage) |
| 63 | +Jodit.make('#editor', { asyncStorage: { defaultProvider: myAsyncStorage } }); |
| 64 | +``` |
| 65 | + |
| 66 | +## Using it directly (without a Jodit instance) |
| 67 | + |
| 68 | +Both facades and all providers are exported on `Jodit.modules`, so you can build a standalone |
| 69 | +storage without creating an editor. This is exactly how the unit tests exercise the module. |
| 70 | + |
| 71 | +### Synchronous — `Jodit.modules.Storage` |
| 72 | + |
| 73 | +```js |
| 74 | +const { Storage } = Jodit.modules; |
| 75 | + |
| 76 | +// makeStorage(persistentOrStrategy = false, suffix?) |
| 77 | +const storage = Storage.makeStorage('localStorage', 'myApp'); |
| 78 | + |
| 79 | +storage.set('theme', 'dark'); // chainable — returns `this` |
| 80 | +storage.get('theme'); // 'dark' |
| 81 | +storage.get('missing'); // undefined |
| 82 | +storage.exists('theme'); // true |
| 83 | +storage.delete('theme'); |
| 84 | +storage.clear(); |
| 85 | +``` |
| 86 | + |
| 87 | +`persistentOrStrategy` accepts: |
| 88 | + |
| 89 | +| Value | Engine chosen | |
| 90 | +| --- | --- | |
| 91 | +| `false` *(default)* | [[MemoryStorageProvider]] | |
| 92 | +| `true` | [[LocalStorageProvider]] with `localStorage` (memory fallback) | |
| 93 | +| `'localStorage'` | [[LocalStorageProvider]] with `localStorage` (memory fallback) | |
| 94 | +| `'sessionStorage'` | [[LocalStorageProvider]] with `sessionStorage` (memory fallback) | |
| 95 | + |
| 96 | +### Asynchronous — `Jodit.modules.AsyncStorage` |
| 97 | + |
| 98 | +Same shape, but every method returns a `Promise`. It adds `IndexedDB` support and a `close()` |
| 99 | +method (only meaningful for IndexedDB — it releases the DB connection). |
| 100 | + |
| 101 | +```js |
| 102 | +const { AsyncStorage } = Jodit.modules; |
| 103 | + |
| 104 | +// makeStorage(persistentOrStrategy = false, suffix?, options?) |
| 105 | +const storage = AsyncStorage.makeStorage('indexedDB', 'myApp'); |
| 106 | + |
| 107 | +await storage.set('user', { id: 1, name: 'Ann' }); |
| 108 | +await storage.get('user'); // { id: 1, name: 'Ann' } |
| 109 | +await storage.exists('user'); // true |
| 110 | +await storage.delete('user'); |
| 111 | +await storage.clear(); |
| 112 | +await storage.close(); // release the IndexedDB connection when done |
| 113 | +``` |
| 114 | + |
| 115 | +#### Overriding the provider — `options.defaultProvider` |
| 116 | + |
| 117 | +The optional third argument lets you force which provider backs the storage, regardless of |
| 118 | +the first argument. When omitted the storage behaves exactly as before. |
| 119 | + |
| 120 | +```js |
| 121 | +// localStorage instead of the default IndexedDB |
| 122 | +AsyncStorage.makeStorage(true, 'myApp', { defaultProvider: 'local' }); |
| 123 | + |
| 124 | +// in-memory only |
| 125 | +AsyncStorage.makeStorage(true, 'myApp', { defaultProvider: 'memory' }); |
| 126 | + |
| 127 | +// your own IAsyncStorage implementation (set/delete/get/exists/clear/close) |
| 128 | +AsyncStorage.makeStorage(true, 'myApp', { defaultProvider: myAsyncStorage }); |
| 129 | +``` |
| 130 | + |
| 131 | +| `defaultProvider` | Engine used | |
| 132 | +| --- | --- | |
| 133 | +| *(omitted)* | Falls back to the first-argument strategy (default: IndexedDB) | |
| 134 | +| `'local'` | [[LocalStorageProvider]] with `localStorage` (memory fallback) | |
| 135 | +| `'memory'` | [[MemoryStorageProvider]] | |
| 136 | +| an `IAsyncStorage` object | Your implementation, used as-is | |
| 137 | + |
| 138 | +A custom provider is still wrapped by the facade, so keys passed to it are namespaced |
| 139 | +(`Jodit_` + suffix, camelCased) just like the built-in providers. |
| 140 | + |
| 141 | +`persistentOrStrategy` for `AsyncStorage`: |
| 142 | + |
| 143 | +| Value | Engine chosen | |
| 144 | +| --- | --- | |
| 145 | +| `false` *(default)* | [[MemoryStorageProvider]] | |
| 146 | +| `true` | [[IndexedDBProvider]] (memory fallback if IndexedDB is unavailable) | |
| 147 | +| `'indexedDB'` | [[IndexedDBProvider]] (memory fallback) | |
| 148 | +| `'localStorage'` | [[LocalStorageProvider]] with `localStorage` | |
| 149 | +| `'sessionStorage'` | [[LocalStorageProvider]] with `sessionStorage` | |
| 150 | + |
| 151 | +Because provider selection for IndexedDB is itself asynchronous, the facade wraps a |
| 152 | +`Promise<provider>` internally and each call awaits it. You never see that promise — just |
| 153 | +`await` the storage methods. |
19 | 154 |
|
20 | | -jodit.storage.get('someKey'); // 1 |
| 155 | +### Using a provider directly |
| 156 | + |
| 157 | +The engines implement `IStorage` / `IAsyncStorage` and can be instantiated on their own when |
| 158 | +you want full control over the root key and no `Jodit_`/camelCase key mangling (see caveats |
| 159 | +below): |
| 160 | + |
| 161 | +```js |
| 162 | +const { LocalStorageProvider, MemoryStorageProvider, IndexedDBProvider } = Jodit.modules; |
| 163 | + |
| 164 | +const local = new LocalStorageProvider('rootKey', 'sessionStorage'); |
| 165 | +local.set('a', 1); |
| 166 | + |
| 167 | +const mem = new MemoryStorageProvider(); |
| 168 | + |
| 169 | +const idb = new IndexedDBProvider('myDb', 'keyValueStore'); |
| 170 | +await idb.set('a', 1); |
| 171 | +// IndexedDBProvider also has extras not on the IStorage interface: |
| 172 | +await idb.keys(); // ['a'] |
| 173 | +await idb.values(); // [1] |
| 174 | +await idb.entries(); // [['a', 1]] |
| 175 | +await idb.close(); |
21 | 176 | ``` |
| 177 | + |
| 178 | +## Feature detection |
| 179 | + |
| 180 | +Two helpers are exported so you can check availability before choosing a strategy: |
| 181 | + |
| 182 | +```js |
| 183 | +// Synchronous — probes localStorage / sessionStorage by writing a temp key. |
| 184 | +Jodit.modules.canUsePersistentStorage('localStorage'); // boolean |
| 185 | +Jodit.modules.canUsePersistentStorage('sessionStorage'); // boolean |
| 186 | + |
| 187 | +// Asynchronous — opens a throwaway IndexedDB database. Result is cached. |
| 188 | +await Jodit.modules.canUseIndexedDB(); // Promise<boolean> |
| 189 | +Jodit.modules.clearUseIndexedDBCache(); // reset the cached result (used in tests) |
| 190 | +``` |
| 191 | + |
| 192 | +## Namespacing: prefix, suffix and key mangling |
| 193 | + |
| 194 | +- Every facade prefixes stored keys with `Jodit_` (exported as `StorageKey`). |
| 195 | +- The optional `suffix` passed to `makeStorage` is appended to that prefix, letting several |
| 196 | + independent storages coexist (`Jodit.make` uses the editor's `id` as the suffix so two |
| 197 | + editors on the same page don't clobber each other's data). |
| 198 | +- Keys are run through `camelCase`, so `storage.set('my-key')` and `storage.set('myKey')` |
| 199 | + address **the same** entry. Prefer plain identifiers for keys. |
| 200 | + |
| 201 | +```js |
| 202 | +Storage.makeStorage(true, 'app1').set('key', 'a'); |
| 203 | +Storage.makeStorage(true, 'app2').set('key', 'b'); |
| 204 | +// 'app1' and 'app2' are isolated: each 'key' keeps its own value. |
| 205 | +``` |
| 206 | + |
| 207 | +## Supported value types |
| 208 | + |
| 209 | +Anything JSON-serialisable: `string`, `number`, `boolean`, `null`, plain objects and arrays |
| 210 | +(`StorageValueType`). `LocalStorageProvider` and `IndexedDBProvider` round-trip values through |
| 211 | +serialisation, so don't store class instances, functions, `Date`, `undefined`, etc. and expect |
| 212 | +them back intact. `MemoryStorageProvider` keeps the original reference in a `Map`. |
| 213 | + |
| 214 | +## Caveats |
| 215 | + |
| 216 | +- **One scope is one JSON blob.** All keys of a `LocalStorageProvider` instance live inside a |
| 217 | + single JSON object under one `rootKey`. `delete(key)` removes just that key (the rest of the |
| 218 | + scope is preserved); `clear()` drops the whole blob. |
| 219 | +- **Failures are swallowed.** Providers wrap storage access in `try/catch` and fall back |
| 220 | + silently. A blocked `localStorage`, quota error, or private-mode restriction turns into a |
| 221 | + no-op / memory fallback rather than an exception. |
| 222 | +- **`asyncStorage.close()`** should be called when you're finished with an IndexedDB-backed |
| 223 | + storage to release the connection; the editor does this automatically on `beforeDestruct`. |
| 224 | + |
| 225 | +## API surface |
| 226 | + |
| 227 | +`IStorage<T>` (sync): |
| 228 | + |
| 229 | +```ts |
| 230 | +set(key: string, value: T): this; |
| 231 | +delete(key: string): this; |
| 232 | +get<R = T>(key: string): R | void; |
| 233 | +exists(key: string): boolean; |
| 234 | +clear(): this; |
| 235 | +``` |
| 236 | +
|
| 237 | +`IAsyncStorage<T>` (async) — same methods returning `Promise`, plus `close(): Promise<void>`. |
0 commit comments