@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 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. This is not very fast, however existing writers typically have limited activity and newcomers can simply fast-forward to the latest view, making overall performance acceptable.
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 should also run on Node. Because Bare is multi-platform, ParaQL should run on recent versions of Android, iOS, macOS, Linux, and Windows.
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 15x or more depending on the data stored and settings of the database. You should run this periodically when idle.
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
- }
@@ -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, deflate, inflate } = require("./codecs")
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(this.compressed ? await inflate(node.value) : node.value)
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
- this.compressed
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
- this.compressed
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.2.0",
4
- "description": "Parallel multi-writer relational database with SQL syntax. Native support for encryption, compression, and vector search.",
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": "^1.0.10",
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",