@bullet./paraql 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -16
- package/lib/codecs.js +0 -23
- package/lib/statement.d.ts +0 -3
- package/lib/statement.js +0 -12
- package/lib/vfs.js +7 -63
- package/package.json +3 -8
- package/prebuilds/android-arm/bullet.__paraql.bare +0 -0
- package/prebuilds/android-arm64/bullet.__paraql.bare +0 -0
- package/prebuilds/android-ia32/bullet.__paraql.bare +0 -0
- package/prebuilds/android-x64/bullet.__paraql.bare +0 -0
- package/prebuilds/darwin-arm64/bullet.__paraql.bare +0 -0
- package/prebuilds/darwin-arm64/bullet.__paraql.node +0 -0
- package/prebuilds/darwin-x64/bullet.__paraql.bare +0 -0
- package/prebuilds/darwin-x64/bullet.__paraql.node +0 -0
- package/prebuilds/ios-arm64/bullet.__paraql.bare +0 -0
- package/prebuilds/ios-arm64-simulator/bullet.__paraql.bare +0 -0
- package/prebuilds/ios-x64-simulator/bullet.__paraql.bare +0 -0
- package/prebuilds/linux-arm64/bullet.__paraql.bare +0 -0
- package/prebuilds/linux-x64/bullet.__paraql.bare +0 -0
- package/prebuilds/win32-arm64/bullet.__paraql.bare +0 -0
- package/prebuilds/win32-arm64/bullet.__paraql.node +0 -0
- package/prebuilds/win32-x64/bullet.__paraql.bare +0 -0
- package/prebuilds/win32-x64/bullet.__paraql.node +0 -0
package/README.md
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
ParaQL is a mad-science experiment combining [libSQL](https://github.com/tursodatabase/libsql) (an open-contribution [SQLite](https://github.com/sqlite/sqlite) fork with native vector support) with [Autobase](https://github.com/holepunchto/autobase) (or to be more precise, it's next-gen iteration [Autobee](https://github.com/holepunchto/autobee)) for massively-parallel multi-writer access.
|
|
4
4
|
|
|
5
|
-
It logs every write in a per-instance append-only log (oplog) and applies them in deterministic order to a shared view (the database) ensuring no corruption can occur.
|
|
5
|
+
It logs every write in a per-instance append-only log (oplog) and applies them in deterministic order to a shared view (the database) ensuring no corruption can occur. Performance is impressive, it blows vanilla SQLite out of the water, in part due to flushing to disk less often. That's not an issue though, because by the time database write occurs the operation is already in the oplog, so in case crashes or corruption it can simply be reapplied. Peers who haven't written to the database simply fast-forward (download) the latest database version which on a good connection is even faster.
|
|
6
|
+
|
|
7
|
+
The oplog based design means that disk space used is roughly double (after compaction) that of vanilla SQLite. Operating requirements are considerably higher, because Autobee uses RocksDB as the storage backend and RocksDB doesn't free deleted data immediately as a performance optimization. Periodic compaction, both automatic, and manual, makes this an easily solvable issue.
|
|
6
8
|
|
|
7
9
|
For a better idea of how ParaQL performs and compares to other solutions see [the benchmark](./BENCHMARK.md).
|
|
8
10
|
|
|
9
|
-
ParaQL is developed on [Bare](https://github.com/holepunchto/bare) but it
|
|
11
|
+
ParaQL is developed on [Bare](https://github.com/holepunchto/bare) but it's also tested on Node. Because Bare is multi-platform and supports mobile operating systems as first class citizens, ParaQL runs on recent versions of Android, iOS, macOS, Linux, and Windows.
|
|
10
12
|
|
|
11
13
|
ParaQL supports encrypting the database with a 256-bit key, both on disk and in transport (meaning remote peers need to know the key to read or write into the database). The local-only temporary files are currently encrypted with unique but not random nonces. This is something we're still working on.
|
|
12
14
|
|
|
13
|
-
ParaQL also supports compression with zlib's deflate algorithm. While this slows down writing considerably, it reduces disk space used by ~3x or more, so depending on your environment it might be worth considering. Do note that compression affects every instance of the same database.
|
|
14
|
-
|
|
15
15
|
ParaQL has native support for vector data types and vector search functions with optional indexing. This is courtesy of libSQL and one of the primary reasons ParaQL was made: to support vector similarity search in P2P context.
|
|
16
16
|
|
|
17
17
|
## Install
|
|
@@ -40,6 +40,7 @@ for await (const row of select.iterate()) {
|
|
|
40
40
|
console.log(row)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
await select.finalize()
|
|
43
44
|
await db.close()
|
|
44
45
|
```
|
|
45
46
|
|
|
@@ -65,8 +66,6 @@ options = {
|
|
|
65
66
|
keyPair: null,
|
|
66
67
|
encrypted: false,
|
|
67
68
|
encryptionKey: null,
|
|
68
|
-
compressed: false,
|
|
69
|
-
compressionLevel: 6,
|
|
70
69
|
}
|
|
71
70
|
```
|
|
72
71
|
|
|
@@ -95,8 +94,6 @@ Do note that operations initiated by initial instance (root node) and all read-o
|
|
|
95
94
|
|
|
96
95
|
If `encrypted` is true and `encryptionKey` is provided as 32-byte buffer, it is used to encrypt the database.
|
|
97
96
|
|
|
98
|
-
`compressed` enables database compression and `compressionLevel` controls how many resources are used for the compression. It should be between 0 and 9 (inclusive).
|
|
99
|
-
|
|
100
97
|
### `db.name`
|
|
101
98
|
|
|
102
99
|
The name of the database file.
|
|
@@ -143,11 +140,11 @@ Revoke write access from another instance of the database. `key` should be the l
|
|
|
143
140
|
|
|
144
141
|
### `db.replicate(isInitiatorOrStream)`
|
|
145
142
|
|
|
146
|
-
Creates a replication stream that can be piped over any streamable transport. `isInitiatorOrStream` can be a boolean indicating whether this instance initiated Noise handshake or another replication stream.
|
|
143
|
+
Creates a replication stream that can be piped over any streamable transport. `isInitiatorOrStream` can be a boolean indicating whether this instance initiated Noise handshake, or another replication stream.
|
|
147
144
|
|
|
148
145
|
### `await db.compact()`
|
|
149
146
|
|
|
150
|
-
Compacts database and removes stale data. This operation is local only and can reduce disk space usage by up to
|
|
147
|
+
Compacts database and removes stale data. This operation is local only and can reduce disk space usage by up to 30x or more depending on the data stored and settings of the database. Compaction happens automatically but you might want to run this periodically, when idle.
|
|
151
148
|
|
|
152
149
|
### `const info = await db.info()`
|
|
153
150
|
|
|
@@ -193,12 +190,6 @@ Same as `stmt.all()` except it only returns the first row.
|
|
|
193
190
|
|
|
194
191
|
Execute a statement with given params and return an object in the form `{ changes: number, lastInsertRowid: number }`.
|
|
195
192
|
|
|
196
|
-
### `await stmt.batch(...params)`
|
|
197
|
-
|
|
198
|
-
Execute a statement with batches of bind params as a single operation.
|
|
199
|
-
|
|
200
|
-
When doing large writes and params are known in advance this can speed up sync by an order of magnitude or more.
|
|
201
|
-
|
|
202
193
|
### `for await (const row of stmt.iterate(...params))`
|
|
203
194
|
|
|
204
195
|
Execute a statement with given params and return rows one by one.
|
package/lib/codecs.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const c = require("compact-encoding")
|
|
2
2
|
const IndexEncoder = require("index-encoder")
|
|
3
|
-
const zlib = require("zlib")
|
|
4
3
|
|
|
5
4
|
const { OPERATION, PAGE_SIZE } = require("./constants")
|
|
6
5
|
|
|
@@ -96,25 +95,3 @@ module.exports.Operation = {
|
|
|
96
95
|
return c.decode(op, operation)
|
|
97
96
|
},
|
|
98
97
|
}
|
|
99
|
-
|
|
100
|
-
module.exports.deflate = async function (buffer, options) {
|
|
101
|
-
return new Promise((resolve, reject) =>
|
|
102
|
-
zlib.deflate(buffer, { chunkSize: PAGE_SIZE, ...options }, (err, result) => {
|
|
103
|
-
if (err) {
|
|
104
|
-
return reject(result)
|
|
105
|
-
}
|
|
106
|
-
resolve(result)
|
|
107
|
-
}),
|
|
108
|
-
)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
module.exports.inflate = async function (buffer, options) {
|
|
112
|
-
return new Promise((resolve, reject) =>
|
|
113
|
-
zlib.inflate(buffer, { chunkSize: PAGE_SIZE, ...options }, (err, result) => {
|
|
114
|
-
if (err) {
|
|
115
|
-
return reject(result)
|
|
116
|
-
}
|
|
117
|
-
resolve(result)
|
|
118
|
-
}),
|
|
119
|
-
)
|
|
120
|
-
}
|
package/lib/statement.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ import Buffer from "bare-buffer"
|
|
|
3
3
|
|
|
4
4
|
interface ParaQLStatement extends ReadyResource {
|
|
5
5
|
readonly sourceSQL: string
|
|
6
|
-
readonly batching: boolean
|
|
7
6
|
|
|
8
7
|
finalize(): Promise<void>
|
|
9
8
|
|
|
@@ -17,8 +16,6 @@ interface ParaQLStatement extends ReadyResource {
|
|
|
17
16
|
|
|
18
17
|
run(...params: ParaQLStatement.Parameters): Promise<ParaQLStatement.RunResult | undefined>
|
|
19
18
|
|
|
20
|
-
batch(...params: ParaQLStatement.Parameters[]): Promise<void>
|
|
21
|
-
|
|
22
19
|
iterate<T extends ParaQLStatement.Row = ParaQLStatement.Row>(
|
|
23
20
|
...params: ParaQLStatement.Parameters
|
|
24
21
|
): AsyncIterableIterator<T>
|
package/lib/statement.js
CHANGED
|
@@ -99,18 +99,6 @@ module.exports = class ParaQLStatement extends ReadyResource {
|
|
|
99
99
|
return this._db._vfs.run(this._sourceSQL, named, positional)
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
async batch(...params) {
|
|
103
|
-
if (this.closed) throw errors.ALREADY_CLOSED()
|
|
104
|
-
|
|
105
|
-
const batch = params.map((p) => {
|
|
106
|
-
const [named, positional] = splitParameters(p)
|
|
107
|
-
|
|
108
|
-
return { sql: this._sourceSQL, named, positional }
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
return this._db._vfs.batch(batch)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
102
|
async *iterate(...params) {
|
|
115
103
|
if (this.closed) throw errors.ALREADY_CLOSED()
|
|
116
104
|
|
package/lib/vfs.js
CHANGED
|
@@ -6,7 +6,7 @@ const ReadyResource = require("ready-resource")
|
|
|
6
6
|
const RocksDB = require("rocksdb-native")
|
|
7
7
|
|
|
8
8
|
const binding = require("../binding")
|
|
9
|
-
const { PageKey, Operation
|
|
9
|
+
const { PageKey, Operation } = require("./codecs")
|
|
10
10
|
const { OPERATION, PAGE_SIZE } = require("./constants")
|
|
11
11
|
const Deferred = require("./deferred")
|
|
12
12
|
const Encryption = require("./encryption")
|
|
@@ -19,8 +19,6 @@ module.exports = class ParaVFS extends ReadyResource {
|
|
|
19
19
|
keyPair = null,
|
|
20
20
|
encrypted = false,
|
|
21
21
|
encryptionKey = null,
|
|
22
|
-
compressed = false,
|
|
23
|
-
compressionLevel = 6,
|
|
24
22
|
} = options
|
|
25
23
|
|
|
26
24
|
super()
|
|
@@ -30,8 +28,6 @@ module.exports = class ParaVFS extends ReadyResource {
|
|
|
30
28
|
this._name = name
|
|
31
29
|
|
|
32
30
|
this._encryption = null
|
|
33
|
-
this._compressed = compressed
|
|
34
|
-
this._compressionLevel = compressionLevel
|
|
35
31
|
|
|
36
32
|
this.store = store.namespace(name)
|
|
37
33
|
this.bee = new Autobee(this.store, key, {
|
|
@@ -76,10 +72,6 @@ module.exports = class ParaVFS extends ReadyResource {
|
|
|
76
72
|
return !!this._encryption
|
|
77
73
|
}
|
|
78
74
|
|
|
79
|
-
get compressed() {
|
|
80
|
-
return !!this._compressed
|
|
81
|
-
}
|
|
82
|
-
|
|
83
75
|
async _open() {
|
|
84
76
|
await this.bee.ready()
|
|
85
77
|
|
|
@@ -114,7 +106,7 @@ module.exports = class ParaVFS extends ReadyResource {
|
|
|
114
106
|
|
|
115
107
|
async _apply(nodes, view, host) {
|
|
116
108
|
for (const node of nodes) {
|
|
117
|
-
const op = Operation.decode(
|
|
109
|
+
const op = Operation.decode(node.value)
|
|
118
110
|
|
|
119
111
|
switch (op.type) {
|
|
120
112
|
case OPERATION.DESERIALIZE: {
|
|
@@ -180,23 +172,11 @@ module.exports = class ParaVFS extends ReadyResource {
|
|
|
180
172
|
}
|
|
181
173
|
|
|
182
174
|
async addWriter(key) {
|
|
183
|
-
await this.bee.append(
|
|
184
|
-
this.compressed
|
|
185
|
-
? await deflate(Operation.encode({ type: OPERATION.WRITER_ADD, key }), {
|
|
186
|
-
level: this._compressionLevel,
|
|
187
|
-
})
|
|
188
|
-
: Operation.encode({ type: OPERATION.WRITER_ADD, key }),
|
|
189
|
-
)
|
|
175
|
+
await this.bee.append(Operation.encode({ type: OPERATION.WRITER_ADD, key }))
|
|
190
176
|
}
|
|
191
177
|
|
|
192
178
|
async removeWriter(key) {
|
|
193
|
-
await this.bee.append(
|
|
194
|
-
this.compressed
|
|
195
|
-
? await deflate(Operation.encode({ type: OPERATION.WRITER_DEL, key }), {
|
|
196
|
-
level: this._compressionLevel,
|
|
197
|
-
})
|
|
198
|
-
: Operation.encode({ type: OPERATION.WRITER_DEL, key }),
|
|
199
|
-
)
|
|
179
|
+
await this.bee.append(Operation.encode({ type: OPERATION.WRITER_DEL, key }))
|
|
200
180
|
}
|
|
201
181
|
|
|
202
182
|
replicate(isInitiator) {
|
|
@@ -240,11 +220,7 @@ module.exports = class ParaVFS extends ReadyResource {
|
|
|
240
220
|
this._interactive = new Deferred()
|
|
241
221
|
try {
|
|
242
222
|
const promise = await this.bee.append(
|
|
243
|
-
|
|
244
|
-
? await deflate(Operation.encode({ type: OPERATION.DESERIALIZE, data }), {
|
|
245
|
-
level: this._compressionLevel,
|
|
246
|
-
})
|
|
247
|
-
: Operation.encode({ type: OPERATION.DESERIALIZE, data }),
|
|
223
|
+
Operation.encode({ type: OPERATION.DESERIALIZE, data }),
|
|
248
224
|
)
|
|
249
225
|
return await this._interactive.promise
|
|
250
226
|
.then(() => promise)
|
|
@@ -260,13 +236,7 @@ module.exports = class ParaVFS extends ReadyResource {
|
|
|
260
236
|
async exec(sql) {
|
|
261
237
|
this._interactive = new Deferred()
|
|
262
238
|
try {
|
|
263
|
-
const promise = this.bee.append(
|
|
264
|
-
this.compressed
|
|
265
|
-
? await deflate(Operation.encode({ type: OPERATION.EXEC, sql }), {
|
|
266
|
-
level: this._compressionLevel,
|
|
267
|
-
})
|
|
268
|
-
: Operation.encode({ type: OPERATION.EXEC, sql }),
|
|
269
|
-
)
|
|
239
|
+
const promise = this.bee.append(Operation.encode({ type: OPERATION.EXEC, sql }))
|
|
270
240
|
await this._interactive.promise
|
|
271
241
|
.then(() => promise)
|
|
272
242
|
.catch(async (err) => {
|
|
@@ -282,11 +252,7 @@ module.exports = class ParaVFS extends ReadyResource {
|
|
|
282
252
|
this._interactive = new Deferred()
|
|
283
253
|
try {
|
|
284
254
|
const promise = this.bee.append(
|
|
285
|
-
|
|
286
|
-
? await deflate(Operation.encode({ type: OPERATION.RUN, sql, named, positional }), {
|
|
287
|
-
level: this._compressionLevel,
|
|
288
|
-
})
|
|
289
|
-
: Operation.encode({ type: OPERATION.RUN, sql, named, positional }),
|
|
255
|
+
Operation.encode({ type: OPERATION.RUN, sql, named, positional }),
|
|
290
256
|
)
|
|
291
257
|
return await this._interactive.promise
|
|
292
258
|
.then(async (result) => {
|
|
@@ -302,20 +268,6 @@ module.exports = class ParaVFS extends ReadyResource {
|
|
|
302
268
|
}
|
|
303
269
|
}
|
|
304
270
|
|
|
305
|
-
async batch(batch) {
|
|
306
|
-
await this.bee.append(
|
|
307
|
-
this.compressed
|
|
308
|
-
? await Promise.all(
|
|
309
|
-
batch.map((op) =>
|
|
310
|
-
deflate(Operation.encode({ type: OPERATION.RUN, ...op }), {
|
|
311
|
-
level: this._compressionLevel,
|
|
312
|
-
}),
|
|
313
|
-
),
|
|
314
|
-
)
|
|
315
|
-
: batch.map((op) => Operation.encode({ type: OPERATION.RUN, ...op })),
|
|
316
|
-
)
|
|
317
|
-
}
|
|
318
|
-
|
|
319
271
|
async _get(name, index) {
|
|
320
272
|
const tmp = name !== this.name
|
|
321
273
|
const key = PageKey.encode([name, index])
|
|
@@ -333,10 +285,6 @@ module.exports = class ParaVFS extends ReadyResource {
|
|
|
333
285
|
value = entry?.value ?? null
|
|
334
286
|
}
|
|
335
287
|
|
|
336
|
-
if (value && this.compressed) {
|
|
337
|
-
value = await inflate(value)
|
|
338
|
-
}
|
|
339
|
-
|
|
340
288
|
return value
|
|
341
289
|
}
|
|
342
290
|
|
|
@@ -364,10 +312,6 @@ module.exports = class ParaVFS extends ReadyResource {
|
|
|
364
312
|
|
|
365
313
|
this._files.set(name, batch)
|
|
366
314
|
|
|
367
|
-
if (this.compressed) {
|
|
368
|
-
value = await deflate(value, { level: this._compressionLevel })
|
|
369
|
-
}
|
|
370
|
-
|
|
371
315
|
if (tmp && this.encrypted) {
|
|
372
316
|
value = this._encryption.encrypt(value, name, index)
|
|
373
317
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bullet./paraql",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Fast, parallel, multi-writer relational database with SQL syntax. Native support for encryption, compression, and vector search.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Tomas Ravinskas",
|
|
7
7
|
"email": "tomas@tomasrav.me",
|
|
@@ -49,10 +49,6 @@
|
|
|
49
49
|
"path": {
|
|
50
50
|
"bare": "bare-path",
|
|
51
51
|
"default": "path"
|
|
52
|
-
},
|
|
53
|
-
"zlib": {
|
|
54
|
-
"bare": "bare-zlib",
|
|
55
|
-
"default": "zlib"
|
|
56
52
|
}
|
|
57
53
|
},
|
|
58
54
|
"homepage": "https://github.com/getbullet-app/paraql",
|
|
@@ -72,11 +68,10 @@
|
|
|
72
68
|
"vector-search"
|
|
73
69
|
],
|
|
74
70
|
"dependencies": {
|
|
75
|
-
"autobee": "^
|
|
71
|
+
"autobee": "^2.7.2",
|
|
76
72
|
"bare-buffer": "^3.6.2",
|
|
77
73
|
"bare-fs": "^4.7.4",
|
|
78
74
|
"bare-path": "^3.1.1",
|
|
79
|
-
"bare-zlib": "^1.4.1",
|
|
80
75
|
"compact-encoding": "^3.3.0",
|
|
81
76
|
"index-encoder": "^3.5.0",
|
|
82
77
|
"ready-resource": "^1.2.0",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|