@bullet./paraql 0.1.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/binding.js ADDED
@@ -0,0 +1,3 @@
1
+ require.addon = require("require-addon")
2
+
3
+ module.exports = require.addon(".", __filename)
package/index.js ADDED
@@ -0,0 +1,148 @@
1
+ const { Buffer } = require("buffer")
2
+ const ReadyResource = require("ready-resource")
3
+
4
+ const binding = require("./binding")
5
+ const errors = require("./lib/errors")
6
+ const ParaQLStatement = require("./lib/statement")
7
+ const ParaVFS = require("./lib/vfs")
8
+
9
+ module.exports = exports = class ParaQL extends ReadyResource {
10
+ constructor(store, key = null, options = {}) {
11
+ if (isObject(key)) {
12
+ options = key
13
+ key = null
14
+ }
15
+
16
+ const { cacheSize = 1024, ...opts } = options
17
+
18
+ super()
19
+
20
+ this._handle = null
21
+ this._vfs = new ParaVFS(this, store, key, opts)
22
+ this._cacheSize = cacheSize
23
+ this._cache = new Map()
24
+ }
25
+
26
+ get name() {
27
+ return this._vfs.name
28
+ }
29
+
30
+ get key() {
31
+ return this._vfs.key
32
+ }
33
+
34
+ get local() {
35
+ return this._vfs.local
36
+ }
37
+
38
+ get discoveryKey() {
39
+ return this._vfs.discoveryKey
40
+ }
41
+
42
+ get encryptionKey() {
43
+ return this._vfs.encryptionKey
44
+ }
45
+
46
+ get writable() {
47
+ return this._vfs.writable
48
+ }
49
+
50
+ get encrypted() {
51
+ return this._vfs.encrypted
52
+ }
53
+
54
+ async _open() {
55
+ await this._vfs.ready()
56
+
57
+ try {
58
+ this._handle = await binding.open("paraql.db")
59
+ } catch (err) {
60
+ throw errors.from(err)
61
+ }
62
+ }
63
+
64
+ async _close() {
65
+ if (this.opened) {
66
+ try {
67
+ for (const stmt of this._cache.values()) {
68
+ await stmt.finalize()
69
+ }
70
+
71
+ this._cache.clear()
72
+ await binding.close(this._handle)
73
+ } catch (err) {
74
+ throw errors.from(err)
75
+ }
76
+ }
77
+
78
+ await this._vfs.close()
79
+ }
80
+
81
+ async addWriter(key) {
82
+ await this._vfs.addWriter(key)
83
+ }
84
+
85
+ async removeWriter(key) {
86
+ await this._vfs.removeWriter(key)
87
+ }
88
+
89
+ replicate(isInitiator) {
90
+ return this._vfs.replicate(isInitiator)
91
+ }
92
+
93
+ async compact() {
94
+ return this._vfs.compact()
95
+ }
96
+
97
+ async info() {
98
+ return this._vfs.info()
99
+ }
100
+
101
+ async _exec(sql) {
102
+ try {
103
+ await binding.exec(this._handle, sql)
104
+ } catch (err) {
105
+ throw errors.from(err)
106
+ }
107
+ }
108
+
109
+ async exec(sql) {
110
+ if (!this.opened) await this.ready()
111
+
112
+ if (this.closed) throw errors.ALREADY_CLOSED()
113
+
114
+ await this._vfs.exec(sql)
115
+ }
116
+
117
+ async prepare(sql) {
118
+ if (!this.opened) await this.ready()
119
+
120
+ if (this.closed) throw errors.ALREADY_CLOSED()
121
+
122
+ let stmt = this._cache.get(sql)
123
+
124
+ if (stmt) {
125
+ this._cache.delete(sql)
126
+ this._cache.set(sql, stmt)
127
+ return stmt
128
+ }
129
+
130
+ stmt = new ParaQLStatement(this, sql)
131
+
132
+ await stmt.ready()
133
+
134
+ this._cache.set(sql, stmt)
135
+
136
+ if (this._cache.size > this._cacheSize) {
137
+ const oldest = this._cache.values().next().value
138
+
139
+ await oldest.finalize()
140
+ }
141
+
142
+ return stmt
143
+ }
144
+ }
145
+
146
+ function isObject(o) {
147
+ return typeof o === "object" && o && !Buffer.isBuffer(o)
148
+ }
package/lib/codecs.js ADDED
@@ -0,0 +1,138 @@
1
+ const c = require("compact-encoding")
2
+ const IndexEncoder = require("index-encoder")
3
+ const zlib = require("zlib")
4
+
5
+ const { OPERATION, PAGE_SIZE } = require("./constants")
6
+
7
+ const op = {
8
+ preencode(state, operation) {
9
+ c.uint8.preencode(state, operation.type)
10
+
11
+ switch (operation.type) {
12
+ case OPERATION.WRITER_ADD:
13
+ case OPERATION.WRITER_DEL: {
14
+ c.fixed32.preencode(state, operation.key)
15
+ break
16
+ }
17
+ case OPERATION.EXEC: {
18
+ c.string.preencode(state, operation.sql)
19
+ break
20
+ }
21
+ case OPERATION.RUN: {
22
+ c.string.preencode(state, operation.sql)
23
+ c.any.preencode(state, operation.named)
24
+ c.any.preencode(state, operation.positional)
25
+ break
26
+ }
27
+ case OPERATION.STMT: {
28
+ c.string.preencode(state, operation.sql)
29
+ }
30
+ case OPERATION.BATCH: {
31
+ c.any.preencode(state, operation.named)
32
+ c.any.preencode(state, operation.positional)
33
+ }
34
+ case OPERATION.FLUSH: {
35
+ break
36
+ }
37
+ }
38
+ },
39
+ encode(state, operation) {
40
+ c.uint8.encode(state, operation.type)
41
+
42
+ switch (operation.type) {
43
+ case OPERATION.WRITER_ADD:
44
+ case OPERATION.WRITER_DEL: {
45
+ c.fixed32.encode(state, operation.key)
46
+ break
47
+ }
48
+ case OPERATION.EXEC: {
49
+ c.string.encode(state, operation.sql)
50
+ break
51
+ }
52
+ case OPERATION.RUN: {
53
+ c.string.encode(state, operation.sql)
54
+ c.any.encode(state, operation.named)
55
+ c.any.encode(state, operation.positional)
56
+ break
57
+ }
58
+ case OPERATION.STMT: {
59
+ c.string.encode(state, operation.sql)
60
+ }
61
+ case OPERATION.BATCH: {
62
+ c.any.encode(state, operation.named)
63
+ c.any.encode(state, operation.positional)
64
+ }
65
+ case OPERATION.FLUSH: {
66
+ break
67
+ }
68
+ }
69
+ },
70
+ decode(state) {
71
+ const type = c.uint8.decode(state)
72
+
73
+ const operation = { type }
74
+
75
+ switch (type) {
76
+ case OPERATION.WRITER_ADD:
77
+ case OPERATION.WRITER_DEL: {
78
+ operation.key = c.fixed32.decode(state)
79
+ break
80
+ }
81
+ case OPERATION.EXEC: {
82
+ operation.sql = c.string.decode(state)
83
+ break
84
+ }
85
+ case OPERATION.RUN: {
86
+ operation.sql = c.string.decode(state)
87
+ operation.named = c.any.decode(state)
88
+ operation.positional = c.any.decode(state)
89
+ break
90
+ }
91
+ case OPERATION.STMT: {
92
+ operation.sql = c.string.decode(state)
93
+ }
94
+ case OPERATION.BATCH: {
95
+ operation.named = c.any.decode(state)
96
+ operation.positional = c.any.decode(state)
97
+ }
98
+ case OPERATION.FLUSH: {
99
+ break
100
+ }
101
+ }
102
+
103
+ return operation
104
+ },
105
+ }
106
+
107
+ module.exports.PageKey = new IndexEncoder([IndexEncoder.STRING, IndexEncoder.UINT])
108
+
109
+ module.exports.Operation = {
110
+ encode(operation) {
111
+ return c.encode(op, operation)
112
+ },
113
+ decode(operation) {
114
+ return c.decode(op, operation)
115
+ },
116
+ }
117
+
118
+ module.exports.deflate = async function (buffer, options) {
119
+ return new Promise((resolve, reject) =>
120
+ zlib.deflate(buffer, { chunkSize: PAGE_SIZE, ...options }, (err, result) => {
121
+ if (err) {
122
+ return reject(result)
123
+ }
124
+ resolve(result)
125
+ }),
126
+ )
127
+ }
128
+
129
+ module.exports.inflate = async function (buffer, options) {
130
+ return new Promise((resolve, reject) =>
131
+ zlib.inflate(buffer, { chunkSize: PAGE_SIZE, ...options }, (err, result) => {
132
+ if (err) {
133
+ return reject(result)
134
+ }
135
+ resolve(result)
136
+ }),
137
+ )
138
+ }
@@ -0,0 +1,10 @@
1
+ module.exports.PAGE_SIZE = 4096
2
+ module.exports.OPERATION = {
3
+ WRITER_ADD: 1,
4
+ WRITER_DEL: 2,
5
+ EXEC: 8,
6
+ RUN: 9,
7
+ STMT: 10,
8
+ BATCH: 11,
9
+ FLUSH: 12,
10
+ }
@@ -0,0 +1,8 @@
1
+ module.exports = class Deferred {
2
+ constructor() {
3
+ this.promise = new Promise((resolve, reject) => {
4
+ this.resolve = resolve
5
+ this.reject = reject
6
+ })
7
+ }
8
+ }
@@ -0,0 +1,39 @@
1
+ const { Buffer } = require("buffer")
2
+ const c = require("compact-encoding")
3
+ const sodium = require("sodium-native")
4
+
5
+ const errors = require("./errors")
6
+
7
+ module.exports = class ParaQLEncryption {
8
+ constructor(key, encryptionKey) {
9
+ if (
10
+ !Buffer.isBuffer(encryptionKey)
11
+ || encryptionKey.byteLength !== sodium.crypto_stream_KEYBYTES
12
+ ) {
13
+ throw errors.INVALID_ARGUMENT("`encryptioKey` must be 32 byte buffer")
14
+ }
15
+
16
+ this._key = key
17
+ this._encryptionKey = encryptionKey
18
+ }
19
+
20
+ _nonce(name, index) {
21
+ const _name = Buffer.from(name)
22
+ const _index = c.encode(c.uint64, index)
23
+ const result = Buffer.alloc(sodium.crypto_stream_NONCEBYTES)
24
+
25
+ sodium.crypto_generichash(result, Buffer.concat([this._key, _name, _index]))
26
+
27
+ return result
28
+ }
29
+
30
+ encrypt(page, filename, index) {
31
+ sodium.crypto_stream_xor(page, page, this._nonce(filename, index), this._encryptionKey)
32
+ return page
33
+ }
34
+
35
+ decrypt(page, filename, index) {
36
+ sodium.crypto_stream_xor(page, page, this._nonce(filename, index), this._encryptionKey)
37
+ return page
38
+ }
39
+ }
package/lib/errors.js ADDED
@@ -0,0 +1,35 @@
1
+ module.exports = class ParaQLError extends Error {
2
+ constructor(message, fn = ParaQLError, code = fn.name) {
3
+ super(`${code}: ${message}`)
4
+
5
+ this.code = code
6
+
7
+ if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
8
+ }
9
+
10
+ get name() {
11
+ return "ParaQLError"
12
+ }
13
+
14
+ static INTERNAL(message) {
15
+ return new ParaQLError(message, ParaQLError.INTERNAL)
16
+ }
17
+
18
+ static ALREADY_CLOSED(message = "Database has already been closed") {
19
+ return new ParaQLError(message, ParaQLError.ALREADY_CLOSED)
20
+ }
21
+
22
+ static INVALID_ARGUMENT(message) {
23
+ return new ParaQLError(message, ParaQLError.INVALID_ARGUMENT)
24
+ }
25
+
26
+ static from(err) {
27
+ if (err instanceof ParaQLError) return err
28
+
29
+ if (err instanceof TypeError) {
30
+ return ParaQLError.INVALID_ARGUMENT(err.message)
31
+ }
32
+
33
+ return new ParaQLError(err.message, ParaQLError.from, err.code || "ERROR")
34
+ }
35
+ }
@@ -0,0 +1,55 @@
1
+ import ReadyResource from "ready-resource"
2
+ import Buffer from "bare-buffer"
3
+
4
+ interface ParaQLStatement extends ReadyResource {
5
+ readonly sourceSQL: string
6
+ readonly batching: boolean
7
+
8
+ batch(on: boolean): void
9
+
10
+ flush(): Promise<ParaQLStatement.FlushResult>
11
+
12
+ finalize(): Promise<void>
13
+
14
+ all<T extends ParaQLStatement.Row = ParaQLStatement.Row>(
15
+ ...params: ParaQLStatement.Parameters
16
+ ): Promise<T[]>
17
+
18
+ get<T extends ParaQLStatement.Row = ParaQLStatement.Row>(
19
+ ...params: ParaQLStatement.Parameters
20
+ ): Promise<T | undefined>
21
+
22
+ run(...params: ParaQLStatement.Parameters): Promise<ParaQLStatement.RunResult | undefined>
23
+
24
+ iterate<T extends ParaQLStatement.Row = ParaQLStatement.Row>(
25
+ ...params: ParaQLStatement.Parameters
26
+ ): AsyncIterableIterator<T>
27
+ }
28
+
29
+ declare class ParaQLStatement {}
30
+
31
+ declare namespace ParaQLStatement {
32
+ export type Value = null | number | string | Buffer
33
+
34
+ export type BindValue =
35
+ null | undefined | number | bigint | string | ArrayBuffer | ArrayBufferView
36
+
37
+ export type Row = Record<string, Value>
38
+
39
+ export type NamedParameters = Record<string, BindValue>
40
+
41
+ export type Parameters = [NamedParameters, ...BindValue[]] | BindValue[]
42
+
43
+ export interface RunResult {
44
+ changes: number
45
+ lastInsertRowid: number
46
+ }
47
+
48
+ export interface FlushResult {
49
+ changes: number
50
+ lastInsertRowid: number
51
+ errors: number
52
+ }
53
+ }
54
+
55
+ export = ParaQLStatement
@@ -0,0 +1,168 @@
1
+ const { Buffer } = require("buffer")
2
+ const ReadyResource = require("ready-resource")
3
+
4
+ const binding = require("../binding")
5
+ const errors = require("./errors")
6
+
7
+ module.exports = class ParaQLStatement extends ReadyResource {
8
+ constructor(db, sql) {
9
+ super()
10
+
11
+ this._db = db
12
+ this._sourceSQL = sql
13
+ this._handle = null
14
+ this._queue = null
15
+ }
16
+
17
+ async _open() {
18
+ try {
19
+ this._handle = await binding.prepare(this._db._handle, this._sourceSQL)
20
+ } catch (err) {
21
+ throw errors.from(err)
22
+ }
23
+ }
24
+
25
+ async _close() {
26
+ if (this.opened) {
27
+ try {
28
+ await binding.finalize(this._handle)
29
+ this._handle = null
30
+ this._queue = null
31
+ this._db._cache.delete(this._sourceSQL)
32
+ } catch (err) {
33
+ throw errors.from(err)
34
+ }
35
+ }
36
+ }
37
+
38
+ get sourceSQL() {
39
+ return this._sourceSQL
40
+ }
41
+
42
+ get batching() {
43
+ return !!this._queue
44
+ }
45
+
46
+ batch(on) {
47
+ if (on === false) {
48
+ this._queue = null
49
+ } else {
50
+ this._queue = this._queue ?? []
51
+ }
52
+ }
53
+
54
+ async finalize() {
55
+ await this.close()
56
+ }
57
+
58
+ async flush() {
59
+ const queue = this._queue
60
+ this._queue = []
61
+ return this._db._vfs.flush(this._sourceSQL, queue)
62
+ }
63
+
64
+ async _bind(named, positional) {
65
+ for (const key in named) {
66
+ if (Array.isArray(named[key])) named[key] = JSON.stringify(named[key])
67
+ }
68
+
69
+ for (let i = 0; i < positional.length; i++) {
70
+ if (Array.isArray(positional[i])) positional[i] = JSON.stringify(positional[i])
71
+ }
72
+
73
+ return binding.bind(this._handle, named, positional)
74
+ }
75
+
76
+ async all(...params) {
77
+ if (this.closed) throw errors.ALREADY_CLOSED()
78
+
79
+ const rows = []
80
+
81
+ for await (const row of this.iterate(...params)) {
82
+ rows.push(row)
83
+ }
84
+
85
+ return rows
86
+ }
87
+
88
+ async get(...params) {
89
+ if (this.closed) throw errors.ALREADY_CLOSED()
90
+
91
+ const [named, positional] = splitParameters(params)
92
+
93
+ try {
94
+ await this._bind(named, positional)
95
+ const row = await binding.step(this._handle)
96
+ return row === undefined ? undefined : wrapBlobRow(row)
97
+ } catch (err) {
98
+ throw errors.from(err)
99
+ } finally {
100
+ await binding.reset(this._handle)
101
+ }
102
+ }
103
+
104
+ async _run(named, positional) {
105
+ try {
106
+ await this._bind(named, positional)
107
+ return await binding.run(this._handle)
108
+ } catch (err) {
109
+ throw errors.from(err)
110
+ } finally {
111
+ await binding.reset(this._handle)
112
+ }
113
+ }
114
+
115
+ async run(...params) {
116
+ if (this.closed) throw errors.ALREADY_CLOSED()
117
+
118
+ const [named, positional] = splitParameters(params)
119
+
120
+ if (this._queue) {
121
+ this._queue.push({ named, positional })
122
+ return null
123
+ } else {
124
+ return this._db._vfs.run(this._sourceSQL, named, positional)
125
+ }
126
+ }
127
+
128
+ async *iterate(...params) {
129
+ if (this.closed) throw errors.ALREADY_CLOSED()
130
+
131
+ const [named, positional] = splitParameters(params)
132
+
133
+ try {
134
+ await this._bind(named, positional)
135
+
136
+ let row
137
+ while ((row = await binding.step(this._handle)) !== undefined) {
138
+ yield wrapBlobRow(row)
139
+ }
140
+ } catch (err) {
141
+ throw errors.from(err)
142
+ } finally {
143
+ await binding.reset(this._handle)
144
+ }
145
+ }
146
+ }
147
+
148
+ function wrapBlobRow(row) {
149
+ for (const key in row) {
150
+ if (row[key] instanceof ArrayBuffer) row[key] = Buffer.from(row[key])
151
+ }
152
+ return row
153
+ }
154
+
155
+ function splitParameters(params) {
156
+ if (params.length === 0) return [null, params]
157
+ if (!isNamedParameters(params[0])) return [null, params]
158
+ return [params[0], params.slice(1)]
159
+ }
160
+
161
+ function isNamedParameters(value) {
162
+ if (value === null) return false
163
+ if (typeof value !== "object") return false
164
+ if (Array.isArray(value)) return false
165
+ if (ArrayBuffer.isView(value)) return false
166
+ if (value instanceof ArrayBuffer) return false
167
+ return true
168
+ }