You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+179-7Lines changed: 179 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -111,7 +111,7 @@ Close the database. Returns a promise.
111
111
112
112
A database may have associated resources like file handles and locks. When the database is no longer needed (for the remainder of a program) it's recommended to call `db.close()` to free up resources.
113
113
114
-
After `db.close()` has been called, no further read & write operations are allowed unless and until `db.open()` is called again. For example, `db.get(key)` will yield an error with code [`LEVEL_DATABASE_NOT_OPEN`](#errors). Any unclosed iterators or chained batches will be closed by `db.close()` and can then no longer be used even when `db.open()` is called again.
114
+
After `db.close()` has been called, no further read & write operations are allowed unless and until `db.open()` is called again. For example, `db.get(key)` will yield an error with code [`LEVEL_DATABASE_NOT_OPEN`](#errors). Any unclosed iterators, snapshots and chained batches will be closed by `db.close()` and can then no longer be used even when `db.open()` is called again.
115
115
116
116
### `db.supports`
117
117
@@ -129,6 +129,7 @@ Get a value from the database by `key`. The optional `options` object may contai
129
129
130
130
-`keyEncoding`: custom key encoding for this operation, used to encode the `key`.
131
131
-`valueEncoding`: custom value encoding for this operation, used to decode the value.
132
+
-`snapshot`: explicit [snapshot](#snapshot) to [read from](#reading-from-snapshots) assuming `db.supports.explicitSnapshots` is true. If no `snapshot` is provided and `db.supports.implicitSnapshots` is true, the database will create its own internal snapshot for this operation.
132
133
133
134
Returns a promise for the value. If the `key` was not found then the value will be `undefined`.
134
135
@@ -138,6 +139,7 @@ Get multiple values from the database by an array of `keys`. The optional `optio
138
139
139
140
-`keyEncoding`: custom key encoding for this operation, used to encode the `keys`.
140
141
-`valueEncoding`: custom value encoding for this operation, used to decode values.
142
+
-`snapshot`: explicit [snapshot](#snapshot) to [read from](#reading-from-snapshots) assuming `db.supports.explicitSnapshots` is true. If no `snapshot` is provided and `db.supports.implicitSnapshots` is true, the database will create its own internal snapshot for this operation.
141
143
142
144
Returns a promise for an array of values with the same order as `keys`. If a key was not found, the relevant value will be `undefined`.
143
145
@@ -233,6 +235,7 @@ The `gte` and `lte` range options take precedence over `gt` and `lt` respectivel
233
235
-`keyEncoding`: custom key encoding for this iterator, used to encode range options, to encode `seek()` targets and to decode keys.
234
236
-`valueEncoding`: custom value encoding for this iterator, used to decode values.
235
237
-`signal`: an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to [abort read operations on the iterator](#aborting-iterators).
238
+
-`snapshot`: explicit [snapshot](#snapshot) for the iterator to [read from](#reading-from-snapshots) assuming `db.supports.explicitSnapshots` is true. If no `snapshot` is provided and `db.supports.implicitSnapshots` is true, the database will create its own internal snapshot before returning an iterator.
236
239
237
240
Lastly, an implementation is free to add its own options.
238
241
@@ -275,6 +278,7 @@ Delete all entries or a range. Not guaranteed to be atomic. Returns a promise. A
275
278
-`reverse` (boolean, default: `false`): delete entries in reverse order. Only effective in combination with `limit`, to delete the last N entries.
276
279
-`limit` (number, default: `Infinity`): limit the number of entries to be deleted. This number represents a _maximum_ number of entries and will not be reached if the end of the range is reached first. A value of `Infinity` or `-1` means there is no limit. When `reverse` is true the entries with the highest keys will be deleted instead of the lowest keys.
277
280
-`keyEncoding`: custom key encoding for this operation, used to encode range options.
281
+
-`snapshot`: explicit [snapshot](#snapshot) to [read from](#reading-from-snapshots) assuming `db.supports.explicitSnapshots` is true, such that entries not present in the snapshot will not be deleted. If no `snapshot` is provided and `db.supports.implicitSnapshots` is true, the database may create its own internal snapshot but (unlike on other methods) this is currently not a hard requirement for implementations.
278
282
279
283
The `gte` and `lte` range options take precedence over `gt` and `lt` respectively. If no options are provided, all entries will be deleted.
**This is an experimental API and not widely supported at the time of writing ([Level/community#118](https://github.com/Level/community/issues/118)).**
391
+
392
+
Create an explicit [snapshot](#snapshot). Throws a [`LEVEL_NOT_SUPPORTED`](#errors) error if `db.supports.explicitSnapshots` is not true. For details, see [Reading From Snapshots](#reading-from-snapshots).
393
+
384
394
### `db.defer(fn[, options])`
385
395
386
396
Call the function `fn` at a later time when [`db.status`](#dbstatus) changes to `'open'` or `'closed'`. Known as a _deferred operation_. Used by `abstract-level` itself to implement "deferred open" which is a feature that makes it possible to call methods like `db.put()` before the database has finished opening. The `defer()` method is exposed for implementations and plugins to achieve the same on their custom methods:
@@ -464,8 +474,6 @@ A reference to the database that created this chained batch.
464
474
465
475
An iterator allows one to lazily read a range of entries stored in the database. The entries will be sorted by keys in [lexicographic order](https://en.wikipedia.org/wiki/Lexicographic_order) (in other words: byte order) which in short means key `'a'` comes before `'b'` and key `'10'` comes before `'2'`.
466
476
467
-
An iterator reads from a snapshot of the database, created at the time `db.iterator()` was called. This means the iterator will not see the data of simultaneous write operations. Most but not all implementations can offer this guarantee, as indicated by `db.supports.snapshots`.
468
-
469
477
Iterators can be consumed with [`for await...of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) and `iterator.all()`, or by manually calling `iterator.next()` or `nextv()` in succession. In the latter case, `iterator.close()` must always be called. In contrast, finishing, throwing, breaking or returning from a `for await...of` loop automatically calls `iterator.close()`, as does `iterator.all()`.
470
478
471
479
An iterator reaches its natural end in the following situations:
Free up underlying resources. Be sure to call this when the snapshot is no longer needed, because snapshots may cause the database to temporarily pause internal storage optimizations. Returns a promise. Closing the snapshot is an idempotent operation, such that calling `snapshot.close()` more than once is allowed and makes no difference.
703
+
704
+
After `snapshot.close()` has been called, no further operations are allowed. For example, `db.get(key, { snapshot })` will yield an error with code [`LEVEL_SNAPSHOT_NOT_OPEN`](#errors). Any unclosed iterators (that use this snapshot) will be closed by `snapshot.close()` and can then no longer be used.
705
+
706
+
#### `snapshot.db`
707
+
708
+
A reference to the database that created this snapshot.
709
+
690
710
### Hooks
691
711
692
712
**Hooks are experimental and subject to change without notice.**
@@ -1079,6 +1099,10 @@ When `iterator.next()` or `seek()` was called while a previous `next()` call was
1079
1099
1080
1100
When an operation was made on a chained batch while it was closing or closed, which may also be the result of the database being closed or that `write()` was called on the chained batch.
1081
1101
1102
+
#### `LEVEL_SNAPSHOT_NOT_OPEN`
1103
+
1104
+
When an operation was made on a snapshot while it was closing or closed, which may also be the result of the database being closed.
1105
+
1082
1106
#### `LEVEL_ABORTED`
1083
1107
1084
1108
When an operation was aborted by the user. For [web compatibility](https://dom.spec.whatwg.org/#aborting-ongoing-activities) this error can also be identified by its `name` which is `'AbortError'`:
@@ -1178,6 +1202,110 @@ When a database relies on a connection to a remote party and that connection has
1178
1202
1179
1203
When a remote party encountered an unexpected condition that it can't reflect with a more specific code. Used by `many-level`.
1180
1204
1205
+
### Order Of Operations
1206
+
1207
+
There is no defined order between parallel write operations. Consider:
1208
+
1209
+
```js
1210
+
awaitPromise.all([
1211
+
db.put('example', 1),
1212
+
db.put('example', 2)
1213
+
])
1214
+
1215
+
constresult=awaitdb.get('example')
1216
+
```
1217
+
1218
+
The value of `result` could be either `1` or `2`, because the `db.put()` calls are asynchronous and awaited in parallel. Some implementations of `abstract-level` may unintentionally exhibit a "defined" order due to internal details. Implementations are free to change such details at any time, because per the asynchronous `abstract-level` interface that they follow, the order is theoretically random.
1219
+
1220
+
Removing this concern (if necessary) must be done on an application-level. For example, the application could have a queue of operations, or per-key locks, or implement transactions on top of snapshots, or a versioning mechanism in its keyspace, or specialized data types like CRDT, or just say that conflicts are acceptable for that particular application, and so forth. The abundance of examples should explain why `abstract-level` itself doesn't enter this opinionated and application-specific problem space. Each solution has tradeoffs and `abstract-level`, being the core of a modular database, cannot decide which tradeoff to make.
1221
+
1222
+
### Reading From Snapshots
1223
+
1224
+
A snapshot is a lightweight "token" that represents the version of a database at a particular point in time. This allows for reading data without seeing subsequent writes made on the database. It comes in two forms:
1225
+
1226
+
1. Implicit snapshots: created internally by the database and not visible to the outside world.
1227
+
2. Explicit snapshots: created with `snapshot = db.snapshot()`. Because it acts as a token, `snapshot` has no methods of its own besides `snapshot.close()`. Instead the snapshot is to be passed to database (or [sublevel](#sublevel)) methods like `db.iterator()`.
1228
+
1229
+
Use explicit snapshots wisely, because their lifetime must be managed manually. Implicit snapshots are typically more convenient and possibly more performant because they can handled natively and have their lifetime limited by the surrounding operation. That said, explicit snapshots can be useful to make multiple read operations that require a shared, consistent view of the data.
1230
+
1231
+
Most but not all `abstract-level` implementations support snapshots. They can be divided into three groups.
1232
+
1233
+
#### 1. Implementation does not support snapshots
1234
+
1235
+
As indicated by `db.supports.implicitSnapshots` and `db.supports.explicitSnapshots` being false. In this case, operations read from the latest version of the database. This most notably affects iterators:
1236
+
1237
+
```js
1238
+
awaitdb.put('example', 'a')
1239
+
constit=db.iterator()
1240
+
awaitdb.del('example')
1241
+
constentries=awaitit.all() // Likely an empty array
1242
+
```
1243
+
1244
+
The `db.supports.implicitSnapshots` property is aliased as `db.supports.snapshots` for backwards compatibility.
As indicated by `db.supports.implicitSnapshots` being true. An iterator, upon creation, will synchronously create a snapshot and subsequently read from that snapshot rather than the latest version of the database. There are no actual numerical versions, but let's say there are in order to clarify the behavior:
1249
+
1250
+
```js
1251
+
awaitdb.put('example', 'a') // Results in v1
1252
+
constit=db.iterator() // Creates snapshot of v1
1253
+
awaitdb.del('example') // Results in v2
1254
+
constentries=awaitit.all() // Reads from snapshot and thus v1
1255
+
```
1256
+
1257
+
The `entries` array thus includes the deleted entry, because the snapshot of the iterator represents the database version from before the entry was deleted.
1258
+
1259
+
Other read operations like `db.get()` also use a snapshot. Such calls synchronously create a snapshot and then asynchronously read from it. This means a write operation (to the same key) may not be visible unless awaited:
1260
+
1261
+
```js
1262
+
awaitdb.put('example', 1) // Awaited
1263
+
db.put('example', 2) // Not awaited
1264
+
awaitdb.get('example') // Yields 1 (typically)
1265
+
```
1266
+
1267
+
In other words, once a write operation has _finished_ (including having communicated that to the main thread of JavaScript, i.e. by resolving the promise in the above example) subsequent reads are guaranteed to include that data. That's because those reads use a snapshot created in the main thread which is aware of the finished write at this point. Before that point, no guarantee can be given.
As indicated by `db.supports.explicitSnapshots` being true. This is the most precise and flexible way to control the version of the data to read. The previous example can be modified to get a consistent result:
On implementations that support implicit but not explicit snapshots, some of the above can be simulated. In particular, to get multiple entries from a snapshot, one could create an iterator and then repeatedly `seek()` to the desired entries.
1308
+
1181
1309
### Shared Access
1182
1310
1183
1311
Unless documented otherwise, implementations of `abstract-level` do _not_ support accessing a database from multiple processes running in parallel. That includes Node.js clusters and Electron renderer processes.
@@ -1195,7 +1323,8 @@ const {
1195
1323
AbstractIterator,
1196
1324
AbstractKeyIterator,
1197
1325
AbstractValueIterator,
1198
-
AbstractChainedBatch
1326
+
AbstractChainedBatch,
1327
+
AbstractSnapshot
1199
1328
} =require('abstract-level')
1200
1329
```
1201
1330
@@ -1325,7 +1454,7 @@ The default `_close()` is an async noop. In native implementations (native addon
1325
1454
1326
1455
Get a value by `key`. The `options` object will always have the following properties: `keyEncoding` and `valueEncoding`. Must return a promise. If an error occurs, reject the promise. Otherwise resolve the promise with the value. If the `key` was not found then use `undefined` as value.
1327
1456
1328
-
If the database indicates support of snapshots via `db.supports.snapshots` then `db._get()` must read from a snapshot of the database. That snapshot (or similar mechanism) must be created synchronously when `db._get()` is called, before asynchronously reading the value. This means it should not see the data of write operations that are scheduled immediately after `db._get()`.
1457
+
If the database indicates support of snapshots via `db.supports.implicitSnapshots` then `db._get()` must read from a snapshot of the database. That snapshot (or similar mechanism) must be created synchronously when `db._get()` is called, before asynchronously reading the value. This means it should not see the data of write operations that are scheduled immediately after `db._get()`.
1329
1458
1330
1459
The default `_get()` returns a promise for an `undefined` value. It must be overridden.
1331
1460
@@ -1488,6 +1617,39 @@ class ExampleSublevel extends AbstractSublevel {
1488
1617
}
1489
1618
```
1490
1619
1620
+
### `snapshot = db._snapshot()`
1621
+
1622
+
The default `_snapshot()` throws a [`LEVEL_NOT_SUPPORTED`](#errors) error. To implement this method, extend `AbstractSnapshot`, return an instance of this class in an overridden `_snapshot()` method and set `manifest.explicitSnapshots` to `true`:
The snapshot of the underlying database (or other mechanisms to achieve the same effect) must be created synchronously, such that a call like `db.put()` made immediately after `db._snapshot()` will not affect the snapshot. As for previous write operations that are still in progress at the time that `db._snapshot()` is called: `db._snapshot()` does not have to (and should not) wait for such operations. Solving inconsistencies that may arise from this behavior is an application-level concern. To be clear, if the application awaits the write operations before calling `db.snapshot()` then the snapshot does need to reflect (include) those operations.
1652
+
1491
1653
### `iterator = AbstractIterator(db, options)`
1492
1654
1493
1655
The first argument to this constructor must be an instance of the relevant `AbstractLevel` implementation. The constructor will set `iterator.db` which is used (among other things) to access encodings and ensures that `db` will not be garbage collected in case there are no other references to it. The `options` argument must be the original `options` object that was passed to `db._iterator()` and it is therefore not (publicly) possible to create an iterator via constructors alone.
@@ -1590,6 +1752,16 @@ Free up underlying resources. This method is guaranteed to only be called once.
1590
1752
1591
1753
The default `_close()` returns a resolved promise. Overriding is optional.
1592
1754
1755
+
### `snapshot = AbstractSnapshot(db)`
1756
+
1757
+
The first argument to this constructor must be an instance of the relevant `AbstractLevel` implementation. The constructor will set `snapshot.db` which ensures that `db` will not be garbage collected in case there are no other references to it.
1758
+
1759
+
#### `snapshot._close()`
1760
+
1761
+
Free up underlying resources. This method is guaranteed to only be called once. Must return a promise.
1762
+
1763
+
The default `_close()` returns a resolved promise. Overriding is optional.
1764
+
1593
1765
## Test Suite
1594
1766
1595
1767
To prove that your implementation is `abstract-level` compliant, include the abstract test suite in your `test.js` (or similar):
@@ -1627,14 +1799,14 @@ suite({
1627
1799
1628
1800
### Excluding tests
1629
1801
1630
-
As not every implementation can be fully compliant due to limitations of its underlying storage, some tests may be skipped. This must be done via `db.supports` which is set via the constructor. For example, to skip snapshot tests:
1802
+
As not every implementation can be fully compliant due to limitations of its underlying storage, some tests may be skipped. This must be done via `db.supports` which is set via the constructor. For example, to skip tests of implicit snapshots:
0 commit comments