@sqlitecloud/drivers 0.0.40 → 0.0.56
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/README.md +1 -1
- package/lib/drivers/connection-tls.d.ts +29 -0
- package/lib/drivers/connection-tls.js +220 -0
- package/lib/{transport-ws.d.ts → drivers/connection-ws.d.ts} +5 -6
- package/lib/{transport-ws.js → drivers/connection-ws.js} +11 -8
- package/lib/drivers/connection.d.ts +34 -0
- package/lib/drivers/connection.js +81 -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} +4 -2
- package/lib/{utilities.d.ts → drivers/utilities.d.ts} +6 -0
- package/lib/{utilities.js → drivers/utilities.js} +53 -3
- package/lib/index.d.ts +6 -8
- package/lib/index.js +13 -13
- package/lib/sqlitecloud.drivers.dev.js +619 -0
- package/lib/sqlitecloud.drivers.js +1 -0
- package/package.json +12 -3
- package/lib/connection.d.ts +0 -59
- package/lib/connection.js +0 -230
- package/lib/sqlitecloud.v0.0.40.dev.js +0 -597
- package/lib/sqlitecloud.v0.0.40.js +0 -1
- package/lib/transport-tls.d.ts +0 -25
- 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
- /package/lib/{types.js → drivers/types.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;
|
|
@@ -18,10 +18,12 @@ export interface SQLiteCloudConfig {
|
|
|
18
18
|
password?: string;
|
|
19
19
|
/** True if password is hashed, default is false */
|
|
20
20
|
passwordHashed?: boolean;
|
|
21
|
-
/** Host name is required unless connectionString is provided */
|
|
21
|
+
/** Host name is required unless connectionString is provided, eg: xxx.sqlitecloud.io */
|
|
22
22
|
host?: string;
|
|
23
23
|
/** Port number for tls socket */
|
|
24
24
|
port?: number;
|
|
25
|
+
/** Connect using plain TCP port, without TLS encryption, NOT RECOMMENDED, TEST ONLY */
|
|
26
|
+
insecure?: boolean;
|
|
25
27
|
/** Optional query timeout passed directly to TLS socket */
|
|
26
28
|
timeout?: number;
|
|
27
29
|
/** Name of database to open */
|
|
@@ -45,7 +47,7 @@ export interface SQLiteCloudConfig {
|
|
|
45
47
|
tlsOptions?: tls.ConnectionOptions;
|
|
46
48
|
/** True if we should force use of SQLite Cloud Gateway and websocket connections, default: true in browsers, false in node.js */
|
|
47
49
|
useWebsocket?: boolean;
|
|
48
|
-
/** Url where we can connect to a SQLite Cloud Gateway that has a socket.io deamon waiting to connect, eg.
|
|
50
|
+
/** Url where we can connect to a SQLite Cloud Gateway that has a socket.io deamon waiting to connect, eg. wss://host:4000 */
|
|
49
51
|
gatewayUrl?: string;
|
|
50
52
|
/** Optional identifier used for verbose logging */
|
|
51
53
|
clientId?: string;
|
|
@@ -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. */
|
|
@@ -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.validateConfiguration = 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) {
|
|
@@ -105,6 +153,7 @@ exports.popCallback = popCallback;
|
|
|
105
153
|
//
|
|
106
154
|
/** Validate configuration, apply defaults, throw if something is missing or misconfigured */
|
|
107
155
|
function validateConfiguration(config) {
|
|
156
|
+
console.assert(config, 'SQLiteCloudConnection.validateConfiguration - missing config');
|
|
108
157
|
if (config.connectionString) {
|
|
109
158
|
config = Object.assign(Object.assign(Object.assign({}, config), parseConnectionString(config.connectionString)), { connectionString: config.connectionString // keep original connection string
|
|
110
159
|
});
|
|
@@ -118,6 +167,7 @@ function validateConfiguration(config) {
|
|
|
118
167
|
config.compression = parseBoolean(config.compression);
|
|
119
168
|
config.createDatabase = parseBoolean(config.createDatabase);
|
|
120
169
|
config.nonlinearizable = parseBoolean(config.nonlinearizable);
|
|
170
|
+
config.insecure = parseBoolean(config.insecure);
|
|
121
171
|
if (!config.username || !config.password || !config.host) {
|
|
122
172
|
console.error('SQLiteCloudConnection.validateConfiguration - missing arguments', config);
|
|
123
173
|
throw new types_1.SQLiteCloudError('The user, password and host arguments must be specified.', { errorCode: 'ERR_MISSING_ARGS' });
|
|
@@ -125,7 +175,7 @@ function validateConfiguration(config) {
|
|
|
125
175
|
if (!config.connectionString) {
|
|
126
176
|
// build connection string from configuration, values are already validated
|
|
127
177
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
128
|
-
config.connectionString = `sqlitecloud://${config.username}:${config.password}@${config.host}:${config.port}/${config.database}`;
|
|
178
|
+
config.connectionString = `sqlitecloud://${encodeURIComponent(config.username)}:${encodeURIComponent(config.password)}@${config.host}:${config.port}/${config.database}`;
|
|
129
179
|
}
|
|
130
180
|
return config;
|
|
131
181
|
}
|
|
@@ -144,7 +194,7 @@ function parseConnectionString(connectionString) {
|
|
|
144
194
|
url.searchParams.forEach((value, key) => {
|
|
145
195
|
options[key] = value;
|
|
146
196
|
});
|
|
147
|
-
const config = Object.assign({ username: url.username, password: url.password, host: url.hostname, port: url.port ? parseInt(url.port) : undefined }, options);
|
|
197
|
+
const config = Object.assign({ username: decodeURIComponent(url.username), password: decodeURIComponent(url.password), host: url.hostname, port: url.port ? parseInt(url.port) : undefined }, options);
|
|
148
198
|
const database = url.pathname.replace('/', ''); // pathname is database name, remove the leading slash
|
|
149
199
|
if (database) {
|
|
150
200
|
config.database = database;
|
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, validateConfiguration } 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 ResultsCallback, 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,26 +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
26
|
Object.defineProperty(exports, "validateConfiguration", { enumerable: true, get: function () { return utilities_1.validateConfiguration; } });
|
|
23
|
-
var transport_ws_1 = require("./transport-ws");
|
|
24
|
-
Object.defineProperty(exports, "WebSocketTransport", { enumerable: true, get: function () { return transport_ws_1.WebSocketTransport; } });
|
|
25
|
-
var transport_tls_1 = require("./transport-tls");
|
|
26
|
-
Object.defineProperty(exports, "TlsSocketTransport", { enumerable: true, get: function () { return transport_tls_1.TlsSocketTransport; } });
|