rqlite-js 2.6.0 → 3.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.
Files changed (98) hide show
  1. package/README.md +207 -29
  2. package/dist/index.cjs +904 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +329 -0
  5. package/dist/index.d.ts +329 -0
  6. package/dist/index.js +865 -0
  7. package/dist/index.js.map +1 -0
  8. package/docs/MIGRATION-v2-to-v3.md +71 -0
  9. package/package.json +56 -35
  10. package/src/api/backup/index.ts +69 -0
  11. package/src/api/backup-formats.ts +8 -0
  12. package/src/api/client/index.ts +97 -0
  13. package/src/api/consistency-levels.ts +10 -0
  14. package/src/api/data/index.ts +87 -0
  15. package/src/api/paths.ts +13 -0
  16. package/src/api/query-params.ts +15 -0
  17. package/src/api/results/data-result-error.ts +41 -0
  18. package/src/api/results/data-result.ts +91 -0
  19. package/src/api/results/data-results.ts +91 -0
  20. package/src/api/results/index.ts +7 -0
  21. package/src/api/results/result-fields.ts +14 -0
  22. package/src/api/status/index.ts +53 -0
  23. package/src/http-request/auth-keys.ts +14 -0
  24. package/src/http-request/content-types.ts +4 -0
  25. package/src/http-request/errors.ts +17 -0
  26. package/src/http-request/http-headers.ts +19 -0
  27. package/src/http-request/http-methods.ts +12 -0
  28. package/src/http-request/index.ts +676 -0
  29. package/src/http-request/retryable.ts +41 -0
  30. package/src/index.ts +38 -0
  31. package/lib/api/backup/index.js +0 -72
  32. package/lib/api/backup/index.js.map +0 -1
  33. package/lib/api/backup/test.unit.js +0 -45
  34. package/lib/api/backup/test.unit.js.map +0 -1
  35. package/lib/api/backup-formats.js +0 -16
  36. package/lib/api/backup-formats.js.map +0 -1
  37. package/lib/api/client/index.js +0 -105
  38. package/lib/api/client/index.js.map +0 -1
  39. package/lib/api/client/test.unit.js +0 -53
  40. package/lib/api/client/test.unit.js.map +0 -1
  41. package/lib/api/consistency-levels.js +0 -30
  42. package/lib/api/consistency-levels.js.map +0 -1
  43. package/lib/api/data/index.js +0 -108
  44. package/lib/api/data/index.js.map +0 -1
  45. package/lib/api/data/test.unit.js +0 -98
  46. package/lib/api/data/test.unit.js.map +0 -1
  47. package/lib/api/paths.js +0 -33
  48. package/lib/api/paths.js.map +0 -1
  49. package/lib/api/query-params.js +0 -43
  50. package/lib/api/query-params.js.map +0 -1
  51. package/lib/api/results/data-result-error.js +0 -62
  52. package/lib/api/results/data-result-error.js.map +0 -1
  53. package/lib/api/results/data-result.js +0 -141
  54. package/lib/api/results/data-result.js.map +0 -1
  55. package/lib/api/results/data-results.js +0 -142
  56. package/lib/api/results/data-results.js.map +0 -1
  57. package/lib/api/results/index.js +0 -8
  58. package/lib/api/results/index.js.map +0 -1
  59. package/lib/api/results/result-fields.js +0 -47
  60. package/lib/api/results/result-fields.js.map +0 -1
  61. package/lib/api/results/test.unit.js +0 -238
  62. package/lib/api/results/test.unit.js.map +0 -1
  63. package/lib/api/status/index.js +0 -67
  64. package/lib/api/status/index.js.map +0 -1
  65. package/lib/api/status/test.unit.js +0 -57
  66. package/lib/api/status/test.unit.js.map +0 -1
  67. package/lib/http-request/auth-keys.js +0 -15
  68. package/lib/http-request/auth-keys.js.map +0 -1
  69. package/lib/http-request/content-types.js +0 -5
  70. package/lib/http-request/content-types.js.map +0 -1
  71. package/lib/http-request/errors.js +0 -17
  72. package/lib/http-request/errors.js.map +0 -1
  73. package/lib/http-request/http-headers.js +0 -20
  74. package/lib/http-request/http-headers.js.map +0 -1
  75. package/lib/http-request/http-methods.js +0 -40
  76. package/lib/http-request/http-methods.js.map +0 -1
  77. package/lib/http-request/index.js +0 -764
  78. package/lib/http-request/index.js.map +0 -1
  79. package/lib/http-request/retryable.js +0 -42
  80. package/lib/http-request/retryable.js.map +0 -1
  81. package/lib/http-request/test.unit.js +0 -560
  82. package/lib/http-request/test.unit.js.map +0 -1
  83. package/lib/index.js +0 -39
  84. package/lib/index.js.map +0 -1
  85. package/lib/test/api-data-execute-nock.js +0 -142
  86. package/lib/test/api-data-execute-nock.js.map +0 -1
  87. package/lib/test/api-data-query-nock.js +0 -210
  88. package/lib/test/api-data-query-nock.js.map +0 -1
  89. package/lib/test/backups/backup-nock.js +0 -29
  90. package/lib/test/backups/backup-nock.js.map +0 -1
  91. package/lib/test/backups/restore-nock.js +0 -30
  92. package/lib/test/backups/restore-nock.js.map +0 -1
  93. package/lib/test/env.js +0 -15
  94. package/lib/test/env.js.map +0 -1
  95. package/lib/test/unit/index.unit.js +0 -27
  96. package/lib/test/unit/index.unit.js.map +0 -1
  97. package/lib/test.integration.js +0 -238
  98. package/lib/test.integration.js.map +0 -1
package/dist/index.js ADDED
@@ -0,0 +1,865 @@
1
+ import { Readable } from "node:stream";
2
+
3
+ //#region src/http-request/http-methods.ts
4
+ /**
5
+ * Http methods
6
+ * @module http-request/http-methods
7
+ */
8
+ const HTTP_METHOD_DELETE = "DELETE";
9
+ const HTTP_METHOD_GET = "GET";
10
+ const HTTP_METHOD_HEAD = "HEAD";
11
+ const HTTP_METHOD_OPTIONS = "OPTIONS";
12
+ const HTTP_METHOD_PATCH = "PATCH";
13
+ const HTTP_METHOD_POST = "POST";
14
+ const HTTP_METHOD_PUT = "PUT";
15
+
16
+ //#endregion
17
+ //#region src/http-request/content-types.ts
18
+ const CONTENT_TYPE_TEXT_PLAIN = "text/plain";
19
+ const CONTENT_TYPE_APPLICATION_JSON = "application/json";
20
+ const CONTENT_TYPE_APPLICATION_OCTET_STREAM = "application/octet-stream";
21
+
22
+ //#endregion
23
+ //#region src/http-request/errors.ts
24
+ /**
25
+ * Http request errors
26
+ * @module http-request/errors
27
+ */
28
+ /**
29
+ * Error when max redirect attempts have been reached
30
+ */
31
+ var ERROR_HTTP_REQUEST_MAX_REDIRECTS = class extends Error {
32
+ code;
33
+ constructor(...args) {
34
+ super(...args);
35
+ this.name = this.constructor.name;
36
+ this.code = this.constructor.name;
37
+ }
38
+ };
39
+
40
+ //#endregion
41
+ //#region src/http-request/retryable.ts
42
+ /**
43
+ * Error codes which can be retried
44
+ */
45
+ const RETRYABLE_ERROR_CODES = new Set([
46
+ "EADDRINUSE",
47
+ "EAI_AGAIN",
48
+ "ECONNREFUSED",
49
+ "ECONNRESET",
50
+ "ENETUNREACH",
51
+ "ENOTFOUND",
52
+ "EPIPE",
53
+ "ETIMEDOUT"
54
+ ]);
55
+ /**
56
+ * HTTP status codes which can be retried
57
+ */
58
+ const RETRYABLE_STATUS_CODES = new Set([
59
+ 408,
60
+ 413,
61
+ 429,
62
+ 500,
63
+ 502,
64
+ 503,
65
+ 504,
66
+ 521,
67
+ 522,
68
+ 524
69
+ ]);
70
+ /**
71
+ * HTTP methods which can be retried
72
+ */
73
+ const RETRYABLE_HTTP_METHODS = new Set([
74
+ HTTP_METHOD_DELETE,
75
+ HTTP_METHOD_GET,
76
+ HTTP_METHOD_HEAD,
77
+ HTTP_METHOD_OPTIONS,
78
+ HTTP_METHOD_PATCH,
79
+ HTTP_METHOD_POST,
80
+ HTTP_METHOD_PUT
81
+ ]);
82
+
83
+ //#endregion
84
+ //#region src/http-request/auth-keys.ts
85
+ /**
86
+ * Authentication Map key constants
87
+ * @module http-request/auth-keys
88
+ */
89
+ /**
90
+ * Username key for authentication
91
+ */
92
+ const AUTH_USERNAME = "username";
93
+ /**
94
+ * Password key for authentication
95
+ */
96
+ const AUTH_PASSWORD = "password";
97
+
98
+ //#endregion
99
+ //#region src/http-request/http-headers.ts
100
+ /**
101
+ * HTTP header name constants used by RQLite clients
102
+ * @module http-request/http-headers
103
+ */
104
+ /**
105
+ * Accept request header
106
+ */
107
+ const HTTP_HEADER_ACCEPT = "Accept";
108
+ /**
109
+ * Content-Type request header
110
+ */
111
+ const HTTP_HEADER_CONTENT_TYPE = "Content-Type";
112
+ /**
113
+ * Location response header (redirects)
114
+ */
115
+ const HTTP_HEADER_LOCATION = "Location";
116
+
117
+ //#endregion
118
+ //#region src/http-request/index.ts
119
+ /**
120
+ * The default timeout value
121
+ */
122
+ const DEAULT_TIMEOUT = 3e4;
123
+ /**
124
+ * Create default header for all HTTP requests
125
+ */
126
+ function createDefaultHeaders(headers = {}) {
127
+ const accept = headers[HTTP_HEADER_ACCEPT] || CONTENT_TYPE_APPLICATION_JSON;
128
+ return {
129
+ ...headers,
130
+ [HTTP_HEADER_ACCEPT]: accept
131
+ };
132
+ }
133
+ /**
134
+ * Clean the request path remove / from the beginning
135
+ */
136
+ function cleanPath(path) {
137
+ return String(path).replace(/^\//, "");
138
+ }
139
+ /**
140
+ * Serialize query params similar to qs arrayFormat: 'brackets'
141
+ */
142
+ function stringifyQuery(params = {}) {
143
+ const search = new URLSearchParams();
144
+ Object.entries(params).forEach(([key, value]) => {
145
+ if (typeof value === "undefined") return;
146
+ if (Array.isArray(value)) {
147
+ value.forEach((entry) => {
148
+ search.append(`${key}[]`, String(entry));
149
+ });
150
+ return;
151
+ }
152
+ search.append(key, String(value));
153
+ });
154
+ return search.toString();
155
+ }
156
+ function basicAuthHeader(username, password) {
157
+ return `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
158
+ }
159
+ function getErrorCode(error) {
160
+ if (!error || typeof error !== "object") return;
161
+ const err = error;
162
+ if (typeof err.code === "string") return err.code;
163
+ if (err.cause && typeof err.cause.code === "string") return err.cause.code;
164
+ if (err.name === "AbortError" || err.name === "TimeoutError") return "ETIMEDOUT";
165
+ }
166
+ /**
167
+ * Returns the next wait interval, in milliseconds, using an exponential
168
+ * backoff algorithm.
169
+ */
170
+ function getWaitTimeExponential(attempt = 0, base = 100, exponent = 2) {
171
+ if (attempt === 0) return 0;
172
+ return exponent ** attempt * base;
173
+ }
174
+ /**
175
+ * Generic HTTP Request class which all RQLiteJS clients should extend
176
+ */
177
+ var HttpRequest = class {
178
+ activeHostIndex = 0;
179
+ activeHostRoundRobin = true;
180
+ absoluteUriPattern = /^https?:\/\//;
181
+ hosts = [];
182
+ httpAgent;
183
+ httpsAgent;
184
+ leaderHostIndex = 0;
185
+ retryableErrorCodes = RETRYABLE_ERROR_CODES;
186
+ retryableStatusCodes = RETRYABLE_STATUS_CODES;
187
+ retryableHttpMethods = RETRYABLE_HTTP_METHODS;
188
+ exponentailBackoffBase = 100;
189
+ exponentailBackoffExponent = 1.1;
190
+ authentication = /* @__PURE__ */ new Map();
191
+ constructor(hosts, options = {}) {
192
+ this.setHosts(hosts);
193
+ if (this.getTotalHosts() === 0) throw new Error("At least one host must be provided");
194
+ const { activeHostRoundRobin = true, httpAgent, httpsAgent, retryableErrorCodes, retryableStatusCodes, retryableHttpMethods, exponentailBackoffExponent, exponentailBackoffBase, authentication } = options;
195
+ if (typeof authentication === "object") {
196
+ const { username, password } = authentication;
197
+ if (username) this.authentication.set(AUTH_USERNAME, username);
198
+ if (password) this.authentication.set(AUTH_PASSWORD, password);
199
+ }
200
+ if (typeof activeHostRoundRobin !== "undefined") this.setActiveHostRoundRobin(activeHostRoundRobin);
201
+ if (typeof httpAgent !== "undefined") this.setHttpAgent(httpAgent);
202
+ if (typeof httpsAgent !== "undefined") this.setHttpsAgent(httpsAgent);
203
+ if (retryableErrorCodes instanceof Set || Array.isArray(retryableErrorCodes)) this.setRetryableErrorCodes(Array.isArray(retryableErrorCodes) ? new Set(retryableErrorCodes) : retryableErrorCodes);
204
+ if (retryableStatusCodes instanceof Set || Array.isArray(retryableStatusCodes)) this.setRetryableStatusCodes(Array.isArray(retryableStatusCodes) ? new Set(retryableStatusCodes) : retryableStatusCodes);
205
+ if (retryableHttpMethods instanceof Set || Array.isArray(retryableHttpMethods)) this.setRetryableHttpMethods(Array.isArray(retryableHttpMethods) ? new Set(retryableHttpMethods) : retryableHttpMethods);
206
+ if (Number.isFinite(exponentailBackoffBase)) this.setExponentailBackoffBase(exponentailBackoffBase);
207
+ if (Number.isFinite(exponentailBackoffExponent)) this.setExponentailBackoffExponent(exponentailBackoffExponent);
208
+ }
209
+ setAuthentication(authentication = {}) {
210
+ const { username, password } = authentication;
211
+ if (username) this.authentication.set(AUTH_USERNAME, username);
212
+ if (password) this.authentication.set(AUTH_PASSWORD, password);
213
+ }
214
+ setExponentailBackoffBase(exponentailBackoffBase) {
215
+ this.exponentailBackoffBase = exponentailBackoffBase;
216
+ }
217
+ getExponentailBackoffBase() {
218
+ return this.exponentailBackoffBase;
219
+ }
220
+ setExponentailBackoffExponent(exponentailBackoffExponent) {
221
+ this.exponentailBackoffExponent = exponentailBackoffExponent;
222
+ }
223
+ getExponentailBackoffExponent() {
224
+ return this.exponentailBackoffExponent;
225
+ }
226
+ setRetryableErrorCodes(retryableErrorCodes) {
227
+ this.retryableErrorCodes = retryableErrorCodes;
228
+ }
229
+ getRetryableErrorCodes() {
230
+ return this.retryableErrorCodes;
231
+ }
232
+ setRetryableStatusCodes(retryableStatusCodes) {
233
+ this.retryableStatusCodes = retryableStatusCodes;
234
+ }
235
+ getRetryableStatusCodes() {
236
+ return this.retryableStatusCodes;
237
+ }
238
+ setRetryableHttpMethods(retryableHttpMethods) {
239
+ this.retryableHttpMethods = retryableHttpMethods;
240
+ }
241
+ getRetryableHttpMethods() {
242
+ return this.retryableHttpMethods;
243
+ }
244
+ setHttpAgent(httpAgent) {
245
+ this.httpAgent = httpAgent;
246
+ }
247
+ getHttpAgent() {
248
+ return this.httpAgent;
249
+ }
250
+ setHttpsAgent(httpsAgent) {
251
+ this.httpsAgent = httpsAgent;
252
+ }
253
+ getHttpsAgent() {
254
+ return this.httpsAgent;
255
+ }
256
+ setHosts(hosts) {
257
+ this.hosts = (!Array.isArray(hosts) ? String(hosts).split(",") : hosts).reduce((acc, v) => {
258
+ const host = String(v).trim().replace(/\/$/, "");
259
+ if (!host) return acc;
260
+ return acc.concat(host);
261
+ }, []);
262
+ }
263
+ getHosts() {
264
+ return this.hosts;
265
+ }
266
+ findHostIndex(host) {
267
+ let parsedHostToFind;
268
+ try {
269
+ parsedHostToFind = new URL(host);
270
+ } catch {
271
+ return -1;
272
+ }
273
+ return this.getHosts().findIndex((v) => {
274
+ try {
275
+ const parsedHost = new URL(v);
276
+ return [
277
+ "hostname",
278
+ "protocol",
279
+ "port",
280
+ "pathname"
281
+ ].every((field) => parsedHostToFind[field] === parsedHost[field]);
282
+ } catch {
283
+ return false;
284
+ }
285
+ });
286
+ }
287
+ getActiveHost(useLeader) {
288
+ const activeHostIndex = useLeader ? this.getLeaderHostIndex() : this.getActiveHostIndex();
289
+ return this.getHosts()[activeHostIndex];
290
+ }
291
+ setActiveHostIndex(activeHostIndex) {
292
+ if (!Number.isFinite(activeHostIndex)) throw new Error("The activeHostIndex should be a finite number");
293
+ const totalHosts = this.getTotalHosts();
294
+ if (activeHostIndex < 0) this.activeHostIndex = 0;
295
+ else if (activeHostIndex >= totalHosts) this.activeHostIndex = totalHosts - 1;
296
+ else this.activeHostIndex = activeHostIndex;
297
+ return this.activeHostIndex;
298
+ }
299
+ getLeaderHostIndex() {
300
+ return this.leaderHostIndex;
301
+ }
302
+ setLeaderHostIndex(leaderHostIndex) {
303
+ if (!Number.isFinite(leaderHostIndex)) throw new Error("The leaderHostIndex should be a finite number");
304
+ const totalHosts = this.getTotalHosts();
305
+ if (leaderHostIndex < 0) this.leaderHostIndex = 0;
306
+ else if (leaderHostIndex > totalHosts) this.leaderHostIndex = totalHosts - 1;
307
+ else this.leaderHostIndex = leaderHostIndex;
308
+ return this.leaderHostIndex;
309
+ }
310
+ getActiveHostIndex() {
311
+ return this.activeHostIndex;
312
+ }
313
+ setActiveHostRoundRobin(activeHostRoundRobin) {
314
+ if (typeof activeHostRoundRobin !== "boolean") throw new Error("The activeHostRoundRobin argument must be boolean");
315
+ this.activeHostRoundRobin = activeHostRoundRobin;
316
+ }
317
+ getActiveHostRoundRobin() {
318
+ return this.activeHostRoundRobin;
319
+ }
320
+ getNextActiveHostIndex(activeHostIndex = this.getActiveHostIndex()) {
321
+ const totalHosts = this.getTotalHosts();
322
+ const nextIndex = activeHostIndex + 1;
323
+ if (totalHosts === nextIndex) return 0;
324
+ return nextIndex;
325
+ }
326
+ setNextActiveHostIndex() {
327
+ if (!this.getActiveHostRoundRobin()) return;
328
+ const totalHosts = this.getTotalHosts();
329
+ if (this.getActiveHostRoundRobin() && totalHosts <= 1) return;
330
+ this.setActiveHostIndex(this.getNextActiveHostIndex());
331
+ }
332
+ getTotalHosts() {
333
+ return this.getHosts().length;
334
+ }
335
+ uriIsAbsolute(uri) {
336
+ return this.absoluteUriPattern.test(uri);
337
+ }
338
+ requestIsRetryable(options = {}) {
339
+ const { statusCode, errorCode, httpMethod } = options;
340
+ if (!httpMethod || !this.getRetryableHttpMethods().has(httpMethod)) return false;
341
+ if (statusCode && this.getRetryableStatusCodes().has(statusCode)) return true;
342
+ if (errorCode && this.getRetryableErrorCodes().has(errorCode)) return true;
343
+ return false;
344
+ }
345
+ /**
346
+ * Perform an HTTP request using the provided options
347
+ */
348
+ async fetch(options = {}) {
349
+ const { body, headers = {}, httpMethod = HTTP_METHOD_GET, query, stream = false, timeout = DEAULT_TIMEOUT, useLeader = false, retries = this.getTotalHosts() * 3, maxRedirects = 10, attempt = 0, retryAttempt = 0, redirectAttempt = 0, attemptHostIndex, exponentailBackoffExponent = this.getExponentailBackoffExponent(), exponentailBackoffBase = this.getExponentailBackoffBase(), httpAgent = this.getHttpAgent(), httpsAgent = this.getHttpsAgent() } = options;
350
+ const activeHost = Number.isFinite(attemptHostIndex) ? this.getHosts()[attemptHostIndex] : this.getActiveHost(useLeader);
351
+ let { uri } = options;
352
+ if (!uri) throw new Error("The uri option is required");
353
+ uri = this.uriIsAbsolute(uri) ? uri : `${activeHost}/${cleanPath(uri)}`;
354
+ const requestHeaders = createDefaultHeaders(headers);
355
+ if (this.authentication.size) requestHeaders.Authorization = basicAuthHeader(this.authentication.get(AUTH_USERNAME) || "", this.authentication.get(AUTH_PASSWORD) || "");
356
+ const url = new URL(uri);
357
+ if (query && typeof query === "object") {
358
+ const serialized = stringifyQuery(query);
359
+ if (serialized) url.search = serialized;
360
+ }
361
+ const controller = new AbortController();
362
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
363
+ const fetchInit = {
364
+ method: httpMethod,
365
+ headers: requestHeaders,
366
+ redirect: "manual",
367
+ signal: controller.signal
368
+ };
369
+ const agent = url.protocol === "https:" ? httpsAgent : httpAgent;
370
+ if (agent && typeof agent.dispatch === "function") {
371
+ const init = fetchInit;
372
+ init.dispatcher = agent;
373
+ }
374
+ if (typeof body !== "undefined" && body !== null) if (Buffer.isBuffer(body) || typeof body === "string") fetchInit.body = body;
375
+ else {
376
+ fetchInit.body = JSON.stringify(body);
377
+ if (!requestHeaders[HTTP_HEADER_ACCEPT]) requestHeaders[HTTP_HEADER_ACCEPT] = CONTENT_TYPE_APPLICATION_JSON;
378
+ if (!(HTTP_HEADER_CONTENT_TYPE in requestHeaders) && !Object.keys(requestHeaders).some((k) => k.toLowerCase() === "content-type")) requestHeaders[HTTP_HEADER_CONTENT_TYPE] = CONTENT_TYPE_APPLICATION_JSON;
379
+ fetchInit.headers = requestHeaders;
380
+ }
381
+ const retryOrRedirect = async (responseStatus, responseHeaders, errorCode, error) => {
382
+ const retryable = this.requestIsRetryable({
383
+ statusCode: responseStatus,
384
+ errorCode,
385
+ httpMethod
386
+ });
387
+ let nextAttemptHostIndex = Number.isFinite(attemptHostIndex) ? attemptHostIndex : this.getActiveHostIndex();
388
+ nextAttemptHostIndex += 1;
389
+ if (nextAttemptHostIndex === this.getTotalHosts()) nextAttemptHostIndex = 0;
390
+ if (responseStatus === 301 || responseStatus === 302) {
391
+ if (redirectAttempt >= maxRedirects) throw new ERROR_HTTP_REQUEST_MAX_REDIRECTS(`The maximum number of redirects ${maxRedirects} has been reached`);
392
+ let location;
393
+ if (responseHeaders && typeof responseHeaders.get === "function") location = responseHeaders.get(HTTP_HEADER_LOCATION) || responseHeaders.get(HTTP_HEADER_LOCATION.toLowerCase()) || void 0;
394
+ else if (responseHeaders && typeof responseHeaders === "object") {
395
+ const headerMap = responseHeaders;
396
+ location = headerMap[HTTP_HEADER_LOCATION.toLowerCase()] || headerMap[HTTP_HEADER_LOCATION];
397
+ }
398
+ if (useLeader && location) {
399
+ const newLeaderHostIndex = this.findHostIndex(location);
400
+ if (newLeaderHostIndex > -1) this.setLeaderHostIndex(newLeaderHostIndex);
401
+ }
402
+ return this.fetch({
403
+ ...options,
404
+ uri: location,
405
+ attempt: attempt + 1,
406
+ redirectAttempt: redirectAttempt + 1,
407
+ attemptHostIndex: nextAttemptHostIndex
408
+ });
409
+ }
410
+ if (retryable && retryAttempt < retries) {
411
+ const waitTime = getWaitTimeExponential(retryAttempt, exponentailBackoffBase, exponentailBackoffExponent);
412
+ await new Promise((resolve) => {
413
+ setTimeout(() => resolve(), waitTime);
414
+ });
415
+ return this.fetch({
416
+ ...options,
417
+ attempt: attempt + 1,
418
+ retryAttempt: retryAttempt + 1,
419
+ attemptHostIndex: nextAttemptHostIndex
420
+ });
421
+ }
422
+ if (error instanceof Error) throw error;
423
+ const wrapped = new Error(error?.message || String(error));
424
+ Object.assign(wrapped, error);
425
+ throw wrapped;
426
+ };
427
+ try {
428
+ const response = await fetch(url, fetchInit);
429
+ clearTimeout(timeoutId);
430
+ if (response.status === 301 || response.status === 302) return retryOrRedirect(response.status, response.headers, void 0, /* @__PURE__ */ new Error(`Redirect ${response.status}`));
431
+ if (response.status >= 400) return retryOrRedirect(response.status, response.headers, void 0, Object.assign(/* @__PURE__ */ new Error(`HTTP ${response.status}`), { response: {
432
+ status: response.status,
433
+ headers: Object.fromEntries(response.headers.entries())
434
+ } }));
435
+ if (stream) {
436
+ if (!response.body) return Readable.from([]);
437
+ return Readable.fromWeb(response.body);
438
+ }
439
+ const contentType = response.headers.get("content-type") || "";
440
+ let responseBody;
441
+ if (contentType.includes("application/json")) responseBody = await response.json();
442
+ else responseBody = await response.text();
443
+ return {
444
+ body: responseBody,
445
+ status: response.status
446
+ };
447
+ } catch (e) {
448
+ clearTimeout(timeoutId);
449
+ const errorCode = getErrorCode(e);
450
+ const err = e;
451
+ const responseStatus = err.response?.status;
452
+ const responseHeaders = err.response?.headers;
453
+ return retryOrRedirect(responseStatus, responseHeaders, errorCode, e);
454
+ }
455
+ }
456
+ async get(...args) {
457
+ const options = args[0] || {};
458
+ return this.fetch({
459
+ ...options,
460
+ httpMethod: HTTP_METHOD_GET
461
+ });
462
+ }
463
+ async post(...args) {
464
+ const options = args[0] || {};
465
+ return this.fetch({
466
+ ...options,
467
+ httpMethod: HTTP_METHOD_POST
468
+ });
469
+ }
470
+ };
471
+
472
+ //#endregion
473
+ //#region src/api/query-params.ts
474
+ /**
475
+ * RQLite HTTP query parameter name constants
476
+ * @module api/query-params
477
+ * @see https://rqlite.io/docs/api/api/
478
+ * @see https://rqlite.io/docs/api/read-consistency/
479
+ * @see https://rqlite.io/docs/guides/backup/
480
+ */
481
+ const QUERY_PARAM_LEVEL = "level";
482
+ const QUERY_PARAM_PRETTY = "pretty";
483
+ const QUERY_PARAM_TIMINGS = "timings";
484
+ const QUERY_PARAM_ATOMIC = "atomic";
485
+ const QUERY_PARAM_TRANSACTION = "transaction";
486
+ const QUERY_PARAM_Q = "q";
487
+ const QUERY_PARAM_FMT = "fmt";
488
+
489
+ //#endregion
490
+ //#region src/api/client/index.ts
491
+ /**
492
+ * Create the base HTTP query options from RQLite API options
493
+ * @see https://rqlite.io/docs/api/api/
494
+ * @see module:api/query-params
495
+ */
496
+ function createQuery(options = {}) {
497
+ const { level, pretty, timings, atomic, transaction } = options;
498
+ const query = {
499
+ [QUERY_PARAM_LEVEL]: level,
500
+ [QUERY_PARAM_PRETTY]: pretty,
501
+ [QUERY_PARAM_TIMINGS]: timings,
502
+ [QUERY_PARAM_ATOMIC]: atomic,
503
+ [QUERY_PARAM_TRANSACTION]: transaction
504
+ };
505
+ return Object.entries(query).reduce((acc, entry) => {
506
+ const [key, val] = entry;
507
+ if (typeof val !== "undefined") acc[key] = val;
508
+ return acc;
509
+ }, {});
510
+ }
511
+ /**
512
+ * Base API client for RQLite which abstracts the HTTP calls from the user
513
+ */
514
+ var ApiClient = class extends HttpRequest {
515
+ /**
516
+ * Perform a RQLite data API get request
517
+ * @see https://rqlite.io/docs/api/api/
518
+ */
519
+ async get(...args) {
520
+ const path = args[0];
521
+ const sql = args[1];
522
+ const options = args[2] || {};
523
+ const { useLeader } = options;
524
+ if (!path) throw new Error("The path argument is required");
525
+ return super.get({
526
+ useLeader,
527
+ uri: path,
528
+ httpMethod: HTTP_METHOD_GET,
529
+ query: {
530
+ ...createQuery(options),
531
+ [QUERY_PARAM_Q]: sql
532
+ }
533
+ });
534
+ }
535
+ /**
536
+ * Perform a RQLite data API post request
537
+ * @see https://rqlite.io/docs/api/api/
538
+ */
539
+ async post(...args) {
540
+ const path = args[0];
541
+ const sql = args[1];
542
+ const options = args[2] || {};
543
+ const { useLeader } = options;
544
+ if (!path) throw new Error("The path argument is required");
545
+ return super.post({
546
+ useLeader,
547
+ uri: path,
548
+ httpMethod: HTTP_METHOD_POST,
549
+ query: createQuery(options),
550
+ body: Array.isArray(sql) ? sql : [sql]
551
+ });
552
+ }
553
+ };
554
+
555
+ //#endregion
556
+ //#region src/api/results/result-fields.ts
557
+ /**
558
+ * RQLite JSON response field name constants
559
+ * @module api/results/result-fields
560
+ * @see https://rqlite.io/docs/api/api/
561
+ */
562
+ const RESULT_FIELD_TIME = "time";
563
+ const RESULT_FIELD_ROWS_AFFECTED = "rows_affected";
564
+ const RESULT_FIELD_LAST_INSERT_ID = "last_insert_id";
565
+ const RESULT_FIELD_COLUMNS = "columns";
566
+ const RESULT_FIELD_VALUES = "values";
567
+ const RESULT_FIELD_ERROR = "error";
568
+ const RESULT_FIELD_ERROR_V2 = "error_v2";
569
+ const RESULT_FIELD_RESULTS = "results";
570
+
571
+ //#endregion
572
+ //#region src/api/results/data-result.ts
573
+ /**
574
+ * A class that represents one data result from an RQLite query or execute API call
575
+ * @see https://rqlite.io/docs/api/api/
576
+ */
577
+ var DataResult = class {
578
+ time = 0;
579
+ lastInsertId;
580
+ rowsAffected = 0;
581
+ data = {};
582
+ constructor(result, valuesIndex) {
583
+ if (typeof result !== "object" || result === null) throw new Error("The result argument is required to be an object");
584
+ if (typeof valuesIndex !== "undefined" && !Number.isFinite(valuesIndex)) throw new Error("The valuesIndex argument is required to be a finite number when provided");
585
+ this.time = parseFloat(String(result[RESULT_FIELD_TIME] || 0));
586
+ this.rowsAffected = parseInt(String(result[RESULT_FIELD_ROWS_AFFECTED] || 0), 10);
587
+ this.lastInsertId = result[RESULT_FIELD_LAST_INSERT_ID];
588
+ if (Number.isFinite(valuesIndex)) {
589
+ const columns = result[RESULT_FIELD_COLUMNS];
590
+ const resultValues = result[RESULT_FIELD_VALUES][valuesIndex];
591
+ if (resultValues) this.data = resultValues.reduce((acc, val, i) => {
592
+ const col = columns[i];
593
+ acc[col] = val;
594
+ return acc;
595
+ }, {});
596
+ }
597
+ }
598
+ get(property) {
599
+ return this.data[property];
600
+ }
601
+ getTime() {
602
+ return this.time;
603
+ }
604
+ getLastInsertId() {
605
+ return this.lastInsertId;
606
+ }
607
+ getRowsAffected() {
608
+ return this.rowsAffected;
609
+ }
610
+ toObject() {
611
+ return JSON.parse(JSON.stringify(this.data));
612
+ }
613
+ toArray() {
614
+ return Object.values(this.data);
615
+ }
616
+ toColumnsArray() {
617
+ return Object.keys(this.data);
618
+ }
619
+ toString() {
620
+ return JSON.stringify(this.data);
621
+ }
622
+ };
623
+
624
+ //#endregion
625
+ //#region src/api/results/data-result-error.ts
626
+ /**
627
+ * A class that represents one data error result from an RQLite query or execute API call
628
+ * @see https://rqlite.io/docs/api/api/
629
+ */
630
+ var DataResultError = class extends Error {
631
+ code;
632
+ errorV2;
633
+ constructor(message, errorV2) {
634
+ super(message);
635
+ this.name = this.constructor.name;
636
+ this.code = this.constructor.name;
637
+ if (errorV2) this.errorV2 = errorV2;
638
+ }
639
+ getErrorV2() {
640
+ return this.errorV2;
641
+ }
642
+ toObject() {
643
+ const obj = { [RESULT_FIELD_ERROR]: this.message };
644
+ if (this.errorV2) obj[RESULT_FIELD_ERROR_V2] = this.errorV2;
645
+ return obj;
646
+ }
647
+ toString() {
648
+ return JSON.stringify(this.toObject());
649
+ }
650
+ };
651
+
652
+ //#endregion
653
+ //#region src/api/results/data-results.ts
654
+ /**
655
+ * A class to manage a list of results from an RQLite query or execute API response
656
+ * @see https://rqlite.io/docs/api/api/
657
+ */
658
+ var DataResults = class {
659
+ time = 0;
660
+ results = [];
661
+ constructor(data) {
662
+ this.setApiData(data);
663
+ }
664
+ setApiData(data) {
665
+ if (typeof data !== "object" || data === null) throw new Error("The data argument is required to be an object");
666
+ if (!data[RESULT_FIELD_RESULTS]) throw new Error("The data object is required to have a results property");
667
+ this.time = parseFloat(String(data[RESULT_FIELD_TIME] || 0));
668
+ this.results = (data[RESULT_FIELD_RESULTS] || []).reduce((acc, result) => {
669
+ if (typeof result === "object" && result !== null && result[RESULT_FIELD_ERROR]) return acc.concat(new DataResultError(String(result[RESULT_FIELD_ERROR]), result[RESULT_FIELD_ERROR_V2]));
670
+ const vals = result[RESULT_FIELD_VALUES];
671
+ if (!vals) return acc.concat(new DataResult(result));
672
+ const dataResults = vals.map((_v, valuesIndex) => new DataResult(result, valuesIndex));
673
+ return acc.concat(dataResults);
674
+ }, []);
675
+ }
676
+ hasError() {
677
+ return !!this.getFirstError();
678
+ }
679
+ getFirstError() {
680
+ return this.results.find((v) => v instanceof DataResultError);
681
+ }
682
+ getTime() {
683
+ return this.time;
684
+ }
685
+ get(index) {
686
+ return this.results[index];
687
+ }
688
+ getResults() {
689
+ return this.results;
690
+ }
691
+ toArray() {
692
+ return this.results.map((result) => result.toObject());
693
+ }
694
+ toString() {
695
+ const list = this.results.map((result) => result.toObject());
696
+ return JSON.stringify(list);
697
+ }
698
+ };
699
+
700
+ //#endregion
701
+ //#region src/api/paths.ts
702
+ /**
703
+ * RQLite HTTP API path constants
704
+ * @module api/paths
705
+ * @see https://rqlite.io/docs/api/api/
706
+ * @see https://rqlite.io/docs/guides/backup/
707
+ * @see https://rqlite.io/docs/guides/monitoring-rqlite/
708
+ */
709
+ const PATH_QUERY = "/db/query";
710
+ const PATH_EXECUTE = "/db/execute";
711
+ const PATH_LOAD = "/db/load";
712
+ const PATH_BACKUP = "/db/backup";
713
+ const PATH_STATUS = "/status";
714
+
715
+ //#endregion
716
+ //#region src/api/consistency-levels.ts
717
+ /**
718
+ * RQLite read consistency level constants
719
+ * @module api/consistency-levels
720
+ * @see https://rqlite.io/docs/api/read-consistency/
721
+ */
722
+ const CONSISTENCY_LEVEL_NONE = "none";
723
+ const CONSISTENCY_LEVEL_STRONG = "strong";
724
+ const CONSISTENCY_LEVEL_LINEARIZABLE = "linearizable";
725
+ const CONSISTENCY_LEVEL_WEAK = "weak";
726
+
727
+ //#endregion
728
+ //#region src/api/data/index.ts
729
+ function handleResponse(response, options = {}) {
730
+ const { raw } = options;
731
+ const { body } = response;
732
+ if (raw) return response;
733
+ return new DataResults(body);
734
+ }
735
+ /**
736
+ * Data api client to perform RQLite queries
737
+ */
738
+ var DataApiClient = class extends ApiClient {
739
+ /**
740
+ * Send an RQLite query API request to the RQLite server
741
+ * @see https://rqlite.io/docs/api/api/
742
+ * @see https://rqlite.io/docs/api/read-consistency/
743
+ */
744
+ async query(sql, options = {}) {
745
+ const { level } = options;
746
+ let { useLeader } = options;
747
+ if (level !== CONSISTENCY_LEVEL_NONE) useLeader = true;
748
+ let response;
749
+ if (Array.isArray(sql)) response = await super.post(PATH_QUERY, sql, {
750
+ ...options,
751
+ useLeader
752
+ });
753
+ else response = await super.get(PATH_QUERY, sql, {
754
+ ...options,
755
+ useLeader
756
+ });
757
+ if (!useLeader) this.setNextActiveHostIndex();
758
+ return handleResponse(response, options);
759
+ }
760
+ /**
761
+ * Send an RQLite execute API request to the RQLite server
762
+ * @see https://rqlite.io/docs/api/api/
763
+ */
764
+ async execute(sql, options = {}) {
765
+ return handleResponse(await super.post(PATH_EXECUTE, sql, options), options);
766
+ }
767
+ };
768
+
769
+ //#endregion
770
+ //#region src/api/backup-formats.ts
771
+ /**
772
+ * RQLite backup data format constants
773
+ * @module api/backup-formats
774
+ * @see https://rqlite.io/docs/guides/backup/
775
+ */
776
+ const BACKUP_DATA_FORMAT_SQL = "sql";
777
+ const BACKUP_DATA_FORMAT_DUMP = "dump";
778
+
779
+ //#endregion
780
+ //#region src/api/backup/index.ts
781
+ /**
782
+ * Backup api client to perform RQLite back up and load operations
783
+ */
784
+ var BackupApiClient = class extends HttpRequest {
785
+ /**
786
+ * Perform a SQL dump backup from the RQLite server
787
+ * @see https://rqlite.io/docs/guides/backup/
788
+ * @see module:api/backup-formats
789
+ */
790
+ async backup(format = BACKUP_DATA_FORMAT_DUMP) {
791
+ return super.get({
792
+ headers: { [HTTP_HEADER_ACCEPT]: CONTENT_TYPE_APPLICATION_OCTET_STREAM },
793
+ query: { [QUERY_PARAM_FMT]: format === BACKUP_DATA_FORMAT_SQL ? format : void 0 },
794
+ json: false,
795
+ stream: true,
796
+ uri: PATH_BACKUP,
797
+ useLeader: true
798
+ });
799
+ }
800
+ /**
801
+ * Perform a SQL restore by sending data the RQLite server
802
+ * @see https://rqlite.io/docs/guides/backup/
803
+ * @see module:api/backup-formats
804
+ */
805
+ async load(data, format = BACKUP_DATA_FORMAT_SQL) {
806
+ return super.post({
807
+ body: data,
808
+ headers: { [HTTP_HEADER_CONTENT_TYPE]: format === BACKUP_DATA_FORMAT_SQL ? CONTENT_TYPE_TEXT_PLAIN : CONTENT_TYPE_APPLICATION_OCTET_STREAM },
809
+ json: false,
810
+ stream: true,
811
+ uri: PATH_LOAD,
812
+ useLeader: true
813
+ });
814
+ }
815
+ };
816
+
817
+ //#endregion
818
+ //#region src/api/status/index.ts
819
+ /**
820
+ * Data api client to perform RQLite status queries
821
+ */
822
+ var StatusApiClient = class extends ApiClient {
823
+ /**
824
+ * Get the RQLite server status which defaults to the leader host
825
+ * @see https://rqlite.io/docs/guides/monitoring-rqlite/
826
+ */
827
+ async status(options = {}) {
828
+ return this.fetch({
829
+ ...options,
830
+ uri: PATH_STATUS,
831
+ httpMethod: HTTP_METHOD_GET,
832
+ useLeader: typeof options.useLeader === "boolean" ? options.useLeader : true
833
+ });
834
+ }
835
+ /**
836
+ * Get the RQLite server status for all hosts
837
+ * @see https://rqlite.io/docs/guides/monitoring-rqlite/
838
+ */
839
+ async statusAllHosts(options = {}) {
840
+ const hosts = this.getHosts();
841
+ const promises = hosts.map(async (_host, attemptHostIndex) => {
842
+ return {
843
+ response: await this.status({
844
+ ...options,
845
+ useLeader: false,
846
+ attemptHostIndex
847
+ }),
848
+ host: hosts[attemptHostIndex]
849
+ };
850
+ });
851
+ return Promise.all(promises);
852
+ }
853
+ };
854
+
855
+ //#endregion
856
+ //#region src/index.ts
857
+ var src_default = {
858
+ DataApiClient,
859
+ BackupApiClient,
860
+ StatusApiClient
861
+ };
862
+
863
+ //#endregion
864
+ export { BACKUP_DATA_FORMAT_DUMP, BACKUP_DATA_FORMAT_SQL, BackupApiClient, CONSISTENCY_LEVEL_LINEARIZABLE, CONSISTENCY_LEVEL_NONE, CONSISTENCY_LEVEL_STRONG, CONSISTENCY_LEVEL_WEAK, DataApiClient, PATH_BACKUP, PATH_EXECUTE, PATH_LOAD, PATH_QUERY, PATH_STATUS, StatusApiClient, src_default as default };
865
+ //# sourceMappingURL=index.js.map