presidium-db-client-server 0.0.0-stage → 0.0.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/ArrayEventEmitter.js +17 -0
- package/AsyncIterator.js +34 -0
- package/BinaryEntriesStream.js +71 -0
- package/BinaryStream.js +50 -0
- package/DatabaseHandler.js +288 -0
- package/DatabaseServer.js +97 -0
- package/DiskHashTableClient.js +220 -0
- package/DiskSortedHashTableClient.js +317 -0
- package/TextEntriesStream.js +69 -0
- package/TextStream.js +51 -0
- package/buffersToEntry.js +40 -0
- package/handleServerResponse.js +68 -0
- package/package.json +39 -4
- package/README.md +0 -3
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const stream = require("stream")
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @name ArrayEventEmitter
|
|
5
|
+
*
|
|
6
|
+
* @docs
|
|
7
|
+
* ```specscript
|
|
8
|
+
* module EventEmitter "./specscript/EventEmitter.ss"
|
|
9
|
+
*
|
|
10
|
+
* ArrayEventEmitter(array Array<string|Buffer>) -> EventEmitter
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
function ArrayEventEmitter(array) {
|
|
14
|
+
return stream.Readable.from(array)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = ArrayEventEmitter
|
package/AsyncIterator.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const AsyncIterator = {}
|
|
2
|
+
|
|
3
|
+
function sleep(time) {
|
|
4
|
+
return new Promise(resolve => setTimeout(resolve, time))
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @name fromEvents
|
|
9
|
+
*
|
|
10
|
+
* @docs
|
|
11
|
+
* ```specscript
|
|
12
|
+
* module EventEmitter "./specscript/EventEmitter.ss"
|
|
13
|
+
*
|
|
14
|
+
* fromEvents(events EventEmitter) -> AsyncIterator
|
|
15
|
+
* fromEvents(events EventEmitter, dataEventName string) -> AsyncIterator
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
async function* fromEvents(events, dataEventName = "data") {
|
|
19
|
+
let buffer = []
|
|
20
|
+
events.on(dataEventName, data => buffer.push(data))
|
|
21
|
+
let streaming = true
|
|
22
|
+
events.on("end", () => (streaming = false))
|
|
23
|
+
while (streaming || buffer.length > 0) {
|
|
24
|
+
while (buffer.length > 0) {
|
|
25
|
+
yield* buffer
|
|
26
|
+
buffer = []
|
|
27
|
+
}
|
|
28
|
+
await sleep(1)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
AsyncIterator.fromEvents = fromEvents
|
|
33
|
+
|
|
34
|
+
module.exports = AsyncIterator
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const EventEmitter = require("events")
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @name BinaryEntriesStream
|
|
5
|
+
*
|
|
6
|
+
* @docs
|
|
7
|
+
* ```coffeescript [specscript]
|
|
8
|
+
* module stream 'https://nodejs.org/docs/latest-v26.x/api/stream.html'
|
|
9
|
+
* module events 'https://nodejs.org/docs/latest-v26.x/api/events.html'
|
|
10
|
+
*
|
|
11
|
+
* BinaryEntriesStream(
|
|
12
|
+
* readable stream.Readable,
|
|
13
|
+
* entryLength number,
|
|
14
|
+
* delimiter string
|
|
15
|
+
* ) -> binaryEntriesStream events.EventEmitter
|
|
16
|
+
*
|
|
17
|
+
* binaryEntriesStream.on("entry", (entry Array<Buffer>, keyEncoding string) -> ())
|
|
18
|
+
*
|
|
19
|
+
* binaryEntriesStream.on("end", () -> ())
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
class BinaryEntriesStream extends EventEmitter {
|
|
23
|
+
constructor(readable, entryLength, delimiter, encoding) {
|
|
24
|
+
super()
|
|
25
|
+
|
|
26
|
+
delimiter ??= ""
|
|
27
|
+
encoding ??= "utf8"
|
|
28
|
+
|
|
29
|
+
let buffer = Buffer.from("")
|
|
30
|
+
let entry = []
|
|
31
|
+
|
|
32
|
+
readable.on('data', (chunk, encoding) => {
|
|
33
|
+
if (typeof chunk == 'string') {
|
|
34
|
+
buffer = Buffer.concat([buffer, Buffer.from(chunk, encoding)])
|
|
35
|
+
} else {
|
|
36
|
+
buffer = Buffer.concat([buffer, chunk])
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let delimiterIndex = buffer.indexOf(delimiter)
|
|
40
|
+
while (delimiterIndex > -1) {
|
|
41
|
+
const slice = buffer.slice(0, delimiterIndex)
|
|
42
|
+
entry.push(slice)
|
|
43
|
+
if (entry.length == entryLength) {
|
|
44
|
+
this.emit("entry", entry, encoding)
|
|
45
|
+
entry = []
|
|
46
|
+
}
|
|
47
|
+
buffer = buffer.slice(delimiterIndex + 1)
|
|
48
|
+
delimiterIndex = buffer.indexOf(delimiter)
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
readable.on('end', () => {
|
|
53
|
+
if (buffer.length > 0) {
|
|
54
|
+
entry.push(buffer)
|
|
55
|
+
}
|
|
56
|
+
if (entry.length == entryLength) {
|
|
57
|
+
this.emit("entry", entry, encoding)
|
|
58
|
+
this.emit("end")
|
|
59
|
+
entry = []
|
|
60
|
+
} else if (entry.length > 0) {
|
|
61
|
+
const error = new Error("last entry")
|
|
62
|
+
error.entry = entry
|
|
63
|
+
this.emit("error", error)
|
|
64
|
+
} else {
|
|
65
|
+
this.emit("end")
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = BinaryEntriesStream
|
package/BinaryStream.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const EventEmitter = require("events")
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @name BinaryStream
|
|
5
|
+
*
|
|
6
|
+
* @docs
|
|
7
|
+
* ```coffeescript [specscript]
|
|
8
|
+
* module stream 'https://nodejs.org/docs/latest-v26.x/api/stream.html'
|
|
9
|
+
* module events 'https://nodejs.org/docs/latest-v26.x/api/events.html'
|
|
10
|
+
*
|
|
11
|
+
* BinaryStream(readable stream.Readable, delimiter string) -> binaryStream events.EventEmitter
|
|
12
|
+
*
|
|
13
|
+
* textStream.on("data", chunk Buffer -> ())
|
|
14
|
+
*
|
|
15
|
+
* textStream.on("end", () -> ())
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
class BinaryStream extends EventEmitter {
|
|
19
|
+
constructor(readable, delimiter) {
|
|
20
|
+
super()
|
|
21
|
+
|
|
22
|
+
delimiter ??= ""
|
|
23
|
+
|
|
24
|
+
let buffer = Buffer.from("")
|
|
25
|
+
readable.on('data', (chunk, encoding) => {
|
|
26
|
+
if (typeof chunk == 'string') {
|
|
27
|
+
buffer = Buffer.concat([buffer, Buffer.from(chunk, encoding)])
|
|
28
|
+
} else {
|
|
29
|
+
buffer = Buffer.concat([buffer, chunk])
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let delimiterIndex = buffer.indexOf(delimiter)
|
|
33
|
+
while (delimiterIndex > -1) {
|
|
34
|
+
const slice = buffer.slice(0, delimiterIndex)
|
|
35
|
+
this.emit("data", slice)
|
|
36
|
+
buffer = buffer.slice(delimiterIndex + 1)
|
|
37
|
+
delimiterIndex = buffer.indexOf(delimiter)
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
readable.on('end', () => {
|
|
42
|
+
if (buffer.length > 0) {
|
|
43
|
+
this.emit("data", buffer)
|
|
44
|
+
}
|
|
45
|
+
this.emit("end")
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = BinaryStream
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
require("rubico/global")
|
|
2
|
+
const DiskHashTable = require("presidium/DiskHashTable")
|
|
3
|
+
const DiskSortedHashTable = require("presidium/DiskSortedHashTable")
|
|
4
|
+
const Readable = require("presidium/Readable")
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @name DatabaseHandler
|
|
8
|
+
*
|
|
9
|
+
* @docs
|
|
10
|
+
* ```specscript
|
|
11
|
+
* module DiskHashTable "https://presidium.services/docs/DiskHashTable"
|
|
12
|
+
* module HTTP "https://presidium.services/docs/HTTP"
|
|
13
|
+
*
|
|
14
|
+
* DatabaseHandler(options {
|
|
15
|
+
* databasePath: string,
|
|
16
|
+
* manifestTableName: string,
|
|
17
|
+
* manifestTableInitialLength: number,
|
|
18
|
+
* manifestTableItemSize: number,
|
|
19
|
+
* defaultTableInitialLength: number,
|
|
20
|
+
* defaultTableItemSize: number,
|
|
21
|
+
* manifestHt: DiskHashTable,
|
|
22
|
+
* hts: Map<tableName string, DiskHashTable>,
|
|
23
|
+
* }) -> databaseHandler HTTP.AsyncHandler
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function DatabaseHandler(options) {
|
|
27
|
+
const {
|
|
28
|
+
databasePath,
|
|
29
|
+
manifestTableName,
|
|
30
|
+
manifestTableInitialLength,
|
|
31
|
+
manifestTableItemSize,
|
|
32
|
+
defaultTableInitialLength,
|
|
33
|
+
defaultTableItemSize,
|
|
34
|
+
manifestHt,
|
|
35
|
+
hts,
|
|
36
|
+
} = options
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @name getHt
|
|
40
|
+
*
|
|
41
|
+
* @docs
|
|
42
|
+
* ```specscript
|
|
43
|
+
* module HTTP "https://presidium.services/docs/HTTP"
|
|
44
|
+
*
|
|
45
|
+
* getHt(tableName string, request HTTP.Request) -> ht DiskHashTable|DiskSortedHashTable
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
async function getHt(tableName, request, manifestHt) {
|
|
49
|
+
const htConfigString = await manifestHt.get(tableName)
|
|
50
|
+
let ht
|
|
51
|
+
if (htConfigString) {
|
|
52
|
+
const config = JSON.parse(htConfigString)
|
|
53
|
+
if (request.headers["client-name"] == "DiskHashTableClient") {
|
|
54
|
+
ht = new DiskHashTable(config)
|
|
55
|
+
await ht.init()
|
|
56
|
+
hts.set(tableName, ht)
|
|
57
|
+
} else if (request.headers["client-name"] == "DiskSortedHashTableClient") {
|
|
58
|
+
ht = new DiskSortedHashTable(config)
|
|
59
|
+
await ht.init()
|
|
60
|
+
hts.set(tableName, ht)
|
|
61
|
+
} else {
|
|
62
|
+
const error = new Error("Forbidden: Client-Name")
|
|
63
|
+
error.code = 403
|
|
64
|
+
throw error
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
if (request.headers["client-name"] == "DiskHashTableClient") {
|
|
68
|
+
const config = {
|
|
69
|
+
storagePath: `${databasePath}/${tableName}/storage`,
|
|
70
|
+
headerPath: `${databasePath}/${tableName}/header`,
|
|
71
|
+
initialLength: defaultTableInitialLength,
|
|
72
|
+
itemSize: defaultTableItemSize,
|
|
73
|
+
}
|
|
74
|
+
ht = new DiskHashTable(config)
|
|
75
|
+
await manifestHt.set(tableName, JSON.stringify(config))
|
|
76
|
+
await ht.init()
|
|
77
|
+
hts.set(tableName, ht)
|
|
78
|
+
} else if (request.headers["client-name"] == "DiskSortedHashTableClient") {
|
|
79
|
+
const config = {
|
|
80
|
+
storagePath: `${databasePath}/${tableName}/storage`,
|
|
81
|
+
headerPath: `${databasePath}/${tableName}/header`,
|
|
82
|
+
initialLength: defaultTableInitialLength,
|
|
83
|
+
itemSize: defaultTableItemSize,
|
|
84
|
+
sortValueType: request.headers["sort-value-type"],
|
|
85
|
+
degree: Number(request.headers["degree"]),
|
|
86
|
+
}
|
|
87
|
+
ht = new DiskSortedHashTable(config)
|
|
88
|
+
await manifestHt.set(tableName, JSON.stringify(config))
|
|
89
|
+
await ht.init()
|
|
90
|
+
hts.set(tableName, ht)
|
|
91
|
+
} else {
|
|
92
|
+
const error = new Error("Forbidden: Client-Name")
|
|
93
|
+
error.code = 403
|
|
94
|
+
throw error
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return ht
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return async function databaseHandler(request, response) {
|
|
101
|
+
const { pathname } = new URL(`http://*${request.url}`)
|
|
102
|
+
request.path = decodeURIComponent(
|
|
103
|
+
pathname == "/" ? pathname
|
|
104
|
+
: pathname.endsWith("/") ? pathname.slice(0, -1)
|
|
105
|
+
: pathname
|
|
106
|
+
)
|
|
107
|
+
request.pathSegments = request.path.slice(1).split("/")
|
|
108
|
+
request.pathDepth = request.pathSegments.length
|
|
109
|
+
|
|
110
|
+
if (request.pathDepth == 2) {
|
|
111
|
+
const tableName = request.pathSegments[0]
|
|
112
|
+
const fname = request.pathSegments[1]
|
|
113
|
+
const ht = hts.get(tableName)
|
|
114
|
+
?? await getHt(tableName, request, manifestHt)
|
|
115
|
+
|
|
116
|
+
await switchCase(
|
|
117
|
+
fname,
|
|
118
|
+
|
|
119
|
+
eq("clear"), async () => {
|
|
120
|
+
await ht.clear()
|
|
121
|
+
response.writeHead(200, {
|
|
122
|
+
"Content-Type": "text/plain",
|
|
123
|
+
})
|
|
124
|
+
response.end("OK")
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
eq("set"), async () => {
|
|
128
|
+
const { key, value, sortValue } = await Readable.JSON(request)
|
|
129
|
+
await ht.set(key, value, sortValue)
|
|
130
|
+
response.writeHead(200, {
|
|
131
|
+
"Content-Type": "text/plain",
|
|
132
|
+
})
|
|
133
|
+
response.end("OK")
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
eq("get"), async () => {
|
|
137
|
+
const { key } = await Readable.JSON(request)
|
|
138
|
+
const value = await ht.get(key)
|
|
139
|
+
if (value === undefined) {
|
|
140
|
+
response.writeHead(499, {
|
|
141
|
+
"Content-Type": "text/plain",
|
|
142
|
+
})
|
|
143
|
+
response.end("Resource Not Found")
|
|
144
|
+
} else {
|
|
145
|
+
response.writeHead(200, {
|
|
146
|
+
"Content-Type": "text/plain",
|
|
147
|
+
})
|
|
148
|
+
response.end(value)
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
eq("getBinary"), async () => {
|
|
153
|
+
const { key } = await Readable.JSON(request)
|
|
154
|
+
const value = await ht.getBinary(key)
|
|
155
|
+
response.writeHead(200, {
|
|
156
|
+
"Content-Type": "application/octet-stream",
|
|
157
|
+
})
|
|
158
|
+
response.end(value)
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
eq("delete"), async () => {
|
|
162
|
+
const { key } = await Readable.JSON(request)
|
|
163
|
+
const didDelete = await ht.delete(key)
|
|
164
|
+
response.writeHead(200, {
|
|
165
|
+
"Content-Type": "text/plain",
|
|
166
|
+
})
|
|
167
|
+
response.end(String(didDelete))
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
eq("count"), async () => {
|
|
171
|
+
const count = ht.count()
|
|
172
|
+
response.writeHead(200, {
|
|
173
|
+
"Content-Type": "text/plain",
|
|
174
|
+
})
|
|
175
|
+
response.end(String(count))
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
eq("iterator"), async () => {
|
|
179
|
+
const options = await Readable.JSON(request)
|
|
180
|
+
response.writeHead(200, {
|
|
181
|
+
"Content-Type": "application/octet-stream",
|
|
182
|
+
"Content-Encoding": "utf8",
|
|
183
|
+
})
|
|
184
|
+
const count = ht.count()
|
|
185
|
+
for await (const value of ht.iterator(options)) {
|
|
186
|
+
response.write(value)
|
|
187
|
+
response.write("")
|
|
188
|
+
}
|
|
189
|
+
response.end()
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
eq("entriesIterator"), async () => {
|
|
193
|
+
const options = await Readable.JSON(request)
|
|
194
|
+
response.writeHead(200, {
|
|
195
|
+
"Content-Type": "application/octet-stream",
|
|
196
|
+
"Content-Encoding": "utf8",
|
|
197
|
+
})
|
|
198
|
+
const count = ht.count()
|
|
199
|
+
for await (const [key, value] of ht.entriesIterator(options)) {
|
|
200
|
+
response.write(key)
|
|
201
|
+
response.write("")
|
|
202
|
+
response.write(value)
|
|
203
|
+
response.write("")
|
|
204
|
+
}
|
|
205
|
+
response.end()
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
eq("forwardIterator"), async () => {
|
|
209
|
+
const options = await Readable.JSON(request)
|
|
210
|
+
response.writeHead(200, {
|
|
211
|
+
"Content-Type": "application/octet-stream",
|
|
212
|
+
"Content-Encoding": "utf8",
|
|
213
|
+
})
|
|
214
|
+
const count = ht.count()
|
|
215
|
+
for await (const value of ht.forwardIterator(options)) {
|
|
216
|
+
response.write(value)
|
|
217
|
+
response.write("")
|
|
218
|
+
}
|
|
219
|
+
response.end()
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
eq("reverseIterator"), async () => {
|
|
223
|
+
const options = await Readable.JSON(request)
|
|
224
|
+
response.writeHead(200, {
|
|
225
|
+
"Content-Type": "application/octet-stream",
|
|
226
|
+
"Content-Encoding": "utf8",
|
|
227
|
+
})
|
|
228
|
+
const count = ht.count()
|
|
229
|
+
for await (const value of ht.reverseIterator(options)) {
|
|
230
|
+
response.write(value)
|
|
231
|
+
response.write("")
|
|
232
|
+
}
|
|
233
|
+
response.end()
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
eq("forwardEntriesIterator"), async () => {
|
|
237
|
+
const options = await Readable.JSON(request)
|
|
238
|
+
response.writeHead(200, {
|
|
239
|
+
"Content-Type": "application/octet-stream",
|
|
240
|
+
"Content-Encoding": "utf8",
|
|
241
|
+
})
|
|
242
|
+
const count = ht.count()
|
|
243
|
+
for await (const [key, value, sortValue] of ht.forwardEntriesIterator(options)) {
|
|
244
|
+
response.write(key)
|
|
245
|
+
response.write("")
|
|
246
|
+
response.write(value)
|
|
247
|
+
response.write("")
|
|
248
|
+
response.write(String(sortValue))
|
|
249
|
+
response.write("")
|
|
250
|
+
}
|
|
251
|
+
response.end()
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
eq("reverseEntriesIterator"), async () => {
|
|
255
|
+
const options = await Readable.JSON(request)
|
|
256
|
+
response.writeHead(200, {
|
|
257
|
+
"Content-Type": "application/octet-stream",
|
|
258
|
+
"Content-Encoding": "utf8",
|
|
259
|
+
})
|
|
260
|
+
const count = ht.count()
|
|
261
|
+
for await (const [key, value, sortValue] of ht.reverseEntriesIterator(options)) {
|
|
262
|
+
response.write(key)
|
|
263
|
+
response.write("")
|
|
264
|
+
response.write(value)
|
|
265
|
+
response.write("")
|
|
266
|
+
response.write(String(sortValue))
|
|
267
|
+
response.write("")
|
|
268
|
+
}
|
|
269
|
+
response.end()
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
async () => {
|
|
273
|
+
const error = new Error("Forbidden: Hash Table Method")
|
|
274
|
+
error.code = "403"
|
|
275
|
+
throw error
|
|
276
|
+
},
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
} else {
|
|
280
|
+
response.writeHead(404, {
|
|
281
|
+
"Content-Type": "text/plain",
|
|
282
|
+
})
|
|
283
|
+
response.end("Not Found")
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
module.exports = DatabaseHandler
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const HTTP = require("presidium/HTTP")
|
|
2
|
+
const DiskHashTable = require("presidium/DiskHashTable")
|
|
3
|
+
const DatabaseHandler = require("./DatabaseHandler")
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @name DatabaseServer
|
|
7
|
+
*
|
|
8
|
+
* @docs
|
|
9
|
+
* ```specscript
|
|
10
|
+
* new DatabaseServer(options {
|
|
11
|
+
* databasePath: string,
|
|
12
|
+
* manifestTableName: string,
|
|
13
|
+
* manifestTableInitialLength: number,
|
|
14
|
+
* manifestTableItemSize: number,
|
|
15
|
+
* defaultTableInitialLength: number,
|
|
16
|
+
* defaultTableItemSize: number,
|
|
17
|
+
* defaultDiskSortedHashTableDegree: number,
|
|
18
|
+
* }) -> server DatabaseServer
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
class DatabaseServer {
|
|
22
|
+
constructor(options) {
|
|
23
|
+
this.databasePath = options.databasePath
|
|
24
|
+
this.manifestTableName = options.manifestTableName ?? "Manifest"
|
|
25
|
+
this.manifestTableInitialLength = options.manifestTableInitialLength ?? 1048576
|
|
26
|
+
this.manifestTableItemSize = options.manifestTableItemSize ?? 1024
|
|
27
|
+
this.defaultTableInitialLength = options.defaultTableInitialLength ?? 1048576
|
|
28
|
+
this.defaultTableItemSize = options.defaultTableItemSize ?? 1024
|
|
29
|
+
this.defaultDiskSortedHashTableDegree = options.defaultDiskSortedHashTableDegree ?? 2
|
|
30
|
+
this.server = null
|
|
31
|
+
this.hts = new Map()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @name init
|
|
36
|
+
*
|
|
37
|
+
* @docs
|
|
38
|
+
* ```specscript
|
|
39
|
+
* init() -> Promise<>
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
async init() {
|
|
43
|
+
const manifestHt = new DiskHashTable({
|
|
44
|
+
storagePath: `${this.databasePath}/${this.manifestTableName}/storage`,
|
|
45
|
+
headerPath: `${this.databasePath}/${this.manifestTableName}/header`,
|
|
46
|
+
initialLength: this.manifestTableInitialLength,
|
|
47
|
+
itemSize: this.manifestTableItemSize,
|
|
48
|
+
})
|
|
49
|
+
await manifestHt.init()
|
|
50
|
+
this.manifestHt = manifestHt
|
|
51
|
+
|
|
52
|
+
const databaseHandler = DatabaseHandler({
|
|
53
|
+
databasePath: this.databasePath,
|
|
54
|
+
manifestTableName: this.manifestTableName,
|
|
55
|
+
manifestTableInitialLength: this.manifestTableInitialLength,
|
|
56
|
+
manifestTableItemSize: this.manifestTableItemSize,
|
|
57
|
+
defaultTableInitialLength: this.defaultTableInitialLength,
|
|
58
|
+
defaultTableItemSize: this.defaultTableItemSize,
|
|
59
|
+
defaultDiskSortedHashTableDegree: this.defaultDiskSortedHashTableDegree,
|
|
60
|
+
manifestHt: this.manifestHt,
|
|
61
|
+
hts: this.hts,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
this.server = HTTP.Server(databaseHandler)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @name listen
|
|
69
|
+
*
|
|
70
|
+
* @docs
|
|
71
|
+
* ```specscript
|
|
72
|
+
* listen(port number) -> ()
|
|
73
|
+
* listen(port number, callback function) -> ()
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
listen(port, callback) {
|
|
77
|
+
this.server.listen(port, callback)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @name close
|
|
82
|
+
*
|
|
83
|
+
* @docs
|
|
84
|
+
* ```specscript
|
|
85
|
+
* close() -> ()
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
close() {
|
|
89
|
+
this.manifestHt.close()
|
|
90
|
+
for (const ht of this.hts.values()) {
|
|
91
|
+
ht.close()
|
|
92
|
+
}
|
|
93
|
+
this.server.close()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = DatabaseServer
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
require("rubico/global")
|
|
2
|
+
const HTTP = require("presidium/HTTP")
|
|
3
|
+
const Readable = require("presidium/Readable")
|
|
4
|
+
const handleServerResponse = require("./handleServerResponse")
|
|
5
|
+
const buffersToEntry = require("./buffersToEntry")
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @name DiskHashTableClient
|
|
9
|
+
*
|
|
10
|
+
* @docs
|
|
11
|
+
* ```specscript
|
|
12
|
+
* new DiskHashTableClient(options {
|
|
13
|
+
* serverUrl: string,
|
|
14
|
+
* tableName: string,
|
|
15
|
+
* delimiter: string,
|
|
16
|
+
* }) -> DiskHashTableClient
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
class DiskHashTableClient {
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.serverUrl = options.serverUrl
|
|
22
|
+
this.tableName = options.tableName
|
|
23
|
+
this.delimiter = options.delimiter ?? ""
|
|
24
|
+
this.http = new HTTP(this.serverUrl)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @name clear
|
|
29
|
+
*
|
|
30
|
+
* @docs
|
|
31
|
+
* ```specscript
|
|
32
|
+
* module DiskHashTable "https://presidium.services/docs/DiskHashTable"
|
|
33
|
+
*
|
|
34
|
+
* clear DiskHashTable.clear
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
async clear() {
|
|
38
|
+
const response = await this.http.POST(`/${this.tableName}/clear`, {
|
|
39
|
+
headers: {
|
|
40
|
+
"Client-Name": "DiskHashTableClient",
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
return handleServerResponse(response)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @name set
|
|
48
|
+
*
|
|
49
|
+
* @docs
|
|
50
|
+
* ```specscript
|
|
51
|
+
* module DiskHashTable "https://presidium.services/docs/DiskHashTable"
|
|
52
|
+
*
|
|
53
|
+
* set DiskHashTable.set
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
async set(key, value) {
|
|
57
|
+
const response = await this.http.POST(`/${this.tableName}/set`, {
|
|
58
|
+
headers: {
|
|
59
|
+
"Content-Type": "application/json",
|
|
60
|
+
"Client-Name": "DiskHashTableClient",
|
|
61
|
+
},
|
|
62
|
+
body: JSON.stringify({ key, value }),
|
|
63
|
+
})
|
|
64
|
+
await handleServerResponse(response)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @name get
|
|
69
|
+
*
|
|
70
|
+
* @docs
|
|
71
|
+
* ```specscript
|
|
72
|
+
* module DiskHashTable "https://presidium.services/docs/DiskHashTable"
|
|
73
|
+
*
|
|
74
|
+
* get DiskHashTable.get
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
async get(key) {
|
|
78
|
+
const response = await this.http.POST(`/${this.tableName}/get`, {
|
|
79
|
+
headers: {
|
|
80
|
+
"Content-Type": "application/json",
|
|
81
|
+
"Client-Name": "DiskHashTableClient",
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify({ key }),
|
|
84
|
+
})
|
|
85
|
+
if (response.status == 499) {
|
|
86
|
+
return undefined
|
|
87
|
+
}
|
|
88
|
+
return handleServerResponse(response)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @name getBinary
|
|
93
|
+
*
|
|
94
|
+
* @docs
|
|
95
|
+
* ```specscript
|
|
96
|
+
* module DiskHashTable "https://presidium.services/docs/DiskHashTable"
|
|
97
|
+
*
|
|
98
|
+
* getBinary DiskHashTable.getBinary
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
async getBinary(key) {
|
|
102
|
+
const response = await this.http.POST(`/${this.tableName}/getBinary`, {
|
|
103
|
+
headers: {
|
|
104
|
+
"Content-Type": "application/json",
|
|
105
|
+
"Client-Name": "DiskHashTableClient",
|
|
106
|
+
},
|
|
107
|
+
body: JSON.stringify({ key }),
|
|
108
|
+
})
|
|
109
|
+
return handleServerResponse(response)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @name delete
|
|
114
|
+
*
|
|
115
|
+
* @docs
|
|
116
|
+
* ```specscript
|
|
117
|
+
* module DiskHashTable "https://presidium.services/docs/DiskHashTable"
|
|
118
|
+
*
|
|
119
|
+
* delete DiskHashTable.delete
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
async delete(key) {
|
|
123
|
+
const response = await this.http.POST(`/${this.tableName}/delete`, {
|
|
124
|
+
headers: {
|
|
125
|
+
"Content-Type": "application/json",
|
|
126
|
+
"Client-Name": "DiskHashTableClient",
|
|
127
|
+
},
|
|
128
|
+
body: JSON.stringify({ key }),
|
|
129
|
+
})
|
|
130
|
+
const didDeleteText = await handleServerResponse(response)
|
|
131
|
+
return didDeleteText == "true"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @name count
|
|
136
|
+
*
|
|
137
|
+
* @docs
|
|
138
|
+
* ```specscript
|
|
139
|
+
* count() -> Promise<number>
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
async count() {
|
|
143
|
+
const response = await this.http.POST(`/${this.tableName}/count`, {
|
|
144
|
+
headers: {
|
|
145
|
+
"Client-Name": "DiskHashTableClient",
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
const scount = await handleServerResponse(response)
|
|
149
|
+
return Number(scount)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @name iterator
|
|
154
|
+
*
|
|
155
|
+
* @docs
|
|
156
|
+
* ```specscript
|
|
157
|
+
* iterator(options {
|
|
158
|
+
* valueType: "string"|"binary",
|
|
159
|
+
* }) -> values AsyncIterator<string|Buffer>
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
async iterator(options = {}) {
|
|
163
|
+
const { valueType } = options
|
|
164
|
+
const response = await this.http.POST(`/${this.tableName}/iterator`, {
|
|
165
|
+
headers: {
|
|
166
|
+
"Content-Type": "application/json",
|
|
167
|
+
"Client-Name": "DiskHashTableClient",
|
|
168
|
+
},
|
|
169
|
+
body: JSON.stringify(options),
|
|
170
|
+
})
|
|
171
|
+
if (valueType == "binary") {
|
|
172
|
+
return handleServerResponse(response, {
|
|
173
|
+
binaryStream: true,
|
|
174
|
+
delimiter: this.delimiter,
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
return handleServerResponse(response, {
|
|
178
|
+
textStream: true,
|
|
179
|
+
delimiter: this.delimiter,
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @name entriesIterator
|
|
185
|
+
*
|
|
186
|
+
* @docs
|
|
187
|
+
* ```specscript
|
|
188
|
+
* entriesIterator(options {
|
|
189
|
+
* valueType: 'string'|'binary',
|
|
190
|
+
* }) -> entries AsyncIterator<[key string, value string|Buffer]>
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
async entriesIterator(options = {}) {
|
|
194
|
+
const { valueType } = options
|
|
195
|
+
const response = await this.http.POST(`/${this.tableName}/entriesIterator`, {
|
|
196
|
+
headers: {
|
|
197
|
+
"Content-Type": "application/json",
|
|
198
|
+
"Client-Name": "DiskHashTableClient",
|
|
199
|
+
},
|
|
200
|
+
body: JSON.stringify({ valueType }),
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
if (valueType == "binary") {
|
|
204
|
+
const iter = await handleServerResponse(response, {
|
|
205
|
+
binaryEntriesStream: true,
|
|
206
|
+
delimiter: this.delimiter,
|
|
207
|
+
entryLength: 2,
|
|
208
|
+
})
|
|
209
|
+
return map(iter, curry.arity(4, buffersToEntry, __, 2, "utf8", valueType))
|
|
210
|
+
}
|
|
211
|
+
const iter = await handleServerResponse(response, {
|
|
212
|
+
textEntriesStream: true,
|
|
213
|
+
delimiter: this.delimiter,
|
|
214
|
+
entryLength: 2,
|
|
215
|
+
})
|
|
216
|
+
return map(iter, curry.arity(4, buffersToEntry, __, 2, "utf8", valueType))
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module.exports = DiskHashTableClient
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
require("rubico/global")
|
|
2
|
+
const HTTP = require("presidium/HTTP")
|
|
3
|
+
const handleServerResponse = require("./handleServerResponse")
|
|
4
|
+
const buffersToEntry = require("./buffersToEntry")
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @name DiskSortedHashTableClient
|
|
8
|
+
*
|
|
9
|
+
* @docs
|
|
10
|
+
* ```specscript
|
|
11
|
+
* new DiskSortedHashTableClient(options {
|
|
12
|
+
* serverUrl: string,
|
|
13
|
+
* tableName: string,
|
|
14
|
+
* sortValueType: "string"|"number",
|
|
15
|
+
* degree: number,
|
|
16
|
+
* delimiter: string,
|
|
17
|
+
* }) -> DiskSortedHashTableClient
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
class DiskSortedHashTableClient {
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.serverUrl = options.serverUrl
|
|
23
|
+
this.tableName = options.tableName
|
|
24
|
+
this.sortValueType = options.sortValueType
|
|
25
|
+
this.degree = options.degree ?? 2
|
|
26
|
+
this.delimiter = options.delimiter ?? ""
|
|
27
|
+
this.http = new HTTP(this.serverUrl)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @name clear
|
|
32
|
+
*
|
|
33
|
+
* @docs
|
|
34
|
+
* ```specscript
|
|
35
|
+
* module DiskSortedHashTable "https://presidium.services/docs/DiskSortedHashTable"
|
|
36
|
+
*
|
|
37
|
+
* clear DiskSortedHashTable.clear
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
async clear() {
|
|
41
|
+
const response = await this.http.POST(`/${this.tableName}/clear`, {
|
|
42
|
+
headers: {
|
|
43
|
+
"Client-Name": "DiskSortedHashTableClient",
|
|
44
|
+
"Sort-Value-Type": this.sortValueType,
|
|
45
|
+
"Degree": Number(this.degree),
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
return handleServerResponse(response)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @name set
|
|
53
|
+
*
|
|
54
|
+
* @docs
|
|
55
|
+
* ```specscript
|
|
56
|
+
* module DiskSortedHashTable "https://presidium.services/docs/DiskSortedHashTable"
|
|
57
|
+
*
|
|
58
|
+
* set DiskSortedHashTable.set
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
async set(key, value, sortValue) {
|
|
62
|
+
const response = await this.http.POST(`/${this.tableName}/set`, {
|
|
63
|
+
headers: {
|
|
64
|
+
"Content-Type": "application/json",
|
|
65
|
+
"Client-Name": "DiskSortedHashTableClient",
|
|
66
|
+
"Sort-Value-Type": this.sortValueType,
|
|
67
|
+
"Degree": Number(this.degree),
|
|
68
|
+
},
|
|
69
|
+
body: JSON.stringify({ key, value, sortValue }),
|
|
70
|
+
})
|
|
71
|
+
await handleServerResponse(response)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @name get
|
|
76
|
+
*
|
|
77
|
+
* @docs
|
|
78
|
+
* ```specscript
|
|
79
|
+
* module DiskSortedHashTable "https://presidium.services/docs/DiskSortedHashTable"
|
|
80
|
+
*
|
|
81
|
+
* get DiskSortedHashTable.get
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
async get(key) {
|
|
85
|
+
const response = await this.http.POST(`/${this.tableName}/get`, {
|
|
86
|
+
headers: {
|
|
87
|
+
"Content-Type": "application/json",
|
|
88
|
+
"Client-Name": "DiskSortedHashTableClient",
|
|
89
|
+
"Sort-Value-Type": this.sortValueType,
|
|
90
|
+
"Degree": Number(this.degree),
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify({ key }),
|
|
93
|
+
})
|
|
94
|
+
if (response.status == 499) {
|
|
95
|
+
return undefined
|
|
96
|
+
}
|
|
97
|
+
return handleServerResponse(response)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @name getBinary
|
|
102
|
+
*
|
|
103
|
+
* @docs
|
|
104
|
+
* ```specscript
|
|
105
|
+
* module DiskSortedHashTable "https://presidium.services/docs/DiskSortedHashTable"
|
|
106
|
+
*
|
|
107
|
+
* getBinary DiskSortedHashTable.getBinary
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
async getBinary(key) {
|
|
111
|
+
const response = await this.http.POST(`/${this.tableName}/getBinary`, {
|
|
112
|
+
headers: {
|
|
113
|
+
"Content-Type": "application/json",
|
|
114
|
+
"Client-Name": "DiskSortedHashTableClient",
|
|
115
|
+
"Sort-Value-Type": this.sortValueType,
|
|
116
|
+
"Degree": Number(this.degree),
|
|
117
|
+
},
|
|
118
|
+
body: JSON.stringify({ key }),
|
|
119
|
+
})
|
|
120
|
+
return handleServerResponse(response)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @name forwardIterator
|
|
125
|
+
*
|
|
126
|
+
* @docs
|
|
127
|
+
* ```specscript
|
|
128
|
+
* forwardIterator(options {
|
|
129
|
+
* exclusiveStartKey: string,
|
|
130
|
+
* startingSortValue: string|number,
|
|
131
|
+
* endingSortValue: string|number,
|
|
132
|
+
* valueType: 'string'|'binary',
|
|
133
|
+
* }) -> values AsyncIterator<string|Buffer>
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
async forwardIterator(options = {}) {
|
|
137
|
+
const { valueType } = options
|
|
138
|
+
const response = await this.http.POST(`/${this.tableName}/forwardIterator`, {
|
|
139
|
+
headers: {
|
|
140
|
+
"Content-Type": "application/json",
|
|
141
|
+
"Client-Name": "DiskSortedHashTableClient",
|
|
142
|
+
"Sort-Value-Type": this.sortValueType,
|
|
143
|
+
"Degree": Number(this.degree),
|
|
144
|
+
},
|
|
145
|
+
body: JSON.stringify(options),
|
|
146
|
+
})
|
|
147
|
+
if (valueType == "binary") {
|
|
148
|
+
return handleServerResponse(response, {
|
|
149
|
+
binaryStream: true,
|
|
150
|
+
delimiter: this.delimiter,
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
return handleServerResponse(response, {
|
|
154
|
+
textStream: true,
|
|
155
|
+
delimiter: this.delimiter,
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @name reverseIterator
|
|
161
|
+
*
|
|
162
|
+
* @docs
|
|
163
|
+
* ```specscript
|
|
164
|
+
* reverseIterator(options {
|
|
165
|
+
* exclusiveStartKey: string,
|
|
166
|
+
* startingSortValue: string|number,
|
|
167
|
+
* endingSortValue: string|number,
|
|
168
|
+
* valueType: 'string'|'binary',
|
|
169
|
+
* }) -> values AsyncIterator<string|Buffer>
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
async reverseIterator(options = {}) {
|
|
173
|
+
const { valueType } = options
|
|
174
|
+
const response = await this.http.POST(`/${this.tableName}/reverseIterator`, {
|
|
175
|
+
headers: {
|
|
176
|
+
"Content-Type": "application/json",
|
|
177
|
+
"Client-Name": "DiskSortedHashTableClient",
|
|
178
|
+
"Sort-Value-Type": this.sortValueType,
|
|
179
|
+
"Degree": Number(this.degree),
|
|
180
|
+
},
|
|
181
|
+
body: JSON.stringify(options),
|
|
182
|
+
})
|
|
183
|
+
if (valueType == "binary") {
|
|
184
|
+
return handleServerResponse(response, {
|
|
185
|
+
binaryStream: true,
|
|
186
|
+
delimiter: this.delimiter,
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
return handleServerResponse(response, {
|
|
190
|
+
textStream: true,
|
|
191
|
+
delimiter: this.delimiter,
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @name forwardEntriesIterator
|
|
197
|
+
*
|
|
198
|
+
* @docs
|
|
199
|
+
* ```specscript
|
|
200
|
+
* forwardEntriesIterator(options {
|
|
201
|
+
* exclusiveStartKey: string,
|
|
202
|
+
* startingSortValue: string|number,
|
|
203
|
+
* endingSortValue: string|number,
|
|
204
|
+
* valueType: 'string'|'binary',
|
|
205
|
+
* }) -> values AsyncIterator<string|Buffer>
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
async forwardEntriesIterator(options = {}) {
|
|
209
|
+
const { valueType } = options
|
|
210
|
+
const response = await this.http.POST(`/${this.tableName}/forwardEntriesIterator`, {
|
|
211
|
+
headers: {
|
|
212
|
+
"Content-Type": "application/json",
|
|
213
|
+
"Client-Name": "DiskSortedHashTableClient",
|
|
214
|
+
"Sort-Value-Type": this.sortValueType,
|
|
215
|
+
"Degree": Number(this.degree),
|
|
216
|
+
},
|
|
217
|
+
body: JSON.stringify(options),
|
|
218
|
+
})
|
|
219
|
+
if (valueType == "binary") {
|
|
220
|
+
const iter = await handleServerResponse(response, {
|
|
221
|
+
binaryEntriesStream: true,
|
|
222
|
+
delimiter: this.delimiter,
|
|
223
|
+
entryLength: 3,
|
|
224
|
+
})
|
|
225
|
+
return map(iter, curry(buffersToEntry, __, 3, "utf8", valueType, this.sortValueType))
|
|
226
|
+
}
|
|
227
|
+
const iter = await handleServerResponse(response, {
|
|
228
|
+
textEntriesStream: true,
|
|
229
|
+
delimiter: this.delimiter,
|
|
230
|
+
entryLength: 3,
|
|
231
|
+
})
|
|
232
|
+
return map(iter, curry(buffersToEntry, __, 3, "utf8", valueType, this.sortValueType))
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* @name reverseEntriesIterator
|
|
237
|
+
*
|
|
238
|
+
* @docs
|
|
239
|
+
* ```specscript
|
|
240
|
+
* module DiskSortedHashTable "https://presidium.services/docs/DiskSortedHashTable"
|
|
241
|
+
*
|
|
242
|
+
* reverseEntriesIterator DiskSortedHashTable.reverseEntriesIterator
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
async reverseEntriesIterator(options = {}) {
|
|
246
|
+
const { valueType } = options
|
|
247
|
+
const response = await this.http.POST(`/${this.tableName}/reverseEntriesIterator`, {
|
|
248
|
+
headers: {
|
|
249
|
+
"Content-Type": "application/json",
|
|
250
|
+
"Client-Name": "DiskSortedHashTableClient",
|
|
251
|
+
"Sort-Value-Type": this.sortValueType,
|
|
252
|
+
"Degree": Number(this.degree),
|
|
253
|
+
},
|
|
254
|
+
body: JSON.stringify(options),
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
if (valueType == "binary") {
|
|
258
|
+
const iter = await handleServerResponse(response, {
|
|
259
|
+
binaryEntriesStream: true,
|
|
260
|
+
delimiter: this.delimiter,
|
|
261
|
+
entryLength: 3,
|
|
262
|
+
})
|
|
263
|
+
return map(iter, curry(buffersToEntry, __, 3, "utf8", valueType, this.sortValueType))
|
|
264
|
+
}
|
|
265
|
+
const iter = await handleServerResponse(response, {
|
|
266
|
+
textEntriesStream: true,
|
|
267
|
+
delimiter: this.delimiter,
|
|
268
|
+
entryLength: 3,
|
|
269
|
+
})
|
|
270
|
+
return map(iter, curry(buffersToEntry, __, 3, "utf8", valueType, this.sortValueType))
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* @name delete
|
|
275
|
+
*
|
|
276
|
+
* @docs
|
|
277
|
+
* ```specscript
|
|
278
|
+
* module DiskSortedHashTable "https://presidium.services/docs/DiskSortedHashTable"
|
|
279
|
+
*
|
|
280
|
+
* delete DiskSortedHashTable.delete
|
|
281
|
+
* ```
|
|
282
|
+
*/
|
|
283
|
+
async delete(key) {
|
|
284
|
+
const response = await this.http.POST(`/${this.tableName}/delete`, {
|
|
285
|
+
headers: {
|
|
286
|
+
"Content-Type": "application/json",
|
|
287
|
+
"Client-Name": "DiskSortedHashTableClient",
|
|
288
|
+
"Sort-Value-Type": this.sortValueType,
|
|
289
|
+
"Degree": Number(this.degree),
|
|
290
|
+
},
|
|
291
|
+
body: JSON.stringify({ key }),
|
|
292
|
+
})
|
|
293
|
+
const didDeleteText = await handleServerResponse(response)
|
|
294
|
+
return didDeleteText == "true"
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* @name count
|
|
299
|
+
*
|
|
300
|
+
* @docs
|
|
301
|
+
* ```specscript
|
|
302
|
+
* count() -> Promise<number>
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
async count() {
|
|
306
|
+
const response = await this.http.POST(`/${this.tableName}/count`, {
|
|
307
|
+
headers: {
|
|
308
|
+
"Client-Name": "DiskSortedHashTableClient",
|
|
309
|
+
"Sort-Value-Type": this.sortValueType,
|
|
310
|
+
"Degree": Number(this.degree),
|
|
311
|
+
},
|
|
312
|
+
})
|
|
313
|
+
return Number(await handleServerResponse(response))
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
module.exports = DiskSortedHashTableClient
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const EventEmitter = require("events")
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @name TextEntriesStream
|
|
5
|
+
*
|
|
6
|
+
* @docs
|
|
7
|
+
* ```coffeescript [specscript]
|
|
8
|
+
* module EventEmitter "./specscript/EventEmitter.ss"
|
|
9
|
+
*
|
|
10
|
+
* TextEntriesStream(
|
|
11
|
+
* readable stream.Readable,
|
|
12
|
+
* entryLength number,
|
|
13
|
+
* delimiter string
|
|
14
|
+
* ) -> textEntriesStream EventEmitter
|
|
15
|
+
*
|
|
16
|
+
* textEntriesStream.on("entry", (entry Array<string>, keyEncoding string) -> ())
|
|
17
|
+
*
|
|
18
|
+
* textEntriesStream.on("end", () -> ())
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
class TextEntriesStream extends EventEmitter {
|
|
22
|
+
constructor(readable, entryLength, delimiter, encoding) {
|
|
23
|
+
super()
|
|
24
|
+
|
|
25
|
+
delimiter ??= ""
|
|
26
|
+
encoding ??= "utf8"
|
|
27
|
+
|
|
28
|
+
let s = ""
|
|
29
|
+
let entry = []
|
|
30
|
+
readable.on('data', (chunk, encoding) => {
|
|
31
|
+
if (typeof chunk == 'string') {
|
|
32
|
+
s += Buffer.from(chunk, encoding).toString(encoding)
|
|
33
|
+
} else {
|
|
34
|
+
s += chunk.toString(encoding)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let delimiterIndex = s.indexOf(delimiter)
|
|
38
|
+
while (delimiterIndex > -1) {
|
|
39
|
+
const text = s.slice(0, delimiterIndex)
|
|
40
|
+
entry.push(text)
|
|
41
|
+
if (entry.length == entryLength) {
|
|
42
|
+
this.emit("entry", entry, encoding)
|
|
43
|
+
entry = []
|
|
44
|
+
}
|
|
45
|
+
s = s.slice(delimiterIndex + 1)
|
|
46
|
+
delimiterIndex = s.indexOf(delimiter)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
readable.on("end", () => {
|
|
51
|
+
if (s.length > 0) {
|
|
52
|
+
entry.push(s)
|
|
53
|
+
}
|
|
54
|
+
if (entry.length == entryLength) {
|
|
55
|
+
this.emit("entry", entry, encoding)
|
|
56
|
+
this.emit("end")
|
|
57
|
+
entry = []
|
|
58
|
+
} else if (entry.length > 0) {
|
|
59
|
+
const error = new Error("last entry")
|
|
60
|
+
error.entry = entry
|
|
61
|
+
this.emit("error", error)
|
|
62
|
+
} else {
|
|
63
|
+
this.emit("end")
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = TextEntriesStream
|
package/TextStream.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const EventEmitter = require("events")
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @name TextStream
|
|
5
|
+
*
|
|
6
|
+
* @docs
|
|
7
|
+
* ```coffeescript [specscript]
|
|
8
|
+
* module stream 'https://nodejs.org/docs/latest-v26.x/api/stream.html'
|
|
9
|
+
* module events 'https://nodejs.org/docs/latest-v26.x/api/events.html'
|
|
10
|
+
*
|
|
11
|
+
* TextStream(readable stream.Readable, delimiter string) -> textStream events.EventEmitter
|
|
12
|
+
*
|
|
13
|
+
* textStream.on("data", (text string, encoding string) -> ())
|
|
14
|
+
*
|
|
15
|
+
* textStream.on("end", () -> ())
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
class TextStream extends EventEmitter {
|
|
19
|
+
constructor(readable, delimiter, encoding) {
|
|
20
|
+
super()
|
|
21
|
+
|
|
22
|
+
delimiter ??= ""
|
|
23
|
+
encoding ??= "utf8"
|
|
24
|
+
|
|
25
|
+
let s = ""
|
|
26
|
+
readable.on('data', (chunk, encoding) => {
|
|
27
|
+
if (typeof chunk == 'string') {
|
|
28
|
+
s += Buffer.from(chunk, encoding).toString(encoding)
|
|
29
|
+
} else {
|
|
30
|
+
s += chunk.toString(encoding)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let delimiterIndex = s.indexOf(delimiter)
|
|
34
|
+
while (delimiterIndex > -1) {
|
|
35
|
+
const text = s.slice(0, delimiterIndex)
|
|
36
|
+
this.emit("data", text, encoding)
|
|
37
|
+
s = s.slice(delimiterIndex + 1)
|
|
38
|
+
delimiterIndex = s.indexOf(delimiter)
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
readable.on('end', () => {
|
|
43
|
+
if (s.length > 0) {
|
|
44
|
+
this.emit("data", s, encoding)
|
|
45
|
+
}
|
|
46
|
+
this.emit("end")
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = TextStream
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @name buffersToEntry
|
|
3
|
+
*
|
|
4
|
+
* @docs
|
|
5
|
+
* ```specscript
|
|
6
|
+
* buffersToEntry(
|
|
7
|
+
* buffers Array<Buffer>,
|
|
8
|
+
* entryLength number,
|
|
9
|
+
* encoding string,
|
|
10
|
+
* valueType string,
|
|
11
|
+
* ) -> Array<string, Buffer>
|
|
12
|
+
*
|
|
13
|
+
* buffersToEntry(
|
|
14
|
+
* buffers Array<Buffer>,
|
|
15
|
+
* entryLength number,
|
|
16
|
+
* encoding string,
|
|
17
|
+
* valueType string,
|
|
18
|
+
* sortValueType string
|
|
19
|
+
* ) -> Array<string, Buffer>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
function buffersToEntry(buffers, entryLength, encoding, valueType, sortValueType) {
|
|
23
|
+
if (entryLength == 3) {
|
|
24
|
+
const key = buffers[0].toString(encoding)
|
|
25
|
+
const value = valueType == "string"
|
|
26
|
+
? buffers[1].toString(encoding)
|
|
27
|
+
: buffers[1]
|
|
28
|
+
const sortValue = sortValueType == "number"
|
|
29
|
+
? Number(buffers[2].toString(encoding))
|
|
30
|
+
: buffers[2].toString(encoding)
|
|
31
|
+
return [key, value, sortValue]
|
|
32
|
+
}
|
|
33
|
+
const key = buffers[0].toString(encoding)
|
|
34
|
+
const value = valueType == "string"
|
|
35
|
+
? buffers[1].toString(encoding)
|
|
36
|
+
: buffers[1]
|
|
37
|
+
return [key, value]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = buffersToEntry
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const Readable = require("presidium/Readable")
|
|
2
|
+
const TextStream = require("./TextStream")
|
|
3
|
+
const BinaryStream = require("./BinaryStream")
|
|
4
|
+
const TextEntriesStream = require("./TextEntriesStream")
|
|
5
|
+
const BinaryEntriesStream = require("./BinaryEntriesStream")
|
|
6
|
+
const AsyncIterator = require("./AsyncIterator")
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @name handleServerResponse
|
|
10
|
+
*
|
|
11
|
+
* @docs
|
|
12
|
+
* ```specscript
|
|
13
|
+
* module http 'https://nodejs.org/docs/latest-v26.x/api/http.html'
|
|
14
|
+
* module events 'https://nodejs.org/docs/latest-v26.x/api/events.html'
|
|
15
|
+
*
|
|
16
|
+
* handleServerResponse(response http.ServerResponse, options {
|
|
17
|
+
* textStream: boolean,
|
|
18
|
+
* binaryStream: boolean,
|
|
19
|
+
* textEntriesStream: boolean,
|
|
20
|
+
* binaryEntriesStream: boolean,
|
|
21
|
+
* delimiter: string,
|
|
22
|
+
* entryLength: number,
|
|
23
|
+
* }) -> data object|events.EventEmitter
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
async function handleServerResponse(response, options = {}) {
|
|
27
|
+
const {
|
|
28
|
+
textStream,
|
|
29
|
+
binaryStream,
|
|
30
|
+
textEntriesStream,
|
|
31
|
+
binaryEntriesStream,
|
|
32
|
+
delimiter = "",
|
|
33
|
+
entryLength,
|
|
34
|
+
} = options
|
|
35
|
+
|
|
36
|
+
const encoding = response.headers["content-encoding"] ?? "utf8"
|
|
37
|
+
|
|
38
|
+
if (response.ok) {
|
|
39
|
+
if (textStream) {
|
|
40
|
+
const stream = new TextStream(response, delimiter, encoding)
|
|
41
|
+
return AsyncIterator.fromEvents(stream)
|
|
42
|
+
}
|
|
43
|
+
if (binaryStream) {
|
|
44
|
+
const stream = new BinaryStream(response, delimiter)
|
|
45
|
+
return AsyncIterator.fromEvents(stream)
|
|
46
|
+
}
|
|
47
|
+
if (textEntriesStream) {
|
|
48
|
+
const stream = new TextEntriesStream(response, entryLength, delimiter, encoding)
|
|
49
|
+
return AsyncIterator.fromEvents(stream, "entry")
|
|
50
|
+
}
|
|
51
|
+
if (binaryEntriesStream) {
|
|
52
|
+
const stream = new BinaryEntriesStream(response, entryLength, delimiter, encoding)
|
|
53
|
+
return AsyncIterator.fromEvents(stream, "entry")
|
|
54
|
+
}
|
|
55
|
+
if (response.headers["content-type"] == "application/json") {
|
|
56
|
+
return Readable.JSON(response)
|
|
57
|
+
}
|
|
58
|
+
if (response.headers["content-type"] == "application/octet-stream") {
|
|
59
|
+
return Readable.Buffer(response)
|
|
60
|
+
}
|
|
61
|
+
return Readable.Text(response)
|
|
62
|
+
}
|
|
63
|
+
const message = await Readable.Text(response)
|
|
64
|
+
const error = new Error(message)
|
|
65
|
+
throw error
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = handleServerResponse
|
package/package.json
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "presidium-db-client-server",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Presidium DB client and server library",
|
|
5
|
+
"author": "Richard Tong",
|
|
6
|
+
"license": "CFOSS",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "github:richytong/presidium-db-client-server"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/richytong/presidium-db-client-server",
|
|
12
|
+
"files": [
|
|
13
|
+
"ArrayEventEmitter.js",
|
|
14
|
+
"AsyncIterator.js",
|
|
15
|
+
"BinaryEntriesStream.js",
|
|
16
|
+
"BinaryStream.js",
|
|
17
|
+
"DatabaseHandler.js",
|
|
18
|
+
"DatabaseServer.js",
|
|
19
|
+
"DiskHashTableClient.js",
|
|
20
|
+
"DiskSortedHashTableClient.js",
|
|
21
|
+
"TextEntriesStream.js",
|
|
22
|
+
"TextStream.js",
|
|
23
|
+
"buffersToEntry.js",
|
|
24
|
+
"handleServerResponse.js"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"presidium",
|
|
28
|
+
"db",
|
|
29
|
+
"database",
|
|
30
|
+
"client",
|
|
31
|
+
"server",
|
|
32
|
+
"key-value",
|
|
33
|
+
"node",
|
|
34
|
+
"nodejs",
|
|
35
|
+
"javascript"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"test": "node test.js"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {}
|
|
41
|
+
}
|
package/README.md
DELETED