presidium-db-client-server 0.4.0 → 0.6.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.
@@ -111,186 +111,219 @@ function DatabaseHandler(options) {
111
111
  request.pathSegments = request.path.slice(1).split("/")
112
112
  request.pathDepth = request.pathSegments.length
113
113
 
114
- if (request.path == "/health") {
115
- response.writeHead(200, {
116
- "Content-Type": "text/plain",
117
- })
118
- response.end("OK")
119
- } else if (request.pathDepth == 2) {
120
- const tableName = request.pathSegments[0]
121
- const fname = request.pathSegments[1]
122
- const ht = hts.get(tableName)
123
- ?? await getHt(tableName, request, manifestHt)
114
+ const tableName = request.pathSegments[0]
115
+ const fname = request.pathSegments[1]
116
+ const ht = hts.get(tableName) ?? await getHt(tableName, request, manifestHt)
124
117
 
125
- await switchCase(
126
- fname,
118
+ await switchCase(
119
+ fname,
127
120
 
128
- eq("clear"), async () => {
129
- await ht.clear()
130
- response.writeHead(200, {
121
+ eq("clear"), async () => {
122
+ await ht.clear()
123
+ response.writeHead(200, {
124
+ "Content-Type": "text/plain",
125
+ })
126
+ response.end("OK")
127
+ },
128
+
129
+ eq("set"), async () => {
130
+ const key = request.headers["key"]
131
+ const sortValue = request.headers["sort-value"]
132
+ const value = request.headers["content-type"] == "text/plain"
133
+ ? await Readable.Text(request)
134
+ : await Readable.Buffer(request)
135
+ await ht.set(key, value, sortValue)
136
+ response.writeHead(200, {
137
+ "Content-Type": "text/plain",
138
+ })
139
+ response.end("OK")
140
+ },
141
+
142
+ eq("get"), async () => {
143
+ const key = request.headers["key"]
144
+ const value = await ht.get(key)
145
+ if (value === undefined) {
146
+ response.writeHead(499, {
131
147
  "Content-Type": "text/plain",
132
148
  })
133
- response.end("OK")
134
- },
135
-
136
- eq("set"), async () => {
137
- const { key, value, sortValue } = await Readable.JSON(request)
138
- await ht.set(key, value, sortValue)
149
+ response.end("Resource Not Found")
150
+ } else {
139
151
  response.writeHead(200, {
140
152
  "Content-Type": "text/plain",
141
153
  })
142
- response.end("OK")
143
- },
154
+ response.end(value)
155
+ }
156
+ },
144
157
 
145
- eq("get"), async () => {
146
- const { key } = await Readable.JSON(request)
147
- const value = await ht.get(key)
148
- if (value === undefined) {
149
- response.writeHead(499, {
150
- "Content-Type": "text/plain",
151
- })
152
- response.end("Resource Not Found")
153
- } else {
154
- response.writeHead(200, {
155
- "Content-Type": "text/plain",
156
- })
157
- response.end(value)
158
- }
159
- },
158
+ eq("getBinary"), async () => {
159
+ const key = request.headers["key"]
160
+ const value = await ht.getBinary(key)
161
+ response.writeHead(200, {
162
+ "Content-Type": "application/octet-stream",
163
+ })
164
+ response.end(value)
165
+ },
160
166
 
161
- eq("getBinary"), async () => {
162
- const { key } = await Readable.JSON(request)
163
- const value = await ht.getBinary(key)
164
- response.writeHead(200, {
165
- "Content-Type": "application/octet-stream",
166
- })
167
- response.end(value)
168
- },
167
+ eq("delete"), async () => {
168
+ const key = request.headers["key"]
169
+ const didDelete = await ht.delete(key)
170
+ response.writeHead(200, {
171
+ "Content-Type": "text/plain",
172
+ })
173
+ response.end(String(didDelete))
174
+ },
169
175
 
170
- eq("delete"), async () => {
171
- const { key } = await Readable.JSON(request)
172
- const didDelete = await ht.delete(key)
173
- response.writeHead(200, {
174
- "Content-Type": "text/plain",
175
- })
176
- response.end(String(didDelete))
177
- },
176
+ eq("count"), async () => {
177
+ const count = ht.count()
178
+ response.writeHead(200, {
179
+ "Content-Type": "text/plain",
180
+ })
181
+ response.end(String(count))
182
+ },
178
183
 
179
- eq("count"), async () => {
180
- const count = ht.count()
181
- response.writeHead(200, {
182
- "Content-Type": "text/plain",
183
- })
184
- response.end(String(count))
185
- },
184
+ eq("iterator"), async () => {
185
+ const valueType = request.headers["value-type"]
186
+ const options = filter({ valueType }, __)
187
+ response.writeHead(200, {
188
+ "Content-Type": "application/octet-stream",
189
+ "Content-Encoding": "utf8",
190
+ })
191
+ const count = ht.count()
192
+ for await (const value of ht.iterator(options)) {
193
+ response.write(value)
194
+ response.write("")
195
+ }
196
+ response.end()
197
+ },
186
198
 
187
- eq("iterator"), async () => {
188
- const options = await Readable.JSON(request)
189
- response.writeHead(200, {
190
- "Content-Type": "application/octet-stream",
191
- "Content-Encoding": "utf8",
192
- })
193
- const count = ht.count()
194
- for await (const value of ht.iterator(options)) {
195
- response.write(value)
196
- response.write("")
197
- }
198
- response.end()
199
- },
199
+ eq("entriesIterator"), async () => {
200
+ const valueType = request.headers["value-type"]
201
+ const options = filter({ valueType }, __)
202
+ response.writeHead(200, {
203
+ "Content-Type": "application/octet-stream",
204
+ "Content-Encoding": "utf8",
205
+ })
206
+ const count = ht.count()
207
+ for await (const [key, value] of ht.entriesIterator(options)) {
208
+ response.write(key)
209
+ response.write("")
210
+ response.write(value)
211
+ response.write("")
212
+ }
213
+ response.end()
214
+ },
200
215
 
201
- eq("entriesIterator"), async () => {
202
- const options = await Readable.JSON(request)
203
- response.writeHead(200, {
204
- "Content-Type": "application/octet-stream",
205
- "Content-Encoding": "utf8",
206
- })
207
- const count = ht.count()
208
- for await (const [key, value] of ht.entriesIterator(options)) {
209
- response.write(key)
210
- response.write("")
211
- response.write(value)
212
- response.write("")
213
- }
214
- response.end()
215
- },
216
+ eq("forwardIterator"), async () => {
217
+ const valueType = request.headers["value-type"]
218
+ const exclusiveStartKey = request.headers["exclusive-start-key"]
219
+ const startingSortValue = request.headers["starting-sort-value"]
220
+ const endingSortValue = request.headers["ending-sort-value"]
221
+ const options = filter({
222
+ valueType,
223
+ exclusiveStartKey,
224
+ startingSortValue,
225
+ endingSortValue,
226
+ }, __)
216
227
 
217
- eq("forwardIterator"), async () => {
218
- const options = await Readable.JSON(request)
219
- response.writeHead(200, {
220
- "Content-Type": "application/octet-stream",
221
- "Content-Encoding": "utf8",
222
- })
223
- const count = ht.count()
224
- for await (const value of ht.forwardIterator(options)) {
225
- response.write(value)
226
- response.write("")
227
- }
228
- response.end()
229
- },
228
+ response.writeHead(200, {
229
+ "Content-Type": "application/octet-stream",
230
+ "Content-Encoding": "utf8",
231
+ })
232
+ const count = ht.count()
233
+ for await (const value of ht.forwardIterator(options)) {
234
+ response.write(value)
235
+ response.write("")
236
+ }
237
+ response.end()
238
+ },
230
239
 
231
- eq("reverseIterator"), async () => {
232
- const options = await Readable.JSON(request)
233
- response.writeHead(200, {
234
- "Content-Type": "application/octet-stream",
235
- "Content-Encoding": "utf8",
236
- })
237
- const count = ht.count()
238
- for await (const value of ht.reverseIterator(options)) {
239
- response.write(value)
240
- response.write("")
241
- }
242
- response.end()
243
- },
240
+ eq("reverseIterator"), async () => {
241
+ const valueType = request.headers["value-type"]
242
+ const exclusiveStartKey = request.headers["exclusive-start-key"]
243
+ const startingSortValue = request.headers["starting-sort-value"]
244
+ const endingSortValue = request.headers["ending-sort-value"]
245
+ const options = filter({
246
+ valueType,
247
+ exclusiveStartKey,
248
+ startingSortValue,
249
+ endingSortValue,
250
+ }, __)
244
251
 
245
- eq("forwardEntriesIterator"), async () => {
246
- const options = await Readable.JSON(request)
247
- response.writeHead(200, {
248
- "Content-Type": "application/octet-stream",
249
- "Content-Encoding": "utf8",
250
- })
251
- const count = ht.count()
252
- for await (const [key, value, sortValue] of ht.forwardEntriesIterator(options)) {
253
- response.write(key)
254
- response.write("")
255
- response.write(value)
256
- response.write("")
257
- response.write(String(sortValue))
258
- response.write("")
259
- }
260
- response.end()
261
- },
252
+ response.writeHead(200, {
253
+ "Content-Type": "application/octet-stream",
254
+ "Content-Encoding": "utf8",
255
+ })
256
+ const count = ht.count()
257
+ for await (const value of ht.reverseIterator(options)) {
258
+ response.write(value)
259
+ response.write("")
260
+ }
261
+ response.end()
262
+ },
262
263
 
263
- eq("reverseEntriesIterator"), async () => {
264
- const options = await Readable.JSON(request)
265
- response.writeHead(200, {
266
- "Content-Type": "application/octet-stream",
267
- "Content-Encoding": "utf8",
268
- })
269
- const count = ht.count()
270
- for await (const [key, value, sortValue] of ht.reverseEntriesIterator(options)) {
271
- response.write(key)
272
- response.write("")
273
- response.write(value)
274
- response.write("")
275
- response.write(String(sortValue))
276
- response.write("")
277
- }
278
- response.end()
279
- },
264
+ eq("forwardEntriesIterator"), async () => {
265
+ const valueType = request.headers["value-type"]
266
+ const exclusiveStartKey = request.headers["exclusive-start-key"]
267
+ const startingSortValue = request.headers["starting-sort-value"]
268
+ const endingSortValue = request.headers["ending-sort-value"]
269
+ const options = filter({
270
+ valueType,
271
+ exclusiveStartKey,
272
+ startingSortValue,
273
+ endingSortValue,
274
+ }, __)
280
275
 
281
- async () => {
282
- const error = new Error("Forbidden: Hash Table Method")
283
- error.code = "403"
284
- throw error
285
- },
286
- )
276
+ response.writeHead(200, {
277
+ "Content-Type": "application/octet-stream",
278
+ "Content-Encoding": "utf8",
279
+ })
280
+ const count = ht.count()
281
+ for await (const [key, value, sortValue] of ht.forwardEntriesIterator(options)) {
282
+ response.write(key)
283
+ response.write("")
284
+ response.write(value)
285
+ response.write("")
286
+ response.write(String(sortValue))
287
+ response.write("")
288
+ }
289
+ response.end()
290
+ },
291
+
292
+ eq("reverseEntriesIterator"), async () => {
293
+ const valueType = request.headers["value-type"]
294
+ const exclusiveStartKey = request.headers["exclusive-start-key"]
295
+ const startingSortValue = request.headers["starting-sort-value"]
296
+ const endingSortValue = request.headers["ending-sort-value"]
297
+ const options = filter({
298
+ valueType,
299
+ exclusiveStartKey,
300
+ startingSortValue,
301
+ endingSortValue,
302
+ }, __)
303
+
304
+ response.writeHead(200, {
305
+ "Content-Type": "application/octet-stream",
306
+ "Content-Encoding": "utf8",
307
+ })
308
+ const count = ht.count()
309
+ for await (const [key, value, sortValue] of ht.reverseEntriesIterator(options)) {
310
+ response.write(key)
311
+ response.write("")
312
+ response.write(value)
313
+ response.write("")
314
+ response.write(String(sortValue))
315
+ response.write("")
316
+ }
317
+ response.end()
318
+ },
319
+
320
+ async () => {
321
+ const error = new Error("Forbidden: Hash Table Method")
322
+ error.code = "403"
323
+ throw error
324
+ },
325
+ )
287
326
 
288
- } else {
289
- response.writeHead(404, {
290
- "Content-Type": "text/plain",
291
- })
292
- response.end("Not Found")
293
- }
294
327
  }
295
328
  }
296
329
 
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Presidium DB Client and Server may be freely distributed under the CFOSS license.
3
+ */
4
+
5
+ require("rubico/global")
6
+ const HTTP = require("presidium/HTTP")
7
+ const loadBalancerHandler = require("./loadBalancerHandler")
8
+ const healthCheckHandler = require("./healthCheckHandler")
9
+ const notFoundHandler = require("./notFoundHandler")
10
+ const errorHandler = require("./errorHandler")
11
+
12
+ /**
13
+ * @name DatabaseLoadbalancer
14
+ *
15
+ * @docs
16
+ * ```specscript
17
+ * module HTTP "https://presidium.services/docs/HTTP"
18
+ *
19
+ * HTTP.Handler(HTTP.Request, HTTP.Response) -> ()
20
+ *
21
+ * new DatabaseLoadbalancer(
22
+ * hash(tableName string, key string, length number) -> Promise|number,
23
+ * options {
24
+ * serverIps: Array<string>,
25
+ * },
26
+ * ...handlers Arguments<HTTP.Handler>
27
+ * )
28
+ * ```
29
+ */
30
+ class DatabaseLoadbalancer {
31
+ constructor(hash, options, ...handlers) {
32
+ this.hash = hash
33
+ this.serverIps = options.serverIps
34
+ this.handlers = handlers
35
+ this.clients = new Map()
36
+
37
+ const router = tryCatch(
38
+ switchCase(
39
+ request => request.path == "/health",
40
+ healthCheckHandler,
41
+
42
+ request => request.pathDepth == 2,
43
+ curry.call(loadBalancerHandler, this),
44
+
45
+ notFoundHandler,
46
+ ),
47
+ errorHandler
48
+ )
49
+
50
+ const handler = all.series([
51
+ ...this.handlers,
52
+ request => {
53
+ const { pathname } = new URL(`http://*${request.url}`)
54
+ request.path = decodeURIComponent(
55
+ pathname == "/" ? pathname
56
+ : pathname.endsWith("/") ? pathname.slice(0, -1)
57
+ : pathname
58
+ )
59
+ request.pathSegments = request.path.slice(1).split("/")
60
+ request.pathDepth = request.pathSegments.length
61
+ },
62
+ router,
63
+ ])
64
+
65
+ this.server = HTTP.Server(handler)
66
+ }
67
+
68
+ /**
69
+ * @name listen
70
+ *
71
+ * @docs
72
+ * ```specscript
73
+ * listen(port number) -> ()
74
+ * listen(port number, callback function) -> ()
75
+ * ```
76
+ */
77
+ listen(port, callback) {
78
+ this.server.listen(port, callback)
79
+ }
80
+
81
+ /**
82
+ * @name close
83
+ *
84
+ * @docs
85
+ * ```specscript
86
+ * close() -> ()
87
+ * ```
88
+ */
89
+ close() {
90
+ this.server.close()
91
+ }
92
+
93
+ }
94
+
95
+ module.exports = DatabaseLoadbalancer
package/DatabaseServer.js CHANGED
@@ -7,12 +7,19 @@ const HTTP = require("presidium/HTTP")
7
7
  const DiskHashTable = require("presidium/DiskHashTable")
8
8
  const DiskSortedHashTable = require("presidium/DiskSortedHashTable")
9
9
  const DatabaseHandler = require("./DatabaseHandler")
10
+ const healthCheckHandler = require("./healthCheckHandler")
11
+ const notFoundHandler = require("./notFoundHandler")
12
+ const errorHandler = require("./errorHandler")
10
13
 
11
14
  /**
12
15
  * @name DatabaseServer
13
16
  *
14
17
  * @docs
15
18
  * ```specscript
19
+ * module HTTP "https://presidium.services/docs/HTTP"
20
+ *
21
+ * HTTP.Handler(request HTTP.Request, response HTTP.Response) -> ()
22
+ *
16
23
  * new DatabaseServer(options {
17
24
  * databasePath: string,
18
25
  * manifestTableName: string,
@@ -21,11 +28,11 @@ const DatabaseHandler = require("./DatabaseHandler")
21
28
  * defaultTableInitialLength: number,
22
29
  * defaultTableItemSize: number,
23
30
  * defaultDiskSortedHashTableDegree: number,
24
- * }) -> server DatabaseServer
31
+ * }, ...handlers Arguments<HTTP.Handler>) -> server DatabaseServer
25
32
  * ```
26
33
  */
27
34
  class DatabaseServer {
28
- constructor(options) {
35
+ constructor(options, ...handlers) {
29
36
  this.databasePath = options.databasePath
30
37
  this.manifestTableName = options.manifestTableName ?? "Manifest"
31
38
  this.manifestTableInitialLength = options.manifestTableInitialLength ?? 1048576
@@ -33,6 +40,7 @@ class DatabaseServer {
33
40
  this.defaultTableInitialLength = options.defaultTableInitialLength ?? 1048576
34
41
  this.defaultTableItemSize = options.defaultTableItemSize ?? 1024
35
42
  this.defaultDiskSortedHashTableDegree = options.defaultDiskSortedHashTableDegree ?? 2
43
+ this.handlers = handlers
36
44
  this.server = null
37
45
  this.hts = new Map()
38
46
  }
@@ -67,16 +75,34 @@ class DatabaseServer {
67
75
  hts: this.hts,
68
76
  })
69
77
 
70
- const handler = tryCatch(
71
- databaseHandler,
72
- function errorHandler(error, request, response) {
73
- response.writeHead(error.code ?? 500, {
74
- "Content-Type": "text/plain",
75
- })
76
- response.end(error.message)
77
- }
78
+ const router = tryCatch(
79
+ switchCase(
80
+ request => request.path == "/health",
81
+ healthCheckHandler,
82
+
83
+ request => request.pathDepth == 2,
84
+ databaseHandler,
85
+
86
+ notFoundHandler,
87
+ ),
88
+ errorHandler
78
89
  )
79
90
 
91
+ const handler = all.series([
92
+ ...this.handlers,
93
+ request => {
94
+ const { pathname } = new URL(`http://*${request.url}`)
95
+ request.path = decodeURIComponent(
96
+ pathname == "/" ? pathname
97
+ : pathname.endsWith("/") ? pathname.slice(0, -1)
98
+ : pathname
99
+ )
100
+ request.pathSegments = request.path.slice(1).split("/")
101
+ request.pathDepth = request.pathSegments.length
102
+ },
103
+ router,
104
+ ])
105
+
80
106
  this.server = HTTP.Server(handler)
81
107
  }
82
108
 
@@ -41,6 +41,7 @@ class DiskHashTableClient {
41
41
  async clear() {
42
42
  const response = await this.http.POST(`/${this.tableName}/clear`, {
43
43
  headers: {
44
+ "Content-Type": "text/plain",
44
45
  "Client-Name": "DiskHashTableClient",
45
46
  },
46
47
  })
@@ -60,10 +61,13 @@ class DiskHashTableClient {
60
61
  async set(key, value) {
61
62
  const response = await this.http.POST(`/${this.tableName}/set`, {
62
63
  headers: {
63
- "Content-Type": "application/json",
64
+ "Content-Type": Buffer.isBuffer(value)
65
+ ? "application/octet-stream"
66
+ : "text/plain",
64
67
  "Client-Name": "DiskHashTableClient",
68
+ "Key": key,
65
69
  },
66
- body: JSON.stringify({ key, value }),
70
+ body: value,
67
71
  })
68
72
  await handleServerResponse(response)
69
73
  }
@@ -81,10 +85,10 @@ class DiskHashTableClient {
81
85
  async get(key) {
82
86
  const response = await this.http.POST(`/${this.tableName}/get`, {
83
87
  headers: {
84
- "Content-Type": "application/json",
88
+ "Content-Type": "text/plain",
85
89
  "Client-Name": "DiskHashTableClient",
90
+ "Key": key,
86
91
  },
87
- body: JSON.stringify({ key }),
88
92
  })
89
93
  if (response.status == 499) {
90
94
  return undefined
@@ -105,10 +109,10 @@ class DiskHashTableClient {
105
109
  async getBinary(key) {
106
110
  const response = await this.http.POST(`/${this.tableName}/getBinary`, {
107
111
  headers: {
108
- "Content-Type": "application/json",
112
+ "Content-Type": "text/plain",
109
113
  "Client-Name": "DiskHashTableClient",
114
+ "Key": key,
110
115
  },
111
- body: JSON.stringify({ key }),
112
116
  })
113
117
  return handleServerResponse(response)
114
118
  }
@@ -126,10 +130,10 @@ class DiskHashTableClient {
126
130
  async delete(key) {
127
131
  const response = await this.http.POST(`/${this.tableName}/delete`, {
128
132
  headers: {
129
- "Content-Type": "application/json",
133
+ "Content-Type": "text/plain",
130
134
  "Client-Name": "DiskHashTableClient",
135
+ "Key": key,
131
136
  },
132
- body: JSON.stringify({ key }),
133
137
  })
134
138
  const didDeleteText = await handleServerResponse(response)
135
139
  return didDeleteText == "true"
@@ -146,6 +150,7 @@ class DiskHashTableClient {
146
150
  async count() {
147
151
  const response = await this.http.POST(`/${this.tableName}/count`, {
148
152
  headers: {
153
+ "Content-Type": "text/plain",
149
154
  "Client-Name": "DiskHashTableClient",
150
155
  },
151
156
  })
@@ -167,10 +172,10 @@ class DiskHashTableClient {
167
172
  const { valueType } = options
168
173
  const response = await this.http.POST(`/${this.tableName}/iterator`, {
169
174
  headers: {
170
- "Content-Type": "application/json",
175
+ "Content-Type": "text/plain",
171
176
  "Client-Name": "DiskHashTableClient",
177
+ ...valueType ? { "Value-Type": valueType } : {},
172
178
  },
173
- body: JSON.stringify(options),
174
179
  })
175
180
  if (valueType == "binary") {
176
181
  return handleServerResponse(response, {
@@ -198,10 +203,10 @@ class DiskHashTableClient {
198
203
  const { valueType } = options
199
204
  const response = await this.http.POST(`/${this.tableName}/entriesIterator`, {
200
205
  headers: {
201
- "Content-Type": "application/json",
206
+ "Content-Type": "text/plain",
202
207
  "Client-Name": "DiskHashTableClient",
208
+ ...valueType ? { "Value-Type": valueType } : {},
203
209
  },
204
- body: JSON.stringify({ valueType }),
205
210
  })
206
211
 
207
212
  if (valueType == "binary") {
@@ -6,6 +6,7 @@ require("rubico/global")
6
6
  const HTTP = require("presidium/HTTP")
7
7
  const handleServerResponse = require("./handleServerResponse")
8
8
  const buffersToEntry = require("./buffersToEntry")
9
+ const parseString = require("./parseString")
9
10
 
10
11
  /**
11
12
  * @name DiskSortedHashTableClient
@@ -44,13 +45,14 @@ class DiskSortedHashTableClient {
44
45
  async clear() {
45
46
  const response = await this.http.POST(`/${this.tableName}/clear`, {
46
47
  headers: {
48
+ "Content-Type": "text/plain",
47
49
  "Client-Name": "DiskSortedHashTableClient",
48
- ...this.sortValueType ? {
49
- "Sort-Value-Type": this.sortValueType,
50
- } : {},
51
- ...this.degree ? {
52
- "Degree": Number(this.degree),
53
- } : {},
50
+
51
+ ...pipe(this, all({
52
+ "Sort-Value-Type": get("sortValueType"),
53
+ "Degree": get("degree"),
54
+ }), filter(__), map(parseString)),
55
+
54
56
  },
55
57
  })
56
58
  return handleServerResponse(response)
@@ -69,16 +71,20 @@ class DiskSortedHashTableClient {
69
71
  async set(key, value, sortValue) {
70
72
  const response = await this.http.POST(`/${this.tableName}/set`, {
71
73
  headers: {
72
- "Content-Type": "application/json",
74
+ "Content-Type": Buffer.isBuffer(value)
75
+ ? "application/octet-stream"
76
+ : "text/plain",
73
77
  "Client-Name": "DiskSortedHashTableClient",
74
- ...this.sortValueType ? {
75
- "Sort-Value-Type": this.sortValueType,
76
- } : {},
77
- ...this.degree ? {
78
- "Degree": Number(this.degree),
79
- } : {},
78
+ "Key": key,
79
+ "Sort-Value": sortValue,
80
+
81
+ ...pipe(this, all({
82
+ "Sort-Value-Type": get("sortValueType"),
83
+ "Degree": get("degree"),
84
+ }), filter(__), map(parseString)),
85
+
80
86
  },
81
- body: JSON.stringify({ key, value, sortValue }),
87
+ body: value,
82
88
  })
83
89
  await handleServerResponse(response)
84
90
  }
@@ -96,16 +102,16 @@ class DiskSortedHashTableClient {
96
102
  async get(key) {
97
103
  const response = await this.http.POST(`/${this.tableName}/get`, {
98
104
  headers: {
99
- "Content-Type": "application/json",
105
+ "Content-Type": "text/plain",
100
106
  "Client-Name": "DiskSortedHashTableClient",
101
- ...this.sortValueType ? {
102
- "Sort-Value-Type": this.sortValueType,
103
- } : {},
104
- ...this.degree ? {
105
- "Degree": Number(this.degree),
106
- } : {},
107
+ "Key": key,
108
+
109
+ ...pipe(this, all({
110
+ "Sort-Value-Type": get("sortValueType"),
111
+ "Degree": get("degree"),
112
+ }), filter(__), map(parseString)),
113
+
107
114
  },
108
- body: JSON.stringify({ key }),
109
115
  })
110
116
  if (response.status == 499) {
111
117
  return undefined
@@ -126,16 +132,16 @@ class DiskSortedHashTableClient {
126
132
  async getBinary(key) {
127
133
  const response = await this.http.POST(`/${this.tableName}/getBinary`, {
128
134
  headers: {
129
- "Content-Type": "application/json",
135
+ "Content-Type": "text/plain",
130
136
  "Client-Name": "DiskSortedHashTableClient",
131
- ...this.sortValueType ? {
132
- "Sort-Value-Type": this.sortValueType,
133
- } : {},
134
- ...this.degree ? {
135
- "Degree": Number(this.degree),
136
- } : {},
137
+ "Key": key,
138
+
139
+ ...pipe(this, all({
140
+ "Sort-Value-Type": get("sortValueType"),
141
+ "Degree": get("degree"),
142
+ }), filter(__), map(parseString)),
143
+
137
144
  },
138
- body: JSON.stringify({ key }),
139
145
  })
140
146
  return handleServerResponse(response)
141
147
  }
@@ -157,16 +163,22 @@ class DiskSortedHashTableClient {
157
163
  const { valueType } = options
158
164
  const response = await this.http.POST(`/${this.tableName}/forwardIterator`, {
159
165
  headers: {
160
- "Content-Type": "application/json",
166
+ "Content-Type": "text/plain",
161
167
  "Client-Name": "DiskSortedHashTableClient",
162
- ...this.sortValueType ? {
163
- "Sort-Value-Type": this.sortValueType,
164
- } : {},
165
- ...this.degree ? {
166
- "Degree": Number(this.degree),
167
- } : {},
168
+
169
+ ...pipe(options, all({
170
+ "Exclusive-Start-Key": get("exclusiveStartKey"),
171
+ "Starting-Sort-Value": get("startingSortValue"),
172
+ "Ending-Sort-Value": get("endingSortValue"),
173
+ "Value-Type": get("valueType"),
174
+ }), filter(__), map(parseString)),
175
+
176
+ ...pipe(this, all({
177
+ "Sort-Value-Type": get("sortValueType"),
178
+ "Degree": get("degree"),
179
+ }), filter(__), map(parseString)),
180
+
168
181
  },
169
- body: JSON.stringify(options),
170
182
  })
171
183
  if (valueType == "binary") {
172
184
  return handleServerResponse(response, {
@@ -197,16 +209,22 @@ class DiskSortedHashTableClient {
197
209
  const { valueType } = options
198
210
  const response = await this.http.POST(`/${this.tableName}/reverseIterator`, {
199
211
  headers: {
200
- "Content-Type": "application/json",
212
+ "Content-Type": "text/plain",
201
213
  "Client-Name": "DiskSortedHashTableClient",
202
- ...this.sortValueType ? {
203
- "Sort-Value-Type": this.sortValueType,
204
- } : {},
205
- ...this.degree ? {
206
- "Degree": Number(this.degree),
207
- } : {},
214
+
215
+ ...pipe(options, all({
216
+ "Exclusive-Start-Key": get("exclusiveStartKey"),
217
+ "Starting-Sort-Value": get("startingSortValue"),
218
+ "Ending-Sort-Value": get("endingSortValue"),
219
+ "Value-Type": get("valueType"),
220
+ }), filter(__), map(parseString)),
221
+
222
+ ...pipe(this, all({
223
+ "Sort-Value-Type": get("sortValueType"),
224
+ "Degree": get("degree"),
225
+ }), filter(__), map(parseString)),
226
+
208
227
  },
209
- body: JSON.stringify(options),
210
228
  })
211
229
  if (valueType == "binary") {
212
230
  return handleServerResponse(response, {
@@ -237,16 +255,22 @@ class DiskSortedHashTableClient {
237
255
  const { valueType } = options
238
256
  const response = await this.http.POST(`/${this.tableName}/forwardEntriesIterator`, {
239
257
  headers: {
240
- "Content-Type": "application/json",
258
+ "Content-Type": "text/plain",
241
259
  "Client-Name": "DiskSortedHashTableClient",
242
- ...this.sortValueType ? {
243
- "Sort-Value-Type": this.sortValueType,
244
- } : {},
245
- ...this.degree ? {
246
- "Degree": Number(this.degree),
247
- } : {},
260
+
261
+ ...pipe(options, all({
262
+ "Exclusive-Start-Key": get("exclusiveStartKey"),
263
+ "Starting-Sort-Value": get("startingSortValue"),
264
+ "Ending-Sort-Value": get("endingSortValue"),
265
+ "Value-Type": get("valueType"),
266
+ }), filter(__), map(parseString)),
267
+
268
+ ...pipe(this, all({
269
+ "Sort-Value-Type": get("sortValueType"),
270
+ "Degree": get("degree"),
271
+ }), filter(__), map(parseString)),
272
+
248
273
  },
249
- body: JSON.stringify(options),
250
274
  })
251
275
  if (valueType == "binary") {
252
276
  const iter = await handleServerResponse(response, {
@@ -278,16 +302,22 @@ class DiskSortedHashTableClient {
278
302
  const { valueType } = options
279
303
  const response = await this.http.POST(`/${this.tableName}/reverseEntriesIterator`, {
280
304
  headers: {
281
- "Content-Type": "application/json",
305
+ "Content-Type": "text/plain",
282
306
  "Client-Name": "DiskSortedHashTableClient",
283
- ...this.sortValueType ? {
284
- "Sort-Value-Type": this.sortValueType,
285
- } : {},
286
- ...this.degree ? {
287
- "Degree": Number(this.degree),
288
- } : {},
307
+
308
+ ...pipe(options, all({
309
+ "Exclusive-Start-Key": get("exclusiveStartKey"),
310
+ "Starting-Sort-Value": get("startingSortValue"),
311
+ "Ending-Sort-Value": get("endingSortValue"),
312
+ "Value-Type": get("valueType"),
313
+ }), filter(__), map(parseString)),
314
+
315
+ ...pipe(this, all({
316
+ "Sort-Value-Type": get("sortValueType"),
317
+ "Degree": get("degree"),
318
+ }), filter(__), map(parseString)),
319
+
289
320
  },
290
- body: JSON.stringify(options),
291
321
  })
292
322
 
293
323
  if (valueType == "binary") {
@@ -319,16 +349,16 @@ class DiskSortedHashTableClient {
319
349
  async delete(key) {
320
350
  const response = await this.http.POST(`/${this.tableName}/delete`, {
321
351
  headers: {
322
- "Content-Type": "application/json",
352
+ "Content-Type": "text/plain",
323
353
  "Client-Name": "DiskSortedHashTableClient",
324
- ...this.sortValueType ? {
325
- "Sort-Value-Type": this.sortValueType,
326
- } : {},
327
- ...this.degree ? {
328
- "Degree": Number(this.degree),
329
- } : {},
354
+ "Key": key,
355
+
356
+ ...pipe(this, all({
357
+ "Sort-Value-Type": get("sortValueType"),
358
+ "Degree": get("degree"),
359
+ }), filter(__), map(parseString)),
360
+
330
361
  },
331
- body: JSON.stringify({ key }),
332
362
  })
333
363
  const didDeleteText = await handleServerResponse(response)
334
364
  return didDeleteText == "true"
@@ -345,13 +375,14 @@ class DiskSortedHashTableClient {
345
375
  async count() {
346
376
  const response = await this.http.POST(`/${this.tableName}/count`, {
347
377
  headers: {
378
+ "Content-Type": "text/plain",
348
379
  "Client-Name": "DiskSortedHashTableClient",
349
- ...this.sortValueType ? {
350
- "Sort-Value-Type": this.sortValueType,
351
- } : {},
352
- ...this.degree ? {
353
- "Degree": Number(this.degree),
354
- } : {},
380
+
381
+ ...pipe(this, all({
382
+ "Sort-Value-Type": get("sortValueType"),
383
+ "Degree": get("degree"),
384
+ }), filter(__), map(parseString)),
385
+
355
386
  },
356
387
  })
357
388
  return Number(await handleServerResponse(response))
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @name errorHandler
3
+ *
4
+ * @docs
5
+ * ```specscript
6
+ * module HTTP "https://presidium.services/docs/HTTP"
7
+ *
8
+ * errorHandler(error Error, request HTTP.Request, response HTTP.Response) -> ()
9
+ * ```
10
+ */
11
+ function errorHandler(error, request, response) {
12
+ response.writeHead(error.code ?? 500, {
13
+ "Content-Type": "text/plain",
14
+ })
15
+ response.end(error.message)
16
+ }
17
+
18
+ module.exports = errorHandler
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @name healthCheckHandler
3
+ *
4
+ * @docs
5
+ * ```specscript
6
+ * module HTTP "https://presidium.services/docs/HTTP"
7
+ *
8
+ * healthCheckHandler(request HTTP.Request, response HTTP.Response) -> ()
9
+ * ```
10
+ */
11
+ function healthCheckHandler(request, response) {
12
+ response.writeHead(200, {
13
+ 'Content-Type': 'text/plain',
14
+ })
15
+ response.end('OK')
16
+ }
17
+
18
+ module.exports = healthCheckHandler
@@ -0,0 +1,40 @@
1
+ const HTTP = require("presidium/HTTP")
2
+
3
+ /**
4
+ * @name loadBalancerHandler
5
+ *
6
+ * @docs
7
+ * ```specscript
8
+ * module HTTP "https://presidium.services/docs/HTTP"
9
+ *
10
+ * loadBalancerHandler(HTTP.Request, HTTP.Response) -> ()
11
+ * ```
12
+ */
13
+ async function loadBalancerHandler(request, response) {
14
+ const tableName = request.pathSegments[0]
15
+ const key = request.headers["key"]
16
+
17
+ let index = this.hash(tableName, key)
18
+ if (typeof index.then == "function") {
19
+ index = await index
20
+ }
21
+
22
+ const serverIp = this.serverIps[index]
23
+ let client
24
+ if (this.clients.has(serverIp)) {
25
+ client = this.clients.get(serverIp)
26
+ } else {
27
+ client = new HTTP(serverIp)
28
+ this.clients.set(serverIp, client)
29
+ }
30
+
31
+ const serverResponse = await client[request.method](request.url, {
32
+ headers: request.headers,
33
+ body: request,
34
+ })
35
+
36
+ response.writeHead(serverResponse.status, serverResponse.headers)
37
+ serverResponse.pipe(response)
38
+ }
39
+
40
+ module.exports = loadBalancerHandler
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @name notFoundHandler
3
+ *
4
+ * @docs
5
+ * ```specscript
6
+ * module HTTP "https://presidium.services/docs/HTTP"
7
+ *
8
+ * notFoundHandler(request HTTP.Request, response HTTP.Response) -> ()
9
+ * ```
10
+ */
11
+ function notFoundHandler(request, response) {
12
+ response.writeHead(404, {
13
+ "Content-Type": "text/plain",
14
+ })
15
+ response.end("Not Found")
16
+ }
17
+
18
+ module.exports = notFoundHandler
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "presidium-db-client-server",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Presidium DB client and server library",
5
5
  "author": "Richard Tong",
6
6
  "license": "CFOSS",
@@ -14,14 +14,20 @@
14
14
  "AsyncIterator.js",
15
15
  "BinaryEntriesStream.js",
16
16
  "BinaryStream.js",
17
- "DatabaseHandler.js",
18
17
  "DatabaseServer.js",
18
+ "DatabaseHandler.js",
19
+ "DatabaseLoadBalancer.js",
20
+ "loadBalancerHandler.js",
19
21
  "DiskHashTableClient.js",
20
22
  "DiskSortedHashTableClient.js",
21
23
  "TextEntriesStream.js",
22
24
  "TextStream.js",
23
25
  "buffersToEntry.js",
24
- "handleServerResponse.js"
26
+ "handleServerResponse.js",
27
+ "healthCheckHandler.js",
28
+ "notFoundHandler.js",
29
+ "errorHandler.js",
30
+ "parseString.js"
25
31
  ],
26
32
  "keywords": [
27
33
  "presidium",
@@ -39,7 +45,7 @@
39
45
  },
40
46
  "dependencies": {
41
47
  "presidium": "^4.5.0",
42
- "rubico": "^2.20.0"
48
+ "rubico": "^2.21.0"
43
49
  },
44
50
  "devDependencies": {
45
51
  "codecov": "^3.8.2"
package/parseString.js ADDED
@@ -0,0 +1,18 @@
1
+ const numberRegex = /^[0-9\.]+$/
2
+
3
+ /**
4
+ * @name parseString
5
+ *
6
+ * @docs
7
+ * ```specscript
8
+ * parseString(s string) -> number|string
9
+ * ```
10
+ */
11
+ function parseString(s) {
12
+ if (numberRegex.test(s)) {
13
+ return Number(s)
14
+ }
15
+ return s
16
+ }
17
+
18
+ module.exports = parseString