Skip to content

Commit 8cd7f86

Browse files
committed
Initial
0 parents  commit 8cd7f86

16 files changed

+589
-0
lines changed

.airtap.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
providers:
2+
- airtap-playwright
3+
4+
browsers:
5+
- name: chromium
6+
- name: webkit
7+
# Firefox does not support WritableStream
8+
# See https://developer.mozilla.org/en-US/docs/Web/API/WritableStream/WritableStream
9+
# - name: firefox

.github/dependabot.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: npm
4+
directory: /
5+
schedule:
6+
interval: monthly
7+
ignore:
8+
- dependency-name: standard
9+
- dependency-name: hallmark
10+
- package-ecosystem: github-actions
11+
directory: /
12+
schedule:
13+
interval: monthly

.github/workflows/release.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: Release
2+
on:
3+
push:
4+
tags: ['*']
5+
permissions:
6+
contents: write
7+
jobs:
8+
release:
9+
name: Release
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout
13+
uses: actions/checkout@v2
14+
- name: Create GitHub release
15+
uses: docker://antonyurchenko/git-release:v4
16+
env:
17+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/test.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Test
2+
on: [push, pull_request]
3+
permissions:
4+
contents: read
5+
jobs:
6+
test:
7+
runs-on: ubuntu-latest
8+
strategy:
9+
matrix:
10+
node: [16]
11+
name: Node ${{ matrix.node }}
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v2
15+
- name: Use node ${{ matrix.node }}
16+
uses: actions/setup-node@v2
17+
with:
18+
node-version: ${{ matrix.node }}
19+
- name: Install
20+
run: npm install
21+
- name: Test
22+
run: npm test
23+
- name: Coverage
24+
run: npm run coverage
25+
- name: Codecov
26+
uses: codecov/codecov-action@v2
27+
with:
28+
file: coverage/lcov.info

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
.nyc_output/
3+
coverage

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Changelog

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) The contributors to level-web-stream.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# level-web-stream
2+
3+
**Read from an [`abstract-level`](https://github.com/Level/abstract-level) database using [Web Streams](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API).** Compatible with browsers and Node.js.
4+
5+
> :pushpin: To instead consume data using Node.js streams, see [`level-read-stream`](https://github.com/Level/read-stream).
6+
7+
[![level badge][level-badge]](https://github.com/Level/awesome)
8+
[![npm](https://img.shields.io/npm/v/level-web-stream.svg)](https://www.npmjs.com/package/level-web-stream)
9+
[![Node version](https://img.shields.io/node/v/level-web-stream.svg)](https://www.npmjs.com/package/level-web-stream)
10+
[![Test](https://img.shields.io/github/workflow/status/Level/web-stream/Test?label=test)](https://github.com/Level/web-stream/actions/workflows/test.yml)
11+
[![Coverage](https://img.shields.io/codecov/c/github/Level/web-stream?label=&logo=codecov&logoColor=fff)](https://codecov.io/gh/Level/web-stream)
12+
[![Standard](https://img.shields.io/badge/standard-informational?logo=javascript&logoColor=fff)](https://standardjs.com)
13+
[![Common Changelog](https://common-changelog.org/badge.svg)](https://common-changelog.org)
14+
[![Donate](https://img.shields.io/badge/donate-orange?logo=open-collective&logoColor=fff)](https://opencollective.com/level)
15+
16+
## Usage
17+
18+
```js
19+
const { EntryStream } = require('level-web-stream')
20+
const { MemoryLevel } = require('memory-level')
21+
22+
const db = new MemoryLevel()
23+
24+
// Write sample data
25+
await db.put('a', '1')
26+
await db.put('b', '2')
27+
await db.put('c', '3')
28+
29+
// Create a ReadableStream
30+
const src = new EntryStream(db, {
31+
gte: 'b'
32+
})
33+
34+
// Pipe to a stream of choice
35+
const dst = new WritableStream({
36+
write ([key, value]) {
37+
console.log('%s: %s', key, value)
38+
}
39+
})
40+
41+
await src.pipeTo(dst)
42+
```
43+
44+
Yields:
45+
46+
```
47+
b: 2
48+
c: 3
49+
```
50+
51+
To only read keys or values rather than entries:
52+
53+
```js
54+
const { KeyStream, ValueStream } = require('level-web-stream')
55+
56+
await new KeyStream(db).pipeTo(new WritableStream({
57+
write (key) {
58+
console.log(key)
59+
}
60+
}))
61+
```
62+
63+
Note that [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) is a global in browsers. In Node.js it can be imported from [`stream/web`](https://nodejs.org/api/webstreams.html).
64+
65+
## Install
66+
67+
With [npm](https://npmjs.org) do:
68+
69+
```
70+
npm install level-web-stream
71+
```
72+
73+
## API
74+
75+
### `stream = new EntryStream(db[, options])`
76+
77+
Create a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) that will yield entries. An entry is an array with `key` and `value` properties. The `db` argument must be an `abstract-level` database. The optional `options` object may contain:
78+
79+
- `highWaterMark` (number): the maximum number of entries to buffer internally before ceasing to read further entries. Default 1000.
80+
81+
Any other options are forwarded to `db.iterator(options)`. The stream wraps that iterator. If you prefer to consume entries with `for await...of` then it's recommended to directly use `db.iterator()`. In either case, most databases will read from a snapshot (thus unaffected by simultaneous writes) as indicated by `db.supports.snapshots`.
82+
83+
### `stream = new KeyStream(db[, options])`
84+
85+
Same as `EntryStream` but yields keys instead of entries, using `db.keys()` instead of `db.iterator()`. If only keys are needed, using `KeyStream` may increase performance because values won't have to be fetched.
86+
87+
### `stream = new ValueStream(db[, options])`
88+
89+
Same as `EntryStream` but yields values instead of entries, using `db.values()` instead of `db.iterator()`. If only values are needed, using `ValueStream` may increase performance because keys won't have to be fetched.
90+
91+
## Contributing
92+
93+
[`Level/web-stream`](https://github.com/Level/web-stream) is an **OPEN Open Source Project**. This means that:
94+
95+
> Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project.
96+
97+
See the [Contribution Guide](https://github.com/Level/community/blob/master/CONTRIBUTING.md) for more details.
98+
99+
## License
100+
101+
[MIT](LICENSE)
102+
103+
[level-badge]: https://leveljs.org/img/badge.svg

UPGRADING.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Upgrade Guide
2+
3+
This document describes breaking changes and how to upgrade. For a complete list of changes including minor and patch releases, please refer to the [changelog](CHANGELOG.md).
4+
5+
## 1.0.0
6+
7+
_:seedling: Initial release._

index.d.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Assumed to be installed side-by-side, declared as an optional peerDependency.
2+
import {
3+
AbstractLevel,
4+
AbstractIteratorOptions,
5+
AbstractKeyIteratorOptions,
6+
AbstractValueIteratorOptions
7+
} from 'abstract-level'
8+
9+
/**
10+
* A {@link ReadableStream} that yields entries.
11+
*/
12+
export class EntryStream<K, V, TDatabase = AbstractLevel<any, any, any>> extends ReadableStream<{ key: K, value: V }> {
13+
/**
14+
* Create a {@link ReadableStream} that yields entries from {@link db}.
15+
* @param db Database to read from.
16+
* @param options Options for the stream and its underlying iterator.
17+
*/
18+
constructor (db: TDatabase, options?: (LevelReadableStreamOptions & Omit<AbstractIteratorOptions<K, V>, 'keys' | 'values'>) | undefined)
19+
20+
// TODO: support passing in an iterator so that its implementation-specific options are typed?
21+
// constructor (iterator: AbstractIterator<TDatabase, K, V>, ...)
22+
}
23+
24+
/**
25+
* A {@link ReadableStream} that yields keys.
26+
*/
27+
export class KeyStream<K, TDatabase = AbstractLevel<any, any, any>> extends ReadableStream<K> {
28+
/**
29+
* Create a {@link ReadableStream} that yields keys from {@link db}.
30+
* @param db Database to read from.
31+
* @param options Options for the stream and its underlying iterator.
32+
*/
33+
constructor (db: TDatabase, options?: (LevelReadableStreamOptions & AbstractKeyIteratorOptions<K>) | undefined)
34+
}
35+
36+
/**
37+
* A {@link ReadableStream} that yields values.
38+
*/
39+
export class ValueStream<K, V, TDatabase = AbstractLevel<any, any, any>> extends ReadableStream<V> {
40+
/**
41+
* Create a {@link ReadableStream} that yields values from {@link db}.
42+
* @param db Database to read from.
43+
* @param options Options for the stream and its underlying iterator.
44+
*/
45+
constructor (db: TDatabase, options?: (LevelReadableStreamOptions & AbstractValueIteratorOptions<K, V>) | undefined)
46+
}
47+
48+
declare interface LevelReadableStreamOptions {
49+
/**
50+
* The maximum number of items to buffer internally before ceasing to read further
51+
* items.
52+
*
53+
* @defaultValue `1000`
54+
*/
55+
highWaterMark?: number | undefined
56+
57+
/**
58+
* Limit the amount of data that the underlying iterator will hold in memory.
59+
*
60+
* Only supported by [`classic-level`][1] and [`rocks-level`][2], and possibly by
61+
* similar `abstract-level` implementations that are backed by a database on disk.
62+
*
63+
* [1]: https://github.com/Level/classic-level
64+
* [2]: https://github.com/Level/rocks-level
65+
*/
66+
highWaterMarkBytes?: number | undefined
67+
68+
/**
69+
* Only supported by [`classic-level`][1] and [`rocks-level`][2], and possibly by
70+
* similar `abstract-level` implementations that are backed by a database on disk.
71+
*
72+
* [1]: https://github.com/Level/classic-level
73+
* [2]: https://github.com/Level/rocks-level
74+
*/
75+
fillCache?: boolean | undefined
76+
}

index.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
'use strict'
2+
3+
const { ReadableStream, CountQueuingStrategy } = require('./streams')
4+
5+
const kCanceled = Symbol('canceled')
6+
const kIterator = Symbol('iterator')
7+
const kDb = Symbol('db')
8+
9+
class LevelReadableSource {
10+
constructor (iterator) {
11+
this[kIterator] = iterator
12+
this[kCanceled] = false
13+
}
14+
15+
/**
16+
* @param {ReadableStreamDefaultController} controller
17+
*/
18+
async pull (controller) {
19+
let items
20+
21+
try {
22+
items = await this[kIterator].nextv(controller.desiredSize)
23+
} catch (err) {
24+
await this[kIterator].close()
25+
throw err
26+
}
27+
28+
// Nothing to do if cancel() was called
29+
if (this[kCanceled]) {
30+
return
31+
}
32+
33+
if (items.length === 0) {
34+
// Reached the natural end of the iterator
35+
await this[kIterator].close()
36+
controller.close()
37+
} else {
38+
for (const item of items) {
39+
controller.enqueue(item)
40+
}
41+
}
42+
}
43+
44+
cancel () {
45+
this[kCanceled] = true
46+
return this[kIterator].close()
47+
}
48+
}
49+
50+
class LevelReadableStream extends ReadableStream {
51+
constructor (db, method, options) {
52+
const { highWaterMark, ...rest } = options || {}
53+
const iterator = db[method](rest)
54+
const source = new LevelReadableSource(iterator)
55+
const queueingStrategy = new CountQueuingStrategy({
56+
highWaterMark: highWaterMark || 1000
57+
})
58+
59+
super(source, queueingStrategy)
60+
61+
// Keep db around to prevent GC
62+
this[kDb] = db
63+
}
64+
}
65+
66+
class EntryStream extends LevelReadableStream {
67+
constructor (db, options) {
68+
super(db, 'iterator', { ...options, keys: true, values: true })
69+
}
70+
}
71+
72+
class KeyStream extends LevelReadableStream {
73+
constructor (db, options) {
74+
super(db, 'keys', options)
75+
}
76+
}
77+
78+
class ValueStream extends LevelReadableStream {
79+
constructor (db, options) {
80+
super(db, 'values', options)
81+
}
82+
}
83+
84+
exports.EntryStream = EntryStream
85+
exports.KeyStream = KeyStream
86+
exports.ValueStream = ValueStream

0 commit comments

Comments
 (0)