Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a2f07b8
Use socketPath option
rikmarais Aug 15, 2023
86eaad5
Override _open for better error handling
rikmarais Aug 16, 2023
6a2370b
Update checks for opening status
rikmarais Aug 16, 2023
b502149
Reorder callback and early exit
rikmarais Aug 16, 2023
102ae90
Merge pull request #1 from ForgeVTT/override_open_improve_err_handling
kakaroto Aug 16, 2023
0b959f8
Do not pass the error back to kConnect
kakaroto Aug 17, 2023
1ef3e6b
Only call cb if it exists
rikmarais Aug 17, 2023
ec727f3
Merge pull request #2 from ForgeVTT/run_cb_if_exists
kakaroto Aug 17, 2023
752aecc
Bump classic-level to 2.0.0
Eranziel Mar 10, 2025
ca43ae7
Initial async refactor. No more callbacks!
Eranziel Mar 10, 2025
3bd3551
Avoid potentially infinite retries
Eranziel Mar 10, 2025
f00dd2f
Abstract Level 2.0 no longer supports Node <16
Eranziel Apr 10, 2025
b0a451c
Bump readable-stream to 4.0.0
Eranziel Apr 17, 2025
9f3b393
Use updated fork of many-level
Eranziel Apr 17, 2025
83e2761
Promisify pipeline
Eranziel Apr 17, 2025
2aa9e3f
Add test with a single database
Eranziel Apr 17, 2025
545af99
Prevent uncaught rejections, allow kConnect to resolve _open early
Eranziel Apr 18, 2025
9bf119d
Update basic tests
Eranziel Apr 18, 2025
dad1111
Refactor bytewise test to async
Eranziel Apr 18, 2025
e6b3bca
Add explanatory comment
Eranziel Apr 18, 2025
35d1e43
Use git+ssh, pin to specific commit
Eranziel Apr 18, 2025
82eb64d
Update many-level dependency commit
Eranziel May 7, 2025
9702f65
Merge pull request #3 from ForgeVTT/cl-2.0-refactor
Eranziel May 7, 2025
6e69fb1
docs: JSDoc all the things
wyrmisis Nov 10, 2025
b3840bc
chore: package-lock.json was never committed
wyrmisis Nov 10, 2025
3820dbd
chore: set connected to false on disconnected socket; add "closed" st…
wyrmisis Nov 11, 2025
d6b4af4
chore: WIP planning comments
Eranziel Nov 11, 2025
4be82c6
fix: multi-process tests - refactor to use async
Eranziel Nov 11, 2025
1944005
fix: refactor sublevel tests to be async
wyrmisis Nov 11, 2025
64f4c41
fix: refactor election tests to be a bit more straightforward
wyrmisis Nov 11, 2025
8b6991d
feat: add basic test for three databases
wyrmisis Nov 11, 2025
0552f97
chore: add and use faucet to format test output
wyrmisis Nov 11, 2025
32a8990
fix: update many-level to latest Forge fork
wyrmisis Nov 12, 2025
52cab09
test: rewrite multi-process tests to be clearer; add tests for multi-…
wyrmisis Nov 17, 2025
68584ef
fix: listen for flush before removing the flush listener
wyrmisis Nov 17, 2025
ac8c067
test: rewrite open-close tests; rename file to something less ambiguous
wyrmisis Nov 17, 2025
502d04e
Improve test messages
Eranziel Nov 18, 2025
342fec2
Reset connection attempt timer on socket close
Eranziel Nov 18, 2025
99d5076
Update many-level after PR merge
Eranziel Nov 20, 2025
512c6e1
Merge pull request #4 from ForgeVTT/fix/get-tests-passing
Eranziel Nov 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
392 changes: 294 additions & 98 deletions index.js

Large diffs are not rendered by default.

11,482 changes: 11,482 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"url": "http://substack.net"
},
"scripts": {
"test": "standard && ts-standard *.ts && hallmark && nyc tape test/*.js",
"test": "standard && ts-standard *.ts && hallmark && (nyc -s tape test/*.js | faucet) && nyc report",
"coverage": "nyc report -r lcovonly",
"hallmark": "hallmark --fix",
"dependency-check": "dependency-check --no-dev . test/*.js",
Expand All @@ -24,15 +24,16 @@
"UPGRADING.md"
],
"dependencies": {
"classic-level": "^1.2.0",
"many-level": "^1.0.1",
"classic-level": "^2.0.0",
"many-level": "git+ssh://git@github.com/ForgeVTT/many-level.git#e3e77e4ab35f1a39e4c134228b061b70043da442",
"module-error": "^1.0.2",
"readable-stream": "^3.6.0"
"readable-stream": "^4.0.0"
},
"devDependencies": {
"@voxpelli/tsconfig": "^4.0.0",
"bytewise": "^1.1.0",
"dependency-check": "^4.1.0",
"faucet": "^0.0.4",
"hallmark": "^4.1.0",
"nyc": "^15.1.0",
"standard": "^16.0.3",
Expand All @@ -56,6 +57,6 @@
"socket"
],
"engines": {
"node": ">=12"
"node": ">=16"
}
}
61 changes: 47 additions & 14 deletions test/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,66 @@ const path = require('path')
const events = require('events')
const { RaveLevel } = require('..')

test('two databases', function (t) {
test('single database', async function (t) {
t.plan(1)

const location = tempy.directory()
const db = new RaveLevel(location, { valueEncoding: 'json' })
const value = Math.floor(Math.random() * 100000)

await db.put('a', value)
const x = await db.get('a')
t.is(x, value)

await db.close()
})

test('two databases', async function (t) {
t.plan(3)

const location = tempy.directory()
const db1 = new RaveLevel(location, { valueEncoding: 'json' })
const db2 = new RaveLevel(location, { valueEncoding: 'json' })
const value = Math.floor(Math.random() * 100000)

await db1.put('a', value)
const x = await db2.get('a')
t.is(x, value)

await db1.close()
t.is(db1.status, 'closed')

await db2.close()
t.is(db2.status, 'closed')
})

test('three databases', async function (t) {
t.plan(5)

const location = tempy.directory()
const db1 = new RaveLevel(location, { valueEncoding: 'json' })
const db2 = new RaveLevel(location, { valueEncoding: 'json' })
const db3 = new RaveLevel(location, { valueEncoding: 'json' })
const value = Math.floor(Math.random() * 100000)

db1.put('a', value, function (err) {
t.ifError(err)
await db1.put('a', value)
const x = await db2.get('a')
const y = await db3.get('a')
t.is(x, value)
t.is(y, value)

db2.get('a', function (err, x) {
t.ifError(err)
t.is(x, value)
await db1.close()
t.is(db1.status, 'closed')

db1.close(function (err) {
t.ifError(err)
await db2.close()
t.is(db2.status, 'closed')

db2.close(function (err) {
t.ifError(err)
})
})
})
})
await db3.close()
t.is(db3.status, 'closed')
})

test('two locations do not conflict', async function (t) {
t.plan(2)
const db1 = new RaveLevel(tempy.directory())
const db2 = new RaveLevel(tempy.directory())

Expand Down
34 changes: 11 additions & 23 deletions test/bytewise.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,23 @@ const bytewise = require('bytewise')
const tempy = require('./util/tempy')
const { RaveLevel } = require('..')

test('bytewise key encoding', function (t) {
t.plan(7)
test('bytewise key encoding', async function (t) {
t.plan(3)

const location = tempy.directory()
const db1 = new RaveLevel(location, { keyEncoding: bytewise, valueEncoding: 'json' })
const db2 = new RaveLevel(location, { keyEncoding: bytewise, valueEncoding: 'json' })
const value = Math.floor(Math.random() * 100000)

db1.put(['a'], value, function (err) {
t.ifError(err)
await db1.put(['a'], value)
const x = await db2.get(['a'])
t.is(x, value)

db2.get(['a'], function (err, x) {
t.ifError(err)
t.is(x, value)
})
const db1Entries = await db1.iterator().all()
t.same(db1Entries, [[['a'], value]], 'a got correct entries')
const db2Entries = await db2.iterator().all()
t.same(db2Entries, [[['a'], value]], 'b got correct entries')

db1.iterator().all(function (err, entries) {
t.ifError(err)
t.same(entries, [[['a'], value]], 'a got correct entries')
})
db2.iterator().all(function (err, entries) {
t.ifError(err)
t.same(entries, [[['a'], value]], 'b got correct entries')
})
})

t.on('end', function () {
// TODO: await
db1.close()
db2.close()
})
await db1.close()
await db2.close()
})
76 changes: 39 additions & 37 deletions test/election.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,54 +19,56 @@ test('basic failover', async function (t) {
t.same(await db2.values().all(), values)
})

test('failover election party', function (t) {
test('failover election party', async function (t) {
const location = tempy.directory()
const keys = ['a', 'b', 'c', 'e', 'f', 'g']
const len = keys.length
// Assertion count: 6+5+4+3+2+1 = 21 total (one per database per iteration)
t.plan(len * (len + 1) / 2)
let pending = keys.length

const databases = {}

keys.forEach(function (key) {
const h = open(key)
h.on('open', function () {
if (--pending === 0) spinDown()
// Open all databases and wait for them to be ready
await Promise.all(
keys.map(key => {
const db = new RaveLevel(location, { valueEncoding: 'json' })
databases[key] = db

// Wrap the 'open' event in a promise so we can await it
return new Promise((resolve, reject) => {
db.on('open', resolve)
db.on('error', reject)
})
})
})
)

function open (key) {
const h = databases[key] = new RaveLevel(location, { valueEncoding: 'json' })
return h
}
// Test sequential failover: close databases one at a time while
// validating that remaining databases still replicate correctly
const alive = keys.slice() // Copy so we can mutate without affecting original

function spinDown () {
const alive = keys.slice();
(function next () {
if (alive.length === 0) return
while (alive.length > 0) {
// Validate circular replication across all remaining databases
// Each database writes a value that the next database should be able to read
for (let i = 0; i < alive.length; i++) {
const currentKey = alive[i]
const nextKey = alive[(i + 1) % alive.length] // Wrap around to first database
const value = Math.random()

check(alive, function () {
const key = alive.shift()
databases[key].close()
next()
})
})()
}
// Write to current database
await databases[currentKey].put(currentKey, value)

// Give the updated value a chance to propagate
await new Promise(resolve => setTimeout(resolve, 1))

function check (keys, cb) {
let pending = keys.length
if (pending === 0) return cb()
for (let i = 0; i < keys.length; i++) {
(function (a, b) {
const value = Math.random()
databases[a].put(a, value, function (err) {
if (err) t.fail(err)
databases[b].get(a, function (err, x) {
if (err) t.fail(err)
t.equal(x, value)
if (--pending === 0) cb()
})
})
})(keys[i], keys[(i + 1) % keys.length])
// Read from next database (should be replicated)
const retrieved = await databases[nextKey].get(currentKey)

// Verify the replicated value matches what we wrote
t.equal(retrieved, value)
}

// Remove and close the first database in the remaining set
const key = alive.shift()
await databases[key].close()
}
})
Loading