@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.
Files changed (32) hide show
  1. package/README.md +1 -1
  2. package/lib/drivers/connection-tls.d.ts +29 -0
  3. package/lib/drivers/connection-tls.js +220 -0
  4. package/lib/{transport-ws.d.ts → drivers/connection-ws.d.ts} +5 -6
  5. package/lib/{transport-ws.js → drivers/connection-ws.js} +11 -8
  6. package/lib/drivers/connection.d.ts +34 -0
  7. package/lib/drivers/connection.js +81 -0
  8. package/lib/{database.js → drivers/database.js} +68 -19
  9. package/lib/drivers/protocol.d.ts +51 -0
  10. package/lib/drivers/protocol.js +291 -0
  11. package/lib/drivers/queue.d.ts +12 -0
  12. package/lib/drivers/queue.js +42 -0
  13. package/lib/{types.d.ts → drivers/types.d.ts} +4 -2
  14. package/lib/{utilities.d.ts → drivers/utilities.d.ts} +6 -0
  15. package/lib/{utilities.js → drivers/utilities.js} +53 -3
  16. package/lib/index.d.ts +6 -8
  17. package/lib/index.js +13 -13
  18. package/lib/sqlitecloud.drivers.dev.js +619 -0
  19. package/lib/sqlitecloud.drivers.js +1 -0
  20. package/package.json +12 -3
  21. package/lib/connection.d.ts +0 -59
  22. package/lib/connection.js +0 -230
  23. package/lib/sqlitecloud.v0.0.40.dev.js +0 -597
  24. package/lib/sqlitecloud.v0.0.40.js +0 -1
  25. package/lib/transport-tls.d.ts +0 -25
  26. package/lib/transport-tls.js +0 -465
  27. /package/lib/{database.d.ts → drivers/database.d.ts} +0 -0
  28. /package/lib/{rowset.d.ts → drivers/rowset.d.ts} +0 -0
  29. /package/lib/{rowset.js → drivers/rowset.js} +0 -0
  30. /package/lib/{statement.d.ts → drivers/statement.d.ts} +0 -0
  31. /package/lib/{statement.js → drivers/statement.js} +0 -0
  32. /package/lib/{types.js → drivers/types.js} +0 -0
@@ -1,465 +0,0 @@
1
- "use strict";
2
- /**
3
- * transport-tls.ts - handles low level communication with sqlitecloud server via tls socket and binary protocol
4
- */
5
- var __importDefault = (this && this.__importDefault) || function (mod) {
6
- return (mod && mod.__esModule) ? mod : { "default": mod };
7
- };
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.TlsSocketTransport = void 0;
10
- const types_1 = require("./types");
11
- const rowset_1 = require("./rowset");
12
- const connection_1 = require("./connection");
13
- const connection_2 = require("./connection");
14
- const tls_1 = __importDefault(require("tls"));
15
- const lz4 = require('lz4js');
16
- /**
17
- * The server communicates with clients via commands defined
18
- * in the SQLiteCloud Server Protocol (SCSP), see more at:
19
- * https://github.com/sqlitecloud/sdk/blob/master/PROTOCOL.md
20
- */
21
- const CMD_STRING = '+';
22
- const CMD_ZEROSTRING = '!';
23
- const CMD_ERROR = '-';
24
- const CMD_INT = ':';
25
- const CMD_FLOAT = ',';
26
- const CMD_ROWSET = '*';
27
- const CMD_ROWSET_CHUNK = '/';
28
- const CMD_JSON = '#';
29
- const CMD_NULL = '_';
30
- const CMD_BLOB = '$';
31
- const CMD_COMPRESSED = '%';
32
- const CMD_COMMAND = '^';
33
- const CMD_ARRAY = '=';
34
- // const CMD_RAWJSON = '{'
35
- // const CMD_PUBSUB = '|'
36
- // const CMD_RECONNECT = '@'
37
- /**
38
- * Implementation of SQLiteCloudConnection that connects directly to the database via tls socket and raw, binary protocol.
39
- * SQLiteCloud low-level connection, will do messaging, handle socket, authentication, etc.
40
- * A connection socket is established when the connection is created and closed when the connection is closed.
41
- * All operations are serialized by waiting for any pending operations to complete. Once a connection is closed,
42
- * it cannot be reopened and you must create a new connection.
43
- */
44
- class TlsSocketTransport {
45
- /** True if connection is open */
46
- get connected() {
47
- return !!this.socket;
48
- }
49
- /* Opens a connection with the server and sends the initialization commands. Will throw in case of errors. */
50
- connect(config, callback) {
51
- // connection established while we were waiting in line?
52
- console.assert(!this.connected, 'Connection already established');
53
- // clear all listeners and call done in the operations queue
54
- const finish = error => {
55
- if (this.socket) {
56
- this.socket.removeAllListeners('data');
57
- this.socket.removeAllListeners('error');
58
- this.socket.removeAllListeners('close');
59
- if (error) {
60
- this.close();
61
- }
62
- }
63
- };
64
- this.config = config;
65
- // connect to tls socket, initialize connection, setup event handlers
66
- this.socket = tls_1.default.connect(this.config.port, this.config.host, this.config.tlsOptions, () => {
67
- var _a;
68
- if (!((_a = this.socket) === null || _a === void 0 ? void 0 : _a.authorized)) {
69
- const anonimizedError = (0, connection_2.anonimizeError)(this.socket.authorizationError);
70
- console.error('Connection was not authorized', anonimizedError);
71
- this.close();
72
- finish(new types_1.SQLiteCloudError('Connection was not authorized', { cause: anonimizedError }));
73
- }
74
- else {
75
- // the connection was closed before it was even opened,
76
- // eg. client closed the connection before the server accepted it
77
- if (this.socket === null) {
78
- finish(new types_1.SQLiteCloudError('Connection was closed before it was done opening'));
79
- return;
80
- }
81
- // send initialization commands
82
- console.assert(this.socket, 'Connection already closed');
83
- const commands = (0, connection_1.getInitializationCommands)(config);
84
- this.processCommands(commands, error => {
85
- if (error && this.socket) {
86
- this.close();
87
- }
88
- if (callback) {
89
- callback === null || callback === void 0 ? void 0 : callback.call(this, error);
90
- callback = undefined;
91
- }
92
- finish(error);
93
- });
94
- }
95
- });
96
- this.socket.on('close', () => {
97
- this.socket = null;
98
- finish(new types_1.SQLiteCloudError('Connection was closed'));
99
- });
100
- this.socket.once('error', (error) => {
101
- console.error('Connection error', error);
102
- finish(new types_1.SQLiteCloudError('Connection error', { cause: error }));
103
- });
104
- return this;
105
- }
106
- /** Will send a command immediately (no queueing), return the rowset/result or throw an error */
107
- processCommands(commands, callback) {
108
- var _a, _b, _c;
109
- // connection needs to be established?
110
- if (!this.socket) {
111
- callback === null || callback === void 0 ? void 0 : callback.call(this, new types_1.SQLiteCloudError('Connection not established', { errorCode: 'ERR_CONNECTION_NOT_ESTABLISHED' }));
112
- return this;
113
- }
114
- // compose commands following SCPC protocol
115
- commands = formatCommand(commands);
116
- let buffer = Buffer.alloc(0);
117
- const rowsetChunks = [];
118
- // const startedOn = new Date()
119
- // define what to do if an answer does not arrive within the set timeout
120
- let socketTimeout;
121
- // clear all listeners and call done in the operations queue
122
- const finish = (error, result) => {
123
- clearTimeout(socketTimeout);
124
- if (this.socket) {
125
- this.socket.removeAllListeners('data');
126
- this.socket.removeAllListeners('error');
127
- this.socket.removeAllListeners('close');
128
- }
129
- if (callback) {
130
- callback === null || callback === void 0 ? void 0 : callback.call(this, error, result);
131
- callback = undefined;
132
- }
133
- };
134
- // define the Promise that waits for the server response
135
- const readData = (data) => {
136
- var _a, _b;
137
- try {
138
- // on first ondata event, dataType is read from data, on subsequent ondata event, is read from buffer that is the concatanations of data received on each ondata event
139
- let dataType = buffer.length === 0 ? data.subarray(0, 1).toString() : buffer.subarray(0, 1).toString('utf8');
140
- buffer = Buffer.concat([buffer, data]);
141
- const commandLength = hasCommandLength(dataType);
142
- if (commandLength) {
143
- const commandLength = parseCommandLength(buffer);
144
- const hasReceivedEntireCommand = buffer.length - buffer.indexOf(' ') - 1 >= commandLength ? true : false;
145
- if (hasReceivedEntireCommand) {
146
- if ((_a = this.config) === null || _a === void 0 ? void 0 : _a.verbose) {
147
- let bufferString = buffer.toString('utf8');
148
- if (bufferString.length > 1000) {
149
- bufferString = bufferString.substring(0, 100) + '...' + bufferString.substring(bufferString.length - 40);
150
- }
151
- // const elapsedMs = new Date().getTime() - startedOn.getTime()
152
- // console.debug(`Receive: ${bufferString} - ${elapsedMs}ms`)
153
- }
154
- // need to decompress this buffer before decoding?
155
- if (dataType === CMD_COMPRESSED) {
156
- ;
157
- ({ buffer, dataType } = decompressBuffer(buffer));
158
- }
159
- if (dataType !== CMD_ROWSET_CHUNK) {
160
- (_b = this.socket) === null || _b === void 0 ? void 0 : _b.off('data', readData);
161
- const { data } = popData(buffer);
162
- finish(null, data);
163
- }
164
- else {
165
- // @ts-expect-error
166
- // check if rowset received the ending chunk
167
- if (data.subarray(data.indexOf(' ') + 1, data.length).toString() === '0 0 0 ') {
168
- const parsedData = parseRowsetChunks(rowsetChunks);
169
- finish === null || finish === void 0 ? void 0 : finish.call(this, null, parsedData);
170
- }
171
- else {
172
- // no ending string? ask server for another chunk
173
- rowsetChunks.push(buffer);
174
- buffer = Buffer.alloc(0);
175
- // no longer need to ack the server
176
- // const okCommand = formatCommand('OK')
177
- // this.socket?.write(okCommand)
178
- }
179
- }
180
- }
181
- }
182
- else {
183
- // command with no explicit len so make sure that the final character is a space
184
- const lastChar = buffer.subarray(buffer.length - 1, buffer.length).toString('utf8');
185
- if (lastChar == ' ') {
186
- const { data } = popData(buffer);
187
- finish(null, data);
188
- }
189
- }
190
- }
191
- catch (error) {
192
- console.assert(error instanceof Error);
193
- if (error instanceof Error) {
194
- finish(error);
195
- }
196
- }
197
- };
198
- (_a = this.socket) === null || _a === void 0 ? void 0 : _a.once('close', () => {
199
- finish(new types_1.SQLiteCloudError('Connection was closed', { cause: (0, connection_2.anonimizeCommand)(commands) }));
200
- });
201
- (_b = this.socket) === null || _b === void 0 ? void 0 : _b.write(commands, 'utf8', () => {
202
- var _a, _b;
203
- socketTimeout = setTimeout(() => {
204
- const timeoutError = new types_1.SQLiteCloudError('Request timed out', { cause: (0, connection_2.anonimizeCommand)(commands) });
205
- // console.debug(`Request timed out, config.timeout is ${this.config?.timeout as number}ms`, timeoutError)
206
- finish(timeoutError);
207
- }, (_a = this.config) === null || _a === void 0 ? void 0 : _a.timeout);
208
- (_b = this.socket) === null || _b === void 0 ? void 0 : _b.on('data', readData);
209
- });
210
- (_c = this.socket) === null || _c === void 0 ? void 0 : _c.once('error', (error) => {
211
- console.error('Socket error', error);
212
- this.close();
213
- finish(new types_1.SQLiteCloudError('Socket error', { cause: (0, connection_2.anonimizeError)(error) }));
214
- });
215
- return this;
216
- }
217
- /** Disconnect from server, release connection. */
218
- close() {
219
- console.assert(this.socket !== null, 'TlsSocketTransport.close - connection already closed');
220
- if (this.socket) {
221
- this.socket.destroy();
222
- this.socket = null;
223
- }
224
- this.socket = undefined;
225
- return this;
226
- }
227
- }
228
- exports.TlsSocketTransport = TlsSocketTransport;
229
- //
230
- // utility functions
231
- //
232
- /** Analyze first character to check if corresponding data type has LEN */
233
- function hasCommandLength(firstCharacter) {
234
- return firstCharacter == CMD_INT || firstCharacter == CMD_FLOAT || firstCharacter == CMD_NULL ? false : true;
235
- }
236
- /** Analyze a command with explict LEN and extract it */
237
- function parseCommandLength(data) {
238
- return parseInt(data.subarray(1, data.indexOf(' ')).toString('utf8'));
239
- }
240
- /** Receive a compressed buffer, decompress with lz4, return buffer and datatype */
241
- function decompressBuffer(buffer) {
242
- const spaceIndex = buffer.indexOf(' ');
243
- buffer = buffer.subarray(spaceIndex + 1);
244
- // extract compressed size
245
- const compressedSize = parseInt(buffer.subarray(0, buffer.indexOf(' ') + 1).toString('utf8'));
246
- buffer = buffer.subarray(buffer.indexOf(' ') + 1);
247
- // extract decompressed size
248
- const decompressedSize = parseInt(buffer.subarray(0, buffer.indexOf(' ') + 1).toString('utf8'));
249
- buffer = buffer.subarray(buffer.indexOf(' ') + 1);
250
- // extract compressed dataType
251
- const dataType = buffer.subarray(0, 1).toString('utf8');
252
- const decompressedBuffer = Buffer.alloc(decompressedSize);
253
- const compressedBuffer = buffer.subarray(buffer.length - compressedSize);
254
- // lz4js library is javascript and doesn't have types so we silence the type check
255
- // eslint-disable-next-line
256
- const decompressionResult = lz4.decompressBlock(compressedBuffer, decompressedBuffer, 0, compressedSize, 0);
257
- buffer = Buffer.concat([buffer.subarray(0, buffer.length - compressedSize), decompressedBuffer]);
258
- if (decompressionResult <= 0 || decompressionResult !== decompressedSize) {
259
- throw new Error(`lz4 decompression error at offset ${decompressionResult}`);
260
- }
261
- return { buffer, dataType: dataType };
262
- }
263
- /** Parse error message or extended error message */
264
- function parseError(buffer, spaceIndex) {
265
- const errorBuffer = buffer.subarray(spaceIndex + 1);
266
- const errorString = errorBuffer.toString('utf8');
267
- const parts = errorString.split(' ');
268
- let errorCodeStr = parts.shift() || '0'; // Default errorCode is '0' if not present
269
- let extErrCodeStr = '0'; // Default extended error code
270
- let offsetCodeStr = '-1'; // Default offset code
271
- // Split the errorCode by ':' to check for extended error codes
272
- const errorCodeParts = errorCodeStr.split(':');
273
- errorCodeStr = errorCodeParts[0];
274
- if (errorCodeParts.length > 1) {
275
- extErrCodeStr = errorCodeParts[1];
276
- if (errorCodeParts.length > 2) {
277
- offsetCodeStr = errorCodeParts[2];
278
- }
279
- }
280
- // Rest of the error string is the error message
281
- const errorMessage = parts.join(' ');
282
- // Parse error codes to integers safely, defaulting to 0 if NaN
283
- const errorCode = parseInt(errorCodeStr);
284
- const extErrCode = parseInt(extErrCodeStr);
285
- const offsetCode = parseInt(offsetCodeStr);
286
- // create an Error object and add the custom properties
287
- throw new types_1.SQLiteCloudError(errorMessage, {
288
- errorCode: errorCode.toString(),
289
- externalErrorCode: extErrCode.toString(),
290
- offsetCode
291
- });
292
- }
293
- /** Parse an array of items (each of which will be parsed by type separately) */
294
- function parseArray(buffer, spaceIndex) {
295
- const parsedData = [];
296
- const array = buffer.subarray(spaceIndex + 1, buffer.length);
297
- const numberOfItems = parseInt(array.subarray(0, spaceIndex - 2).toString('utf8'));
298
- let arrayItems = array.subarray(array.indexOf(' ') + 1, array.length);
299
- for (let i = 0; i < numberOfItems; i++) {
300
- const { data, fwdBuffer: buffer } = popData(arrayItems);
301
- parsedData.push(data);
302
- arrayItems = buffer;
303
- }
304
- return parsedData;
305
- }
306
- /** Parse header in a rowset or chunk of a chunked rowset */
307
- function parseRowsetHeader(buffer) {
308
- const index = parseInt(buffer.subarray(0, buffer.indexOf(':') + 1).toString());
309
- buffer = buffer.subarray(buffer.indexOf(':') + 1);
310
- // extract rowset header
311
- const { data, fwdBuffer } = popIntegers(buffer, 3);
312
- return {
313
- index,
314
- metadata: {
315
- version: data[0],
316
- numberOfRows: data[1],
317
- numberOfColumns: data[2],
318
- columns: []
319
- },
320
- fwdBuffer
321
- };
322
- }
323
- /** Extract column names and, optionally, more metadata out of a rowset's header */
324
- function parseRowsetColumnsMetadata(buffer, metadata) {
325
- function popForward() {
326
- const { data, fwdBuffer: fwdBuffer } = popData(buffer); // buffer in parent scope
327
- buffer = fwdBuffer;
328
- return data;
329
- }
330
- for (let i = 0; i < metadata.numberOfColumns; i++) {
331
- metadata.columns.push({ name: popForward() });
332
- }
333
- // extract additional metadata if rowset has version 2
334
- if (metadata.version == 2) {
335
- for (let i = 0; i < metadata.numberOfColumns; i++)
336
- metadata.columns[i].type = popForward();
337
- for (let i = 0; i < metadata.numberOfColumns; i++)
338
- metadata.columns[i].database = popForward();
339
- for (let i = 0; i < metadata.numberOfColumns; i++)
340
- metadata.columns[i].table = popForward();
341
- for (let i = 0; i < metadata.numberOfColumns; i++)
342
- metadata.columns[i].column = popForward(); // original column name
343
- for (let i = 0; i < metadata.numberOfColumns; i++)
344
- metadata.columns[i].notNull = popForward();
345
- for (let i = 0; i < metadata.numberOfColumns; i++)
346
- metadata.columns[i].primaryKey = popForward();
347
- for (let i = 0; i < metadata.numberOfColumns; i++)
348
- metadata.columns[i].autoIncrement = popForward();
349
- }
350
- return buffer;
351
- }
352
- /** Parse a regular rowset (no chunks) */
353
- function parseRowset(buffer, spaceIndex) {
354
- buffer = buffer.subarray(spaceIndex + 1, buffer.length);
355
- const { metadata, fwdBuffer } = parseRowsetHeader(buffer);
356
- buffer = parseRowsetColumnsMetadata(fwdBuffer, metadata);
357
- // decode each rowset item
358
- const data = [];
359
- for (let j = 0; j < metadata.numberOfRows * metadata.numberOfColumns; j++) {
360
- const { data: rowData, fwdBuffer } = popData(buffer);
361
- data.push(rowData);
362
- buffer = fwdBuffer;
363
- }
364
- console.assert(data && data.length === metadata.numberOfRows * metadata.numberOfColumns, 'SQLiteCloudConnection.parseRowset - invalid rowset data');
365
- return new rowset_1.SQLiteCloudRowset(metadata, data);
366
- }
367
- /**
368
- * Parse a chunk of a chunked rowset command, eg:
369
- * *LEN 0:VERS NROWS NCOLS DATA
370
- */
371
- function parseRowsetChunks(buffers) {
372
- let metadata = { version: 1, numberOfColumns: 0, numberOfRows: 0, columns: [] };
373
- const data = [];
374
- for (let i = 0; i < buffers.length; i++) {
375
- let buffer = buffers[i];
376
- // validate and skip data type
377
- const dataType = buffer.subarray(0, 1).toString();
378
- console.assert(dataType === CMD_ROWSET_CHUNK);
379
- buffer = buffer.subarray(buffer.indexOf(' ') + 1);
380
- // chunk header, eg: 0:VERS NROWS NCOLS
381
- const { index: chunkIndex, metadata: chunkMetadata, fwdBuffer } = parseRowsetHeader(buffer);
382
- buffer = fwdBuffer;
383
- // first chunk? extract columns metadata
384
- if (chunkIndex === 1) {
385
- metadata = chunkMetadata;
386
- buffer = parseRowsetColumnsMetadata(buffer, metadata);
387
- }
388
- else {
389
- metadata.numberOfRows += chunkMetadata.numberOfRows;
390
- }
391
- // extract single rowset row
392
- for (let k = 0; k < chunkMetadata.numberOfRows * metadata.numberOfColumns; k++) {
393
- const { data: itemData, fwdBuffer } = popData(buffer);
394
- data.push(itemData);
395
- buffer = fwdBuffer;
396
- }
397
- }
398
- console.assert(data && data.length === metadata.numberOfRows * metadata.numberOfColumns, 'SQLiteCloudConnection.parseRowsetChunks - invalid rowset data');
399
- return new rowset_1.SQLiteCloudRowset(metadata, data);
400
- }
401
- /** Pop one or more space separated integers from beginning of buffer, move buffer forward */
402
- function popIntegers(buffer, numberOfIntegers = 1) {
403
- const data = [];
404
- for (let i = 0; i < numberOfIntegers; i++) {
405
- const spaceIndex = buffer.indexOf(' ');
406
- data[i] = parseInt(buffer.subarray(0, spaceIndex).toString());
407
- buffer = buffer.subarray(spaceIndex + 1);
408
- }
409
- return { data, fwdBuffer: buffer };
410
- }
411
- /** Parse command, extract its data, return the data and the buffer moved to the first byte after the command */
412
- function popData(buffer) {
413
- function popResults(data) {
414
- const fwdBuffer = buffer.subarray(commandEnd);
415
- return { data, fwdBuffer };
416
- }
417
- // first character is the data type
418
- console.assert(buffer && buffer instanceof Buffer);
419
- const dataType = buffer.subarray(0, 1).toString('utf8');
420
- console.assert(dataType !== CMD_COMPRESSED, "Compressed data shouldn't be decompressed before parsing");
421
- console.assert(dataType !== CMD_ROWSET_CHUNK, 'Chunked data should be parsed by parseRowsetChunks');
422
- let spaceIndex = buffer.indexOf(' ');
423
- if (spaceIndex === -1) {
424
- spaceIndex = buffer.length - 1;
425
- }
426
- let commandEnd = -1;
427
- if (dataType === CMD_INT || dataType === CMD_FLOAT || dataType === CMD_NULL) {
428
- commandEnd = spaceIndex + 1;
429
- }
430
- else {
431
- const commandLength = parseInt(buffer.subarray(1, spaceIndex).toString());
432
- commandEnd = spaceIndex + 1 + commandLength;
433
- }
434
- switch (dataType) {
435
- case CMD_INT:
436
- return popResults(parseInt(buffer.subarray(1, spaceIndex).toString()));
437
- case CMD_FLOAT:
438
- return popResults(parseFloat(buffer.subarray(1, spaceIndex).toString()));
439
- case CMD_NULL:
440
- return popResults(null);
441
- case CMD_STRING:
442
- return popResults(buffer.subarray(spaceIndex + 1, commandEnd).toString('utf8'));
443
- case CMD_ZEROSTRING:
444
- return popResults(buffer.subarray(spaceIndex + 1, commandEnd - 1).toString('utf8'));
445
- case CMD_COMMAND:
446
- return popResults(buffer.subarray(spaceIndex + 1, commandEnd).toString('utf8'));
447
- case CMD_JSON:
448
- return popResults(JSON.parse(buffer.subarray(spaceIndex + 1, commandEnd).toString('utf8')));
449
- case CMD_BLOB:
450
- return popResults(buffer.subarray(spaceIndex + 1, commandEnd));
451
- case CMD_ARRAY:
452
- return popResults(parseArray(buffer, spaceIndex));
453
- case CMD_ROWSET:
454
- return popResults(parseRowset(buffer, spaceIndex));
455
- case CMD_ERROR:
456
- parseError(buffer, spaceIndex); // throws custom error
457
- break;
458
- }
459
- throw new TypeError(`Data type: ${dataType} is not defined in SCSP`);
460
- }
461
- /** Format a command to be sent via SCSP protocol */
462
- function formatCommand(command) {
463
- const commandLength = Buffer.byteLength(command, 'utf-8');
464
- return `+${commandLength} ${command}`;
465
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes