@sqlitecloud/drivers 0.0.39 → 0.0.50
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/lib/{transport-tls.d.ts → drivers/connection-tls.d.ts} +7 -7
- package/lib/drivers/connection-tls.js +236 -0
- package/lib/{transport-ws.d.ts → drivers/connection-ws.d.ts} +5 -6
- package/lib/{transport-ws.js → drivers/connection-ws.js} +10 -7
- package/lib/drivers/connection.d.ts +32 -0
- package/lib/drivers/connection.js +73 -0
- package/lib/{database.js → drivers/database.js} +68 -19
- package/lib/drivers/protocol.d.ts +51 -0
- package/lib/drivers/protocol.js +291 -0
- package/lib/drivers/queue.d.ts +12 -0
- package/lib/drivers/queue.js +42 -0
- package/lib/{types.d.ts → drivers/types.d.ts} +6 -0
- package/lib/{types.js → drivers/types.js} +5 -1
- package/lib/{utilities.d.ts → drivers/utilities.d.ts} +8 -0
- package/lib/{utilities.js → drivers/utilities.js} +81 -1
- package/lib/index.d.ts +6 -8
- package/lib/index.js +14 -13
- package/lib/sqlitecloud.drivers.dev.js +629 -0
- package/lib/sqlitecloud.drivers.js +1 -0
- package/package.json +12 -3
- package/lib/connection.d.ts +0 -65
- package/lib/connection.js +0 -260
- package/lib/sqlitecloud.v0.0.39.dev.js +0 -597
- package/lib/sqlitecloud.v0.0.39.js +0 -1
- package/lib/transport-tls.js +0 -465
- /package/lib/{database.d.ts → drivers/database.d.ts} +0 -0
- /package/lib/{rowset.d.ts → drivers/rowset.d.ts} +0 -0
- /package/lib/{rowset.js → drivers/rowset.js} +0 -0
- /package/lib/{statement.d.ts → drivers/statement.d.ts} +0 -0
- /package/lib/{statement.js → drivers/statement.js} +0 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
//
|
|
3
|
+
// protocol.ts - low level protocol handling for SQLiteCloud transport
|
|
4
|
+
//
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.formatCommand = exports.popData = exports.parseRowsetChunks = exports.bufferEndsWith = exports.bufferStartsWith = exports.parseRowsetHeader = exports.parseArray = exports.parseError = exports.decompressBuffer = exports.parseCommandLength = exports.hasCommandLength = exports.ROWSET_CHUNKS_END = exports.CMD_ARRAY = exports.CMD_COMMAND = exports.CMD_COMPRESSED = exports.CMD_BLOB = exports.CMD_NULL = exports.CMD_JSON = exports.CMD_ROWSET_CHUNK = exports.CMD_ROWSET = exports.CMD_FLOAT = exports.CMD_INT = exports.CMD_ERROR = exports.CMD_ZEROSTRING = exports.CMD_STRING = void 0;
|
|
7
|
+
const types_1 = require("./types");
|
|
8
|
+
const rowset_1 = require("./rowset");
|
|
9
|
+
const lz4 = require('lz4js');
|
|
10
|
+
// The server communicates with clients via commands defined in
|
|
11
|
+
// SQLiteCloud Server Protocol (SCSP), see more at:
|
|
12
|
+
// https://github.com/sqlitecloud/sdk/blob/master/PROTOCOL.md
|
|
13
|
+
exports.CMD_STRING = '+';
|
|
14
|
+
exports.CMD_ZEROSTRING = '!';
|
|
15
|
+
exports.CMD_ERROR = '-';
|
|
16
|
+
exports.CMD_INT = ':';
|
|
17
|
+
exports.CMD_FLOAT = ',';
|
|
18
|
+
exports.CMD_ROWSET = '*';
|
|
19
|
+
exports.CMD_ROWSET_CHUNK = '/';
|
|
20
|
+
exports.CMD_JSON = '#';
|
|
21
|
+
exports.CMD_NULL = '_';
|
|
22
|
+
exports.CMD_BLOB = '$';
|
|
23
|
+
exports.CMD_COMPRESSED = '%';
|
|
24
|
+
exports.CMD_COMMAND = '^';
|
|
25
|
+
exports.CMD_ARRAY = '=';
|
|
26
|
+
// const CMD_RAWJSON = '{'
|
|
27
|
+
// const CMD_PUBSUB = '|'
|
|
28
|
+
// const CMD_RECONNECT = '@'
|
|
29
|
+
// To mark the end of the Rowset, the special string /LEN 0 0 0 is sent (LEN is always 6 in this case)
|
|
30
|
+
// https://github.com/sqlitecloud/sdk/blob/master/PROTOCOL.md#scsp-rowset-chunk
|
|
31
|
+
exports.ROWSET_CHUNKS_END = '/6 0 0 0 ';
|
|
32
|
+
//
|
|
33
|
+
// utility functions
|
|
34
|
+
//
|
|
35
|
+
/** Analyze first character to check if corresponding data type has LEN */
|
|
36
|
+
function hasCommandLength(firstCharacter) {
|
|
37
|
+
return firstCharacter == exports.CMD_INT || firstCharacter == exports.CMD_FLOAT || firstCharacter == exports.CMD_NULL ? false : true;
|
|
38
|
+
}
|
|
39
|
+
exports.hasCommandLength = hasCommandLength;
|
|
40
|
+
/** Analyze a command with explict LEN and extract it */
|
|
41
|
+
function parseCommandLength(data) {
|
|
42
|
+
return parseInt(data.subarray(1, data.indexOf(' ')).toString('utf8'));
|
|
43
|
+
}
|
|
44
|
+
exports.parseCommandLength = parseCommandLength;
|
|
45
|
+
/** Receive a compressed buffer, decompress with lz4, return buffer and datatype */
|
|
46
|
+
function decompressBuffer(buffer) {
|
|
47
|
+
const spaceIndex = buffer.indexOf(' ');
|
|
48
|
+
buffer = buffer.subarray(spaceIndex + 1);
|
|
49
|
+
// extract compressed size
|
|
50
|
+
const compressedSize = parseInt(buffer.subarray(0, buffer.indexOf(' ') + 1).toString('utf8'));
|
|
51
|
+
buffer = buffer.subarray(buffer.indexOf(' ') + 1);
|
|
52
|
+
// extract decompressed size
|
|
53
|
+
const decompressedSize = parseInt(buffer.subarray(0, buffer.indexOf(' ') + 1).toString('utf8'));
|
|
54
|
+
buffer = buffer.subarray(buffer.indexOf(' ') + 1);
|
|
55
|
+
// extract compressed dataType
|
|
56
|
+
const dataType = buffer.subarray(0, 1).toString('utf8');
|
|
57
|
+
const decompressedBuffer = Buffer.alloc(decompressedSize);
|
|
58
|
+
const compressedBuffer = buffer.subarray(buffer.length - compressedSize);
|
|
59
|
+
// lz4js library is javascript and doesn't have types so we silence the type check
|
|
60
|
+
// eslint-disable-next-line
|
|
61
|
+
const decompressionResult = lz4.decompressBlock(compressedBuffer, decompressedBuffer, 0, compressedSize, 0);
|
|
62
|
+
buffer = Buffer.concat([buffer.subarray(0, buffer.length - compressedSize), decompressedBuffer]);
|
|
63
|
+
if (decompressionResult <= 0 || decompressionResult !== decompressedSize) {
|
|
64
|
+
throw new Error(`lz4 decompression error at offset ${decompressionResult}`);
|
|
65
|
+
}
|
|
66
|
+
return { buffer, dataType };
|
|
67
|
+
}
|
|
68
|
+
exports.decompressBuffer = decompressBuffer;
|
|
69
|
+
/** Parse error message or extended error message */
|
|
70
|
+
function parseError(buffer, spaceIndex) {
|
|
71
|
+
const errorBuffer = buffer.subarray(spaceIndex + 1);
|
|
72
|
+
const errorString = errorBuffer.toString('utf8');
|
|
73
|
+
const parts = errorString.split(' ');
|
|
74
|
+
let errorCodeStr = parts.shift() || '0'; // Default errorCode is '0' if not present
|
|
75
|
+
let extErrCodeStr = '0'; // Default extended error code
|
|
76
|
+
let offsetCodeStr = '-1'; // Default offset code
|
|
77
|
+
// Split the errorCode by ':' to check for extended error codes
|
|
78
|
+
const errorCodeParts = errorCodeStr.split(':');
|
|
79
|
+
errorCodeStr = errorCodeParts[0];
|
|
80
|
+
if (errorCodeParts.length > 1) {
|
|
81
|
+
extErrCodeStr = errorCodeParts[1];
|
|
82
|
+
if (errorCodeParts.length > 2) {
|
|
83
|
+
offsetCodeStr = errorCodeParts[2];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Rest of the error string is the error message
|
|
87
|
+
const errorMessage = parts.join(' ');
|
|
88
|
+
// Parse error codes to integers safely, defaulting to 0 if NaN
|
|
89
|
+
const errorCode = parseInt(errorCodeStr);
|
|
90
|
+
const extErrCode = parseInt(extErrCodeStr);
|
|
91
|
+
const offsetCode = parseInt(offsetCodeStr);
|
|
92
|
+
// create an Error object and add the custom properties
|
|
93
|
+
throw new types_1.SQLiteCloudError(errorMessage, {
|
|
94
|
+
errorCode: errorCode.toString(),
|
|
95
|
+
externalErrorCode: extErrCode.toString(),
|
|
96
|
+
offsetCode
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
exports.parseError = parseError;
|
|
100
|
+
/** Parse an array of items (each of which will be parsed by type separately) */
|
|
101
|
+
function parseArray(buffer, spaceIndex) {
|
|
102
|
+
const parsedData = [];
|
|
103
|
+
const array = buffer.subarray(spaceIndex + 1, buffer.length);
|
|
104
|
+
const numberOfItems = parseInt(array.subarray(0, spaceIndex - 2).toString('utf8'));
|
|
105
|
+
let arrayItems = array.subarray(array.indexOf(' ') + 1, array.length);
|
|
106
|
+
for (let i = 0; i < numberOfItems; i++) {
|
|
107
|
+
const { data, fwdBuffer: buffer } = popData(arrayItems);
|
|
108
|
+
parsedData.push(data);
|
|
109
|
+
arrayItems = buffer;
|
|
110
|
+
}
|
|
111
|
+
return parsedData;
|
|
112
|
+
}
|
|
113
|
+
exports.parseArray = parseArray;
|
|
114
|
+
/** Parse header in a rowset or chunk of a chunked rowset */
|
|
115
|
+
function parseRowsetHeader(buffer) {
|
|
116
|
+
const index = parseInt(buffer.subarray(0, buffer.indexOf(':') + 1).toString());
|
|
117
|
+
buffer = buffer.subarray(buffer.indexOf(':') + 1);
|
|
118
|
+
// extract rowset header
|
|
119
|
+
const { data, fwdBuffer } = popIntegers(buffer, 3);
|
|
120
|
+
return {
|
|
121
|
+
index,
|
|
122
|
+
metadata: {
|
|
123
|
+
version: data[0],
|
|
124
|
+
numberOfRows: data[1],
|
|
125
|
+
numberOfColumns: data[2],
|
|
126
|
+
columns: []
|
|
127
|
+
},
|
|
128
|
+
fwdBuffer
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
exports.parseRowsetHeader = parseRowsetHeader;
|
|
132
|
+
/** Extract column names and, optionally, more metadata out of a rowset's header */
|
|
133
|
+
function parseRowsetColumnsMetadata(buffer, metadata) {
|
|
134
|
+
function popForward() {
|
|
135
|
+
const { data, fwdBuffer: fwdBuffer } = popData(buffer); // buffer in parent scope
|
|
136
|
+
buffer = fwdBuffer;
|
|
137
|
+
return data;
|
|
138
|
+
}
|
|
139
|
+
for (let i = 0; i < metadata.numberOfColumns; i++) {
|
|
140
|
+
metadata.columns.push({ name: popForward() });
|
|
141
|
+
}
|
|
142
|
+
// extract additional metadata if rowset has version 2
|
|
143
|
+
if (metadata.version == 2) {
|
|
144
|
+
for (let i = 0; i < metadata.numberOfColumns; i++)
|
|
145
|
+
metadata.columns[i].type = popForward();
|
|
146
|
+
for (let i = 0; i < metadata.numberOfColumns; i++)
|
|
147
|
+
metadata.columns[i].database = popForward();
|
|
148
|
+
for (let i = 0; i < metadata.numberOfColumns; i++)
|
|
149
|
+
metadata.columns[i].table = popForward();
|
|
150
|
+
for (let i = 0; i < metadata.numberOfColumns; i++)
|
|
151
|
+
metadata.columns[i].column = popForward(); // original column name
|
|
152
|
+
for (let i = 0; i < metadata.numberOfColumns; i++)
|
|
153
|
+
metadata.columns[i].notNull = popForward();
|
|
154
|
+
for (let i = 0; i < metadata.numberOfColumns; i++)
|
|
155
|
+
metadata.columns[i].primaryKey = popForward();
|
|
156
|
+
for (let i = 0; i < metadata.numberOfColumns; i++)
|
|
157
|
+
metadata.columns[i].autoIncrement = popForward();
|
|
158
|
+
}
|
|
159
|
+
return buffer;
|
|
160
|
+
}
|
|
161
|
+
/** Parse a regular rowset (no chunks) */
|
|
162
|
+
function parseRowset(buffer, spaceIndex) {
|
|
163
|
+
buffer = buffer.subarray(spaceIndex + 1, buffer.length);
|
|
164
|
+
const { metadata, fwdBuffer } = parseRowsetHeader(buffer);
|
|
165
|
+
buffer = parseRowsetColumnsMetadata(fwdBuffer, metadata);
|
|
166
|
+
// decode each rowset item
|
|
167
|
+
const data = [];
|
|
168
|
+
for (let j = 0; j < metadata.numberOfRows * metadata.numberOfColumns; j++) {
|
|
169
|
+
const { data: rowData, fwdBuffer } = popData(buffer);
|
|
170
|
+
data.push(rowData);
|
|
171
|
+
buffer = fwdBuffer;
|
|
172
|
+
}
|
|
173
|
+
console.assert(data && data.length === metadata.numberOfRows * metadata.numberOfColumns, 'SQLiteCloudConnection.parseRowset - invalid rowset data');
|
|
174
|
+
return new rowset_1.SQLiteCloudRowset(metadata, data);
|
|
175
|
+
}
|
|
176
|
+
function bufferStartsWith(buffer, prefix) {
|
|
177
|
+
return buffer.length >= prefix.length && buffer.subarray(0, prefix.length).toString('utf8') === prefix;
|
|
178
|
+
}
|
|
179
|
+
exports.bufferStartsWith = bufferStartsWith;
|
|
180
|
+
function bufferEndsWith(buffer, suffix) {
|
|
181
|
+
return buffer.length >= suffix.length && buffer.subarray(buffer.length - suffix.length, buffer.length).toString('utf8') === suffix;
|
|
182
|
+
}
|
|
183
|
+
exports.bufferEndsWith = bufferEndsWith;
|
|
184
|
+
/**
|
|
185
|
+
* Parse a chunk of a chunked rowset command, eg:
|
|
186
|
+
* *LEN 0:VERS NROWS NCOLS DATA
|
|
187
|
+
* @see https://github.com/sqlitecloud/sdk/blob/master/PROTOCOL.md#scsp-rowset-chunk
|
|
188
|
+
*/
|
|
189
|
+
function parseRowsetChunks(buffers) {
|
|
190
|
+
let buffer = Buffer.concat(buffers);
|
|
191
|
+
if (!bufferStartsWith(buffer, exports.CMD_ROWSET_CHUNK) || !bufferEndsWith(buffer, exports.ROWSET_CHUNKS_END)) {
|
|
192
|
+
throw new Error('SQLiteCloudConnection.parseRowsetChunks - invalid chunks buffer');
|
|
193
|
+
}
|
|
194
|
+
let metadata = { version: 1, numberOfColumns: 0, numberOfRows: 0, columns: [] };
|
|
195
|
+
const data = [];
|
|
196
|
+
// validate and skip data type
|
|
197
|
+
const dataType = buffer.subarray(0, 1).toString();
|
|
198
|
+
console.assert(dataType === exports.CMD_ROWSET_CHUNK);
|
|
199
|
+
buffer = buffer.subarray(buffer.indexOf(' ') + 1);
|
|
200
|
+
while (buffer.length > 0 && !bufferStartsWith(buffer, exports.ROWSET_CHUNKS_END)) {
|
|
201
|
+
// chunk header, eg: 0:VERS NROWS NCOLS
|
|
202
|
+
const { index: chunkIndex, metadata: chunkMetadata, fwdBuffer } = parseRowsetHeader(buffer);
|
|
203
|
+
buffer = fwdBuffer;
|
|
204
|
+
// first chunk? extract columns metadata
|
|
205
|
+
if (chunkIndex === 1) {
|
|
206
|
+
metadata = chunkMetadata;
|
|
207
|
+
buffer = parseRowsetColumnsMetadata(buffer, metadata);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
metadata.numberOfRows += chunkMetadata.numberOfRows;
|
|
211
|
+
}
|
|
212
|
+
// extract single rowset row
|
|
213
|
+
for (let k = 0; k < chunkMetadata.numberOfRows * metadata.numberOfColumns; k++) {
|
|
214
|
+
const { data: itemData, fwdBuffer } = popData(buffer);
|
|
215
|
+
data.push(itemData);
|
|
216
|
+
buffer = fwdBuffer;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
console.assert(data && data.length === metadata.numberOfRows * metadata.numberOfColumns, 'parseRowsetChunks - invalid rowset data');
|
|
220
|
+
const rowset = new rowset_1.SQLiteCloudRowset(metadata, data);
|
|
221
|
+
// console.debug(`parseRowsetChunks - ${rowset.numberOfRows} rows, ${rowset.numberOfColumns} columns`)
|
|
222
|
+
return rowset;
|
|
223
|
+
}
|
|
224
|
+
exports.parseRowsetChunks = parseRowsetChunks;
|
|
225
|
+
/** Pop one or more space separated integers from beginning of buffer, move buffer forward */
|
|
226
|
+
function popIntegers(buffer, numberOfIntegers = 1) {
|
|
227
|
+
const data = [];
|
|
228
|
+
for (let i = 0; i < numberOfIntegers; i++) {
|
|
229
|
+
const spaceIndex = buffer.indexOf(' ');
|
|
230
|
+
data[i] = parseInt(buffer.subarray(0, spaceIndex).toString());
|
|
231
|
+
buffer = buffer.subarray(spaceIndex + 1);
|
|
232
|
+
}
|
|
233
|
+
return { data, fwdBuffer: buffer };
|
|
234
|
+
}
|
|
235
|
+
/** Parse command, extract its data, return the data and the buffer moved to the first byte after the command */
|
|
236
|
+
function popData(buffer) {
|
|
237
|
+
function popResults(data) {
|
|
238
|
+
const fwdBuffer = buffer.subarray(commandEnd);
|
|
239
|
+
return { data, fwdBuffer };
|
|
240
|
+
}
|
|
241
|
+
// first character is the data type
|
|
242
|
+
console.assert(buffer && buffer instanceof Buffer);
|
|
243
|
+
const dataType = buffer.subarray(0, 1).toString('utf8');
|
|
244
|
+
console.assert(dataType !== exports.CMD_COMPRESSED, "Compressed data shouldn't be decompressed before parsing");
|
|
245
|
+
console.assert(dataType !== exports.CMD_ROWSET_CHUNK, 'Chunked data should be parsed by parseRowsetChunks');
|
|
246
|
+
let spaceIndex = buffer.indexOf(' ');
|
|
247
|
+
if (spaceIndex === -1) {
|
|
248
|
+
spaceIndex = buffer.length - 1;
|
|
249
|
+
}
|
|
250
|
+
let commandEnd = -1;
|
|
251
|
+
if (dataType === exports.CMD_INT || dataType === exports.CMD_FLOAT || dataType === exports.CMD_NULL) {
|
|
252
|
+
commandEnd = spaceIndex + 1;
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
const commandLength = parseInt(buffer.subarray(1, spaceIndex).toString());
|
|
256
|
+
commandEnd = spaceIndex + 1 + commandLength;
|
|
257
|
+
}
|
|
258
|
+
switch (dataType) {
|
|
259
|
+
case exports.CMD_INT:
|
|
260
|
+
return popResults(parseInt(buffer.subarray(1, spaceIndex).toString()));
|
|
261
|
+
case exports.CMD_FLOAT:
|
|
262
|
+
return popResults(parseFloat(buffer.subarray(1, spaceIndex).toString()));
|
|
263
|
+
case exports.CMD_NULL:
|
|
264
|
+
return popResults(null);
|
|
265
|
+
case exports.CMD_STRING:
|
|
266
|
+
return popResults(buffer.subarray(spaceIndex + 1, commandEnd).toString('utf8'));
|
|
267
|
+
case exports.CMD_ZEROSTRING:
|
|
268
|
+
return popResults(buffer.subarray(spaceIndex + 1, commandEnd - 1).toString('utf8'));
|
|
269
|
+
case exports.CMD_COMMAND:
|
|
270
|
+
return popResults(buffer.subarray(spaceIndex + 1, commandEnd).toString('utf8'));
|
|
271
|
+
case exports.CMD_JSON:
|
|
272
|
+
return popResults(JSON.parse(buffer.subarray(spaceIndex + 1, commandEnd).toString('utf8')));
|
|
273
|
+
case exports.CMD_BLOB:
|
|
274
|
+
return popResults(buffer.subarray(spaceIndex + 1, commandEnd));
|
|
275
|
+
case exports.CMD_ARRAY:
|
|
276
|
+
return popResults(parseArray(buffer, spaceIndex));
|
|
277
|
+
case exports.CMD_ROWSET:
|
|
278
|
+
return popResults(parseRowset(buffer, spaceIndex));
|
|
279
|
+
case exports.CMD_ERROR:
|
|
280
|
+
parseError(buffer, spaceIndex); // throws custom error
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
throw new TypeError(`Data type: ${dataType} is not defined in SCSP`);
|
|
284
|
+
}
|
|
285
|
+
exports.popData = popData;
|
|
286
|
+
/** Format a command to be sent via SCSP protocol */
|
|
287
|
+
function formatCommand(command) {
|
|
288
|
+
const commandLength = Buffer.byteLength(command, 'utf-8');
|
|
289
|
+
return `+${commandLength} ${command}`;
|
|
290
|
+
}
|
|
291
|
+
exports.formatCommand = formatCommand;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type OperationCallback = (error: Error | null) => void;
|
|
2
|
+
export type Operation = (done: OperationCallback) => void;
|
|
3
|
+
export declare class OperationsQueue {
|
|
4
|
+
private queue;
|
|
5
|
+
private isProcessing;
|
|
6
|
+
/** Add operations to the queue, process immediately if possible, else wait for previous operations to complete */
|
|
7
|
+
enqueue(operation: Operation): void;
|
|
8
|
+
/** Clear the queue */
|
|
9
|
+
clear(): void;
|
|
10
|
+
/** Process the next operation in the queue */
|
|
11
|
+
private processNext;
|
|
12
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
//
|
|
3
|
+
// queue.ts - simple task queue used to linearize async operations
|
|
4
|
+
//
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.OperationsQueue = void 0;
|
|
7
|
+
class OperationsQueue {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.queue = [];
|
|
10
|
+
this.isProcessing = false;
|
|
11
|
+
}
|
|
12
|
+
/** Add operations to the queue, process immediately if possible, else wait for previous operations to complete */
|
|
13
|
+
enqueue(operation) {
|
|
14
|
+
this.queue.push(operation);
|
|
15
|
+
if (!this.isProcessing) {
|
|
16
|
+
this.processNext();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Clear the queue */
|
|
20
|
+
clear() {
|
|
21
|
+
this.queue = [];
|
|
22
|
+
this.isProcessing = false;
|
|
23
|
+
}
|
|
24
|
+
/** Process the next operation in the queue */
|
|
25
|
+
processNext() {
|
|
26
|
+
if (this.queue.length === 0) {
|
|
27
|
+
this.isProcessing = false;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.isProcessing = true;
|
|
31
|
+
const operation = this.queue.shift();
|
|
32
|
+
operation === null || operation === void 0 ? void 0 : operation(() => {
|
|
33
|
+
// could receive (error) => { ...
|
|
34
|
+
// if (error) {
|
|
35
|
+
// console.warn('OperationQueue.processNext - error in operation', error)
|
|
36
|
+
// }
|
|
37
|
+
// process the next operation in the queue
|
|
38
|
+
this.processNext();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.OperationsQueue = OperationsQueue;
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
/// <reference types="node" />
|
|
5
5
|
/// <reference types="node" />
|
|
6
6
|
import tls from 'tls';
|
|
7
|
+
/** Default timeout value for queries */
|
|
8
|
+
export declare const DEFAULT_TIMEOUT: number;
|
|
9
|
+
/** Default tls connection port */
|
|
10
|
+
export declare const DEFAULT_PORT = 9960;
|
|
7
11
|
/** Configuration for SQLite cloud connection */
|
|
8
12
|
export interface SQLiteCloudConfig {
|
|
9
13
|
/** Connection string in the form of sqlitecloud://user:password@host:port/database?options */
|
|
@@ -18,6 +22,8 @@ export interface SQLiteCloudConfig {
|
|
|
18
22
|
host?: string;
|
|
19
23
|
/** Port number for tls socket */
|
|
20
24
|
port?: number;
|
|
25
|
+
/** Connect using plain TCP port, without TLS encryption, NOT RECOMMENDED, TEST ONLY */
|
|
26
|
+
insecure?: boolean;
|
|
21
27
|
/** Optional query timeout passed directly to TLS socket */
|
|
22
28
|
timeout?: number;
|
|
23
29
|
/** Name of database to open */
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
* types.ts - shared types and interfaces
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.SQLiteCloudArrayType = exports.SQLiteCloudError = void 0;
|
|
6
|
+
exports.SQLiteCloudArrayType = exports.SQLiteCloudError = exports.DEFAULT_PORT = exports.DEFAULT_TIMEOUT = void 0;
|
|
7
|
+
/** Default timeout value for queries */
|
|
8
|
+
exports.DEFAULT_TIMEOUT = 300 * 1000;
|
|
9
|
+
/** Default tls connection port */
|
|
10
|
+
exports.DEFAULT_PORT = 9960;
|
|
7
11
|
/** Custom error reported by SQLiteCloud drivers */
|
|
8
12
|
class SQLiteCloudError extends Error {
|
|
9
13
|
constructor(message, args) {
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { SQLiteCloudConfig, SQLiteCloudDataTypes } from './types';
|
|
2
2
|
export declare const isBrowser: boolean;
|
|
3
3
|
export declare const isNode: boolean;
|
|
4
|
+
/** Messages going to the server are sometimes logged when error conditions occour and need to be stripped of user credentials */
|
|
5
|
+
export declare function anonimizeCommand(message: string): string;
|
|
6
|
+
/** Strip message code in error of user credentials */
|
|
7
|
+
export declare function anonimizeError(error: Error): Error;
|
|
8
|
+
/** Initialization commands sent to database when connection is established */
|
|
9
|
+
export declare function getInitializationCommands(config: SQLiteCloudConfig): string;
|
|
4
10
|
/** Takes a generic value and escapes it so it can replace ? as a binding in a prepared SQL statement */
|
|
5
11
|
export declare function escapeSqlParameter(param: SQLiteCloudDataTypes): string;
|
|
6
12
|
/** Take a sql statement and replaces ? or $named parameters that are properly serialized and escaped. */
|
|
@@ -17,6 +23,8 @@ export declare function popCallback<T extends ErrorCallback = ErrorCallback>(arg
|
|
|
17
23
|
callback?: T | undefined;
|
|
18
24
|
complete?: ErrorCallback;
|
|
19
25
|
};
|
|
26
|
+
/** Validate configuration, apply defaults, throw if something is missing or misconfigured */
|
|
27
|
+
export declare function validateConfiguration(config: SQLiteCloudConfig): SQLiteCloudConfig;
|
|
20
28
|
/** Parse connectionString like sqlitecloud://username:password@host:port/database?option1=xxx&option2=xxx into its components */
|
|
21
29
|
export declare function parseConnectionString(connectionString: string): SQLiteCloudConfig;
|
|
22
30
|
/** Returns true if value is 1 or true */
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// utilities.ts - utility methods to manipulate SQL statements
|
|
4
4
|
//
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.parseBoolean = exports.parseConnectionString = exports.popCallback = exports.prepareSql = exports.escapeSqlParameter = exports.isNode = exports.isBrowser = void 0;
|
|
6
|
+
exports.parseBoolean = exports.parseConnectionString = exports.validateConfiguration = exports.popCallback = exports.prepareSql = exports.escapeSqlParameter = exports.getInitializationCommands = exports.anonimizeError = exports.anonimizeCommand = exports.isNode = exports.isBrowser = void 0;
|
|
7
7
|
const types_1 = require("./types");
|
|
8
8
|
//
|
|
9
9
|
// determining running environment, thanks to browser-or-node
|
|
@@ -14,6 +14,54 @@ exports.isNode = typeof process !== 'undefined' && process.versions != null && p
|
|
|
14
14
|
//
|
|
15
15
|
// utility methods
|
|
16
16
|
//
|
|
17
|
+
/** Messages going to the server are sometimes logged when error conditions occour and need to be stripped of user credentials */
|
|
18
|
+
function anonimizeCommand(message) {
|
|
19
|
+
// hide password in AUTH command if needed
|
|
20
|
+
message = message.replace(/USER \S+/, 'USER ******');
|
|
21
|
+
message = message.replace(/PASSWORD \S+?(?=;)/, 'PASSWORD ******');
|
|
22
|
+
message = message.replace(/HASH \S+?(?=;)/, 'HASH ******');
|
|
23
|
+
return message;
|
|
24
|
+
}
|
|
25
|
+
exports.anonimizeCommand = anonimizeCommand;
|
|
26
|
+
/** Strip message code in error of user credentials */
|
|
27
|
+
function anonimizeError(error) {
|
|
28
|
+
if (error === null || error === void 0 ? void 0 : error.message) {
|
|
29
|
+
error.message = anonimizeCommand(error.message);
|
|
30
|
+
}
|
|
31
|
+
return error;
|
|
32
|
+
}
|
|
33
|
+
exports.anonimizeError = anonimizeError;
|
|
34
|
+
/** Initialization commands sent to database when connection is established */
|
|
35
|
+
function getInitializationCommands(config) {
|
|
36
|
+
// first user authentication, then all other commands
|
|
37
|
+
let commands = `AUTH USER ${config.username || ''} ${config.passwordHashed ? 'HASH' : 'PASSWORD'} ${config.password || ''}; `;
|
|
38
|
+
if (config.database) {
|
|
39
|
+
if (config.createDatabase && !config.dbMemory) {
|
|
40
|
+
commands += `CREATE DATABASE ${config.database} IF NOT EXISTS; `;
|
|
41
|
+
}
|
|
42
|
+
commands += `USE DATABASE ${config.database}; `;
|
|
43
|
+
}
|
|
44
|
+
if (config.compression) {
|
|
45
|
+
commands += 'SET CLIENT KEY COMPRESSION TO 1; ';
|
|
46
|
+
}
|
|
47
|
+
if (config.nonlinearizable) {
|
|
48
|
+
commands += 'SET CLIENT KEY NONLINEARIZABLE TO 1; ';
|
|
49
|
+
}
|
|
50
|
+
if (config.noBlob) {
|
|
51
|
+
commands += 'SET CLIENT KEY NOBLOB TO 1; ';
|
|
52
|
+
}
|
|
53
|
+
if (config.maxData) {
|
|
54
|
+
commands += `SET CLIENT KEY MAXDATA TO ${config.maxData}; `;
|
|
55
|
+
}
|
|
56
|
+
if (config.maxRows) {
|
|
57
|
+
commands += `SET CLIENT KEY MAXROWS TO ${config.maxRows}; `;
|
|
58
|
+
}
|
|
59
|
+
if (config.maxRowset) {
|
|
60
|
+
commands += `SET CLIENT KEY MAXROWSET TO ${config.maxRowset}; `;
|
|
61
|
+
}
|
|
62
|
+
return commands;
|
|
63
|
+
}
|
|
64
|
+
exports.getInitializationCommands = getInitializationCommands;
|
|
17
65
|
/** Takes a generic value and escapes it so it can replace ? as a binding in a prepared SQL statement */
|
|
18
66
|
function escapeSqlParameter(param) {
|
|
19
67
|
if (param === null || param === undefined) {
|
|
@@ -100,6 +148,38 @@ function popCallback(args) {
|
|
|
100
148
|
return { args: remaining };
|
|
101
149
|
}
|
|
102
150
|
exports.popCallback = popCallback;
|
|
151
|
+
//
|
|
152
|
+
// configuration validation
|
|
153
|
+
//
|
|
154
|
+
/** Validate configuration, apply defaults, throw if something is missing or misconfigured */
|
|
155
|
+
function validateConfiguration(config) {
|
|
156
|
+
console.assert(config, 'SQLiteCloudConnection.validateConfiguration - missing config');
|
|
157
|
+
if (config.connectionString) {
|
|
158
|
+
config = Object.assign(Object.assign(Object.assign({}, config), parseConnectionString(config.connectionString)), { connectionString: config.connectionString // keep original connection string
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
// apply defaults where needed
|
|
162
|
+
config.port || (config.port = types_1.DEFAULT_PORT);
|
|
163
|
+
config.timeout = config.timeout && config.timeout > 0 ? config.timeout : types_1.DEFAULT_TIMEOUT;
|
|
164
|
+
config.clientId || (config.clientId = 'SQLiteCloud');
|
|
165
|
+
config.verbose = parseBoolean(config.verbose);
|
|
166
|
+
config.noBlob = parseBoolean(config.noBlob);
|
|
167
|
+
config.compression = parseBoolean(config.compression);
|
|
168
|
+
config.createDatabase = parseBoolean(config.createDatabase);
|
|
169
|
+
config.nonlinearizable = parseBoolean(config.nonlinearizable);
|
|
170
|
+
config.insecure = parseBoolean(config.insecure);
|
|
171
|
+
if (!config.username || !config.password || !config.host) {
|
|
172
|
+
console.error('SQLiteCloudConnection.validateConfiguration - missing arguments', config);
|
|
173
|
+
throw new types_1.SQLiteCloudError('The user, password and host arguments must be specified.', { errorCode: 'ERR_MISSING_ARGS' });
|
|
174
|
+
}
|
|
175
|
+
if (!config.connectionString) {
|
|
176
|
+
// build connection string from configuration, values are already validated
|
|
177
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
178
|
+
config.connectionString = `sqlitecloud://${config.username}:${config.password}@${config.host}:${config.port}/${config.database}`;
|
|
179
|
+
}
|
|
180
|
+
return config;
|
|
181
|
+
}
|
|
182
|
+
exports.validateConfiguration = validateConfiguration;
|
|
103
183
|
/** Parse connectionString like sqlitecloud://username:password@host:port/database?option1=xxx&option2=xxx into its components */
|
|
104
184
|
function parseConnectionString(connectionString) {
|
|
105
185
|
try {
|
package/lib/index.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
export { Database } from './database';
|
|
2
|
-
export { Statement } from './statement';
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export { escapeSqlParameter, prepareSql, parseConnectionString } from './utilities';
|
|
7
|
-
export { WebSocketTransport } from './transport-ws';
|
|
8
|
-
export { TlsSocketTransport } from './transport-tls';
|
|
1
|
+
export { Database } from './drivers/database';
|
|
2
|
+
export { Statement } from './drivers/statement';
|
|
3
|
+
export { SQLiteCloudConnection } from './drivers/connection';
|
|
4
|
+
export { type SQLiteCloudConfig, type SQLCloudRowsetMetadata, SQLiteCloudError, type ErrorCallback } from './drivers/types';
|
|
5
|
+
export { SQLiteCloudRowset, SQLiteCloudRow } from './drivers/rowset';
|
|
6
|
+
export { escapeSqlParameter, prepareSql, parseConnectionString, validateConfiguration } from './drivers/utilities';
|
package/lib/index.js
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
//
|
|
3
|
-
// index.ts -
|
|
3
|
+
// index.ts - export drivers classes, utilities, types
|
|
4
4
|
//
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
7
|
-
|
|
6
|
+
exports.validateConfiguration = exports.parseConnectionString = exports.prepareSql = exports.escapeSqlParameter = exports.SQLiteCloudRow = exports.SQLiteCloudRowset = exports.SQLiteCloudError = exports.SQLiteCloudConnection = exports.Statement = exports.Database = void 0;
|
|
7
|
+
// include ONLY packages used by drivers
|
|
8
|
+
// do NOT include anything related to gateway or bun or express
|
|
9
|
+
// connection-tls does not want/need to load on browser and is loaded dynamically by Database
|
|
10
|
+
// connection-ws does not want/need to load on node and is loaded dynamically by Database
|
|
11
|
+
var database_1 = require("./drivers/database");
|
|
8
12
|
Object.defineProperty(exports, "Database", { enumerable: true, get: function () { return database_1.Database; } });
|
|
9
|
-
var statement_1 = require("./statement");
|
|
13
|
+
var statement_1 = require("./drivers/statement");
|
|
10
14
|
Object.defineProperty(exports, "Statement", { enumerable: true, get: function () { return statement_1.Statement; } });
|
|
11
|
-
var
|
|
15
|
+
var connection_1 = require("./drivers/connection");
|
|
16
|
+
Object.defineProperty(exports, "SQLiteCloudConnection", { enumerable: true, get: function () { return connection_1.SQLiteCloudConnection; } });
|
|
17
|
+
var types_1 = require("./drivers/types");
|
|
12
18
|
Object.defineProperty(exports, "SQLiteCloudError", { enumerable: true, get: function () { return types_1.SQLiteCloudError; } });
|
|
13
|
-
var rowset_1 = require("./rowset");
|
|
19
|
+
var rowset_1 = require("./drivers/rowset");
|
|
14
20
|
Object.defineProperty(exports, "SQLiteCloudRowset", { enumerable: true, get: function () { return rowset_1.SQLiteCloudRowset; } });
|
|
15
21
|
Object.defineProperty(exports, "SQLiteCloudRow", { enumerable: true, get: function () { return rowset_1.SQLiteCloudRow; } });
|
|
16
|
-
var
|
|
17
|
-
Object.defineProperty(exports, "SQLiteCloudConnection", { enumerable: true, get: function () { return connection_1.SQLiteCloudConnection; } });
|
|
18
|
-
var utilities_1 = require("./utilities");
|
|
22
|
+
var utilities_1 = require("./drivers/utilities");
|
|
19
23
|
Object.defineProperty(exports, "escapeSqlParameter", { enumerable: true, get: function () { return utilities_1.escapeSqlParameter; } });
|
|
20
24
|
Object.defineProperty(exports, "prepareSql", { enumerable: true, get: function () { return utilities_1.prepareSql; } });
|
|
21
25
|
Object.defineProperty(exports, "parseConnectionString", { enumerable: true, get: function () { return utilities_1.parseConnectionString; } });
|
|
22
|
-
|
|
23
|
-
Object.defineProperty(exports, "WebSocketTransport", { enumerable: true, get: function () { return transport_ws_1.WebSocketTransport; } });
|
|
24
|
-
var transport_tls_1 = require("./transport-tls");
|
|
25
|
-
Object.defineProperty(exports, "TlsSocketTransport", { enumerable: true, get: function () { return transport_tls_1.TlsSocketTransport; } });
|
|
26
|
+
Object.defineProperty(exports, "validateConfiguration", { enumerable: true, get: function () { return utilities_1.validateConfiguration; } });
|