@sqlitecloud/drivers 1.0.178 → 1.0.245
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 +28 -0
- package/lib/drivers/connection-tls.d.ts +1 -0
- package/lib/drivers/connection-tls.js +31 -16
- package/lib/drivers/database.d.ts +10 -0
- package/lib/drivers/database.js +23 -0
- package/lib/drivers/protocol.d.ts +2 -0
- package/lib/drivers/protocol.js +38 -22
- package/lib/drivers/pubsub.d.ts +48 -0
- package/lib/drivers/pubsub.js +116 -0
- package/lib/drivers/types.d.ts +2 -1
- package/lib/drivers/types.js +1 -1
- package/lib/drivers/utilities.js +3 -2
- package/lib/sqlitecloud.drivers.dev.js +240 -20
- package/lib/sqlitecloud.drivers.js +1 -1
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -32,6 +32,34 @@ We aim for full compatibility with the established [sqlite3 API](https://www.npm
|
|
|
32
32
|
|
|
33
33
|
The package is developed entirely in TypeScript and is fully compatible with JavaScript. It doesn't require any native libraries. This makes it a straightforward and effective tool for managing cloud-based databases in a familiar SQLite environment.
|
|
34
34
|
|
|
35
|
+
## Publish / Subscribe (Pub/Sub)
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { Database } from '@sqlitecloud/drivers'
|
|
39
|
+
import { PubSub, PUBSUB_ENTITY_TYPE } from '@sqlitecloud/drivers/lib/drivers/pubsub'
|
|
40
|
+
|
|
41
|
+
let database = new Database('sqlitecloud://user:password@xxx.sqlite.cloud:8860/chinook.sqlite')
|
|
42
|
+
// or use sqlitecloud://xxx.sqlite.cloud:8860?apikey=xxxxxxx
|
|
43
|
+
|
|
44
|
+
const pubSub: PubSub = await database.getPubSub()
|
|
45
|
+
|
|
46
|
+
await pubSub.listen(PUBSUB_ENTITY_TYPE.TABLE, 'albums', (error, results, data) => {
|
|
47
|
+
if (results) {
|
|
48
|
+
// Changes on albums table will be received here as JSON object
|
|
49
|
+
console.log('Received message:', results)
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
await database.sql`INSERT INTO albums (Title, ArtistId) values ('Brand new song', 1)`
|
|
54
|
+
|
|
55
|
+
// Stop listening changes on the table
|
|
56
|
+
await pubSub.unlisten(PUBSUB_ENTITY_TYPE.TABLE, 'albums')
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Pub/Sub is a messaging pattern that allows multiple applications to communicate with each other asynchronously. In the context of SQLiteCloud, Pub/Sub can be used to provide real-time updates and notifications to subscribed applications whenever data changes in the database or it can be used to send payloads (messages) to anyone subscribed to a channel.
|
|
60
|
+
|
|
61
|
+
Pub/Sub Documentation: [https://docs.sqlitecloud.io/docs/pub-sub](https://docs.sqlitecloud.io/docs/pub-sub)
|
|
62
|
+
|
|
35
63
|
## More
|
|
36
64
|
|
|
37
65
|
How do I deploy SQLite in the cloud?
|
|
@@ -19,6 +19,7 @@ export declare class SQLiteCloudTlsConnection extends SQLiteCloudConnection {
|
|
|
19
19
|
private startedOn;
|
|
20
20
|
private executingCommands?;
|
|
21
21
|
private processCallback?;
|
|
22
|
+
private pendingChunks;
|
|
22
23
|
/** Handles data received in response to an outbound command sent by processCommands */
|
|
23
24
|
private processCommandsData;
|
|
24
25
|
/** Completes a transaction initiated by processCommands */
|
|
@@ -45,6 +45,7 @@ class SQLiteCloudTlsConnection extends connection_1.SQLiteCloudConnection {
|
|
|
45
45
|
// buffer to accumulate incoming data until an whole command is received and can be parsed
|
|
46
46
|
this.buffer = Buffer.alloc(0);
|
|
47
47
|
this.startedOn = new Date();
|
|
48
|
+
this.pendingChunks = [];
|
|
48
49
|
}
|
|
49
50
|
/** True if connection is open */
|
|
50
51
|
get connected() {
|
|
@@ -137,10 +138,11 @@ class SQLiteCloudTlsConnection extends connection_1.SQLiteCloudConnection {
|
|
|
137
138
|
}
|
|
138
139
|
/** Handles data received in response to an outbound command sent by processCommands */
|
|
139
140
|
processCommandsData(data) {
|
|
140
|
-
var _a, _b, _c, _d, _e, _f;
|
|
141
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
141
142
|
try {
|
|
142
143
|
// append data to buffer as it arrives
|
|
143
144
|
if (data.length && data.length > 0) {
|
|
145
|
+
// console.debug(`processCommandsData - received ${data.length} bytes`)
|
|
144
146
|
this.buffer = Buffer.concat([this.buffer, data]);
|
|
145
147
|
}
|
|
146
148
|
let dataType = (_a = this.buffer) === null || _a === void 0 ? void 0 : _a.subarray(0, 1).toString();
|
|
@@ -154,22 +156,33 @@ class SQLiteCloudTlsConnection extends connection_1.SQLiteCloudConnection {
|
|
|
154
156
|
bufferString = bufferString.substring(0, 100) + '...' + bufferString.substring(bufferString.length - 40);
|
|
155
157
|
}
|
|
156
158
|
const elapsedMs = new Date().getTime() - this.startedOn.getTime();
|
|
157
|
-
console.debug(`<- ${bufferString} (${elapsedMs}ms)`);
|
|
159
|
+
console.debug(`<- ${bufferString} (${bufferString.length} bytes, ${elapsedMs}ms)`);
|
|
158
160
|
}
|
|
159
161
|
// need to decompress this buffer before decoding?
|
|
160
162
|
if (dataType === protocol_1.CMD_COMPRESSED) {
|
|
161
|
-
;
|
|
162
|
-
(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
163
|
+
const decompressResults = (0, protocol_1.decompressBuffer)(this.buffer);
|
|
164
|
+
if (decompressResults.dataType === protocol_1.CMD_ROWSET_CHUNK) {
|
|
165
|
+
this.pendingChunks.push(decompressResults.buffer);
|
|
166
|
+
this.buffer = decompressResults.remainingBuffer;
|
|
167
|
+
this.processCommandsData(Buffer.alloc(0));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
const { data } = (0, protocol_1.popData)(decompressResults.buffer);
|
|
172
|
+
(_c = this.processCommandsFinish) === null || _c === void 0 ? void 0 : _c.call(this, null, data);
|
|
173
|
+
}
|
|
167
174
|
}
|
|
168
175
|
else {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
176
|
+
if (dataType !== protocol_1.CMD_ROWSET_CHUNK) {
|
|
177
|
+
const { data } = (0, protocol_1.popData)(this.buffer);
|
|
178
|
+
(_d = this.processCommandsFinish) === null || _d === void 0 ? void 0 : _d.call(this, null, data);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
const completeChunk = (0, protocol_1.bufferEndsWith)(this.buffer, protocol_1.ROWSET_CHUNKS_END);
|
|
182
|
+
if (completeChunk) {
|
|
183
|
+
const parsedData = (0, protocol_1.parseRowsetChunks)([...this.pendingChunks, this.buffer]);
|
|
184
|
+
(_e = this.processCommandsFinish) === null || _e === void 0 ? void 0 : _e.call(this, null, parsedData);
|
|
185
|
+
}
|
|
173
186
|
}
|
|
174
187
|
}
|
|
175
188
|
}
|
|
@@ -179,14 +192,15 @@ class SQLiteCloudTlsConnection extends connection_1.SQLiteCloudConnection {
|
|
|
179
192
|
const lastChar = this.buffer.subarray(this.buffer.length - 1, this.buffer.length).toString('utf8');
|
|
180
193
|
if (lastChar == ' ') {
|
|
181
194
|
const { data } = (0, protocol_1.popData)(this.buffer);
|
|
182
|
-
(
|
|
195
|
+
(_f = this.processCommandsFinish) === null || _f === void 0 ? void 0 : _f.call(this, null, data);
|
|
183
196
|
}
|
|
184
197
|
}
|
|
185
198
|
}
|
|
186
199
|
catch (error) {
|
|
187
|
-
console.
|
|
200
|
+
console.error(`processCommandsData - error: ${error}`);
|
|
201
|
+
console.assert(error instanceof Error, 'An error occoured while processing data');
|
|
188
202
|
if (error instanceof Error) {
|
|
189
|
-
(
|
|
203
|
+
(_g = this.processCommandsFinish) === null || _g === void 0 ? void 0 : _g.call(this, error);
|
|
190
204
|
}
|
|
191
205
|
}
|
|
192
206
|
}
|
|
@@ -202,8 +216,9 @@ class SQLiteCloudTlsConnection extends connection_1.SQLiteCloudConnection {
|
|
|
202
216
|
}
|
|
203
217
|
if (this.processCallback) {
|
|
204
218
|
this.processCallback(error, result);
|
|
205
|
-
// this.processCallback = undefined
|
|
206
219
|
}
|
|
220
|
+
this.buffer = Buffer.alloc(0);
|
|
221
|
+
this.pendingChunks = [];
|
|
207
222
|
}
|
|
208
223
|
/** Disconnect immediately, release connection, no events. */
|
|
209
224
|
close() {
|
|
@@ -2,6 +2,7 @@ import { SQLiteCloudConfig, RowCountCallback } from './types';
|
|
|
2
2
|
import { Statement } from './statement';
|
|
3
3
|
import { ErrorCallback, ResultsCallback, RowCallback, RowsCallback } from './types';
|
|
4
4
|
import EventEmitter from 'eventemitter3';
|
|
5
|
+
import { PubSub } from './pubsub';
|
|
5
6
|
/**
|
|
6
7
|
* Creating a Database object automatically opens a connection to the SQLite database.
|
|
7
8
|
* When the connection is established the Database object emits an open event and calls
|
|
@@ -151,4 +152,13 @@ export declare class Database extends EventEmitter {
|
|
|
151
152
|
* metadata in case of insert, update, delete.
|
|
152
153
|
*/
|
|
153
154
|
sql(sql: TemplateStringsArray | string, ...values: any[]): Promise<any>;
|
|
155
|
+
/**
|
|
156
|
+
* PubSub class provides a Pub/Sub real-time updates and notifications system to
|
|
157
|
+
* allow multiple applications to communicate with each other asynchronously.
|
|
158
|
+
* It allows applications to subscribe to tables and receive notifications whenever
|
|
159
|
+
* data changes in the database table. It also enables sending messages to anyone
|
|
160
|
+
* subscribed to a specific channel.
|
|
161
|
+
* @returns {PubSub} A PubSub object
|
|
162
|
+
*/
|
|
163
|
+
getPubSub(): Promise<PubSub>;
|
|
154
164
|
}
|
package/lib/drivers/database.js
CHANGED
|
@@ -45,6 +45,7 @@ const utilities_1 = require("./utilities");
|
|
|
45
45
|
const statement_1 = require("./statement");
|
|
46
46
|
const eventemitter3_1 = __importDefault(require("eventemitter3"));
|
|
47
47
|
const utilities_2 = require("./utilities");
|
|
48
|
+
const pubsub_1 = require("./pubsub");
|
|
48
49
|
// Uses eventemitter3 instead of node events for browser compatibility
|
|
49
50
|
// https://github.com/primus/eventemitter3
|
|
50
51
|
/**
|
|
@@ -443,5 +444,27 @@ class Database extends eventemitter3_1.default {
|
|
|
443
444
|
});
|
|
444
445
|
});
|
|
445
446
|
}
|
|
447
|
+
/**
|
|
448
|
+
* PubSub class provides a Pub/Sub real-time updates and notifications system to
|
|
449
|
+
* allow multiple applications to communicate with each other asynchronously.
|
|
450
|
+
* It allows applications to subscribe to tables and receive notifications whenever
|
|
451
|
+
* data changes in the database table. It also enables sending messages to anyone
|
|
452
|
+
* subscribed to a specific channel.
|
|
453
|
+
* @returns {PubSub} A PubSub object
|
|
454
|
+
*/
|
|
455
|
+
getPubSub() {
|
|
456
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
457
|
+
return new Promise((resolve, reject) => {
|
|
458
|
+
this.getConnection((error, connection) => {
|
|
459
|
+
if (error || !connection) {
|
|
460
|
+
reject(error);
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
resolve(new pubsub_1.PubSub(connection));
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
}
|
|
446
469
|
}
|
|
447
470
|
exports.Database = Database;
|
|
@@ -14,6 +14,7 @@ export declare const CMD_BLOB = "$";
|
|
|
14
14
|
export declare const CMD_COMPRESSED = "%";
|
|
15
15
|
export declare const CMD_COMMAND = "^";
|
|
16
16
|
export declare const CMD_ARRAY = "=";
|
|
17
|
+
export declare const CMD_PUBSUB = "|";
|
|
17
18
|
export declare const ROWSET_CHUNKS_END = "/6 0 0 0 ";
|
|
18
19
|
/** Analyze first character to check if corresponding data type has LEN */
|
|
19
20
|
export declare function hasCommandLength(firstCharacter: string): boolean;
|
|
@@ -23,6 +24,7 @@ export declare function parseCommandLength(data: Buffer): number;
|
|
|
23
24
|
export declare function decompressBuffer(buffer: Buffer): {
|
|
24
25
|
buffer: Buffer;
|
|
25
26
|
dataType: string;
|
|
27
|
+
remainingBuffer: Buffer;
|
|
26
28
|
};
|
|
27
29
|
/** Parse error message or extended error message */
|
|
28
30
|
export declare function parseError(buffer: Buffer, spaceIndex: number): never;
|
package/lib/drivers/protocol.js
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
// protocol.ts - low level protocol handling for SQLiteCloud transport
|
|
4
4
|
//
|
|
5
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;
|
|
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_PUBSUB = 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
7
|
const types_1 = require("./types");
|
|
8
8
|
const rowset_1 = require("./rowset");
|
|
9
|
+
// https://www.npmjs.com/package/lz4js
|
|
9
10
|
const lz4 = require('lz4js');
|
|
10
11
|
// The server communicates with clients via commands defined in
|
|
11
12
|
// SQLiteCloud Server Protocol (SCSP), see more at:
|
|
@@ -24,7 +25,7 @@ exports.CMD_COMPRESSED = '%';
|
|
|
24
25
|
exports.CMD_COMMAND = '^';
|
|
25
26
|
exports.CMD_ARRAY = '=';
|
|
26
27
|
// const CMD_RAWJSON = '{'
|
|
27
|
-
|
|
28
|
+
exports.CMD_PUBSUB = '|';
|
|
28
29
|
// const CMD_RECONNECT = '@'
|
|
29
30
|
// To mark the end of the Rowset, the special string /LEN 0 0 0 is sent (LEN is always 6 in this case)
|
|
30
31
|
// https://github.com/sqlitecloud/sdk/blob/master/PROTOCOL.md#scsp-rowset-chunk
|
|
@@ -44,26 +45,31 @@ function parseCommandLength(data) {
|
|
|
44
45
|
exports.parseCommandLength = parseCommandLength;
|
|
45
46
|
/** Receive a compressed buffer, decompress with lz4, return buffer and datatype */
|
|
46
47
|
function decompressBuffer(buffer) {
|
|
48
|
+
// https://github.com/sqlitecloud/sdk/blob/master/PROTOCOL.md#scsp-compression
|
|
49
|
+
// jest test/database.test.ts -t "select large result set"
|
|
50
|
+
// starts with %<commandLength> <compressed> <uncompressed>
|
|
47
51
|
const spaceIndex = buffer.indexOf(' ');
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
const commandLength = parseInt(buffer.subarray(1, spaceIndex).toString('utf8'));
|
|
53
|
+
let commandBuffer = buffer.subarray(spaceIndex + 1, spaceIndex + 1 + commandLength);
|
|
54
|
+
const remainingBuffer = buffer.subarray(spaceIndex + 1 + commandLength);
|
|
55
|
+
// extract compressed + decompressed size
|
|
56
|
+
const compressedSize = parseInt(commandBuffer.subarray(0, commandBuffer.indexOf(' ') + 1).toString('utf8'));
|
|
57
|
+
commandBuffer = commandBuffer.subarray(commandBuffer.indexOf(' ') + 1);
|
|
58
|
+
const decompressedSize = parseInt(commandBuffer.subarray(0, commandBuffer.indexOf(' ') + 1).toString('utf8'));
|
|
59
|
+
commandBuffer = commandBuffer.subarray(commandBuffer.indexOf(' ') + 1);
|
|
55
60
|
// extract compressed dataType
|
|
56
|
-
const dataType =
|
|
57
|
-
|
|
58
|
-
const compressedBuffer =
|
|
61
|
+
const dataType = commandBuffer.subarray(0, 1).toString('utf8');
|
|
62
|
+
let decompressedBuffer = Buffer.alloc(decompressedSize);
|
|
63
|
+
const compressedBuffer = commandBuffer.subarray(commandBuffer.length - compressedSize);
|
|
59
64
|
// lz4js library is javascript and doesn't have types so we silence the type check
|
|
60
65
|
// eslint-disable-next-line
|
|
61
66
|
const decompressionResult = lz4.decompressBlock(compressedBuffer, decompressedBuffer, 0, compressedSize, 0);
|
|
62
|
-
|
|
67
|
+
// the entire command is composed of the header (which is not compressed) + the decompressed block
|
|
68
|
+
decompressedBuffer = Buffer.concat([commandBuffer.subarray(0, commandBuffer.length - compressedSize), decompressedBuffer]);
|
|
63
69
|
if (decompressionResult <= 0 || decompressionResult !== decompressedSize) {
|
|
64
70
|
throw new Error(`lz4 decompression error at offset ${decompressionResult}`);
|
|
65
71
|
}
|
|
66
|
-
return { buffer, dataType };
|
|
72
|
+
return { buffer: decompressedBuffer, dataType, remainingBuffer };
|
|
67
73
|
}
|
|
68
74
|
exports.decompressBuffer = decompressBuffer;
|
|
69
75
|
/** Parse error message or extended error message */
|
|
@@ -117,7 +123,7 @@ function parseRowsetHeader(buffer) {
|
|
|
117
123
|
buffer = buffer.subarray(buffer.indexOf(':') + 1);
|
|
118
124
|
// extract rowset header
|
|
119
125
|
const { data, fwdBuffer } = popIntegers(buffer, 3);
|
|
120
|
-
|
|
126
|
+
const result = {
|
|
121
127
|
index,
|
|
122
128
|
metadata: {
|
|
123
129
|
version: data[0],
|
|
@@ -127,6 +133,8 @@ function parseRowsetHeader(buffer) {
|
|
|
127
133
|
},
|
|
128
134
|
fwdBuffer
|
|
129
135
|
};
|
|
136
|
+
// console.debug(`parseRowsetHeader`, result)
|
|
137
|
+
return result;
|
|
130
138
|
}
|
|
131
139
|
exports.parseRowsetHeader = parseRowsetHeader;
|
|
132
140
|
/** Extract column names and, optionally, more metadata out of a rowset's header */
|
|
@@ -195,7 +203,8 @@ function parseRowsetChunks(buffers) {
|
|
|
195
203
|
const data = [];
|
|
196
204
|
// validate and skip data type
|
|
197
205
|
const dataType = buffer.subarray(0, 1).toString();
|
|
198
|
-
|
|
206
|
+
if (dataType !== exports.CMD_ROWSET_CHUNK)
|
|
207
|
+
throw new Error(`parseRowsetChunks - dataType: ${dataType} should be CMD_ROWSET_CHUNK`);
|
|
199
208
|
buffer = buffer.subarray(buffer.indexOf(' ') + 1);
|
|
200
209
|
while (buffer.length > 0 && !bufferStartsWith(buffer, exports.ROWSET_CHUNKS_END)) {
|
|
201
210
|
// chunk header, eg: 0:VERS NROWS NCOLS
|
|
@@ -240,21 +249,24 @@ function popData(buffer) {
|
|
|
240
249
|
}
|
|
241
250
|
// first character is the data type
|
|
242
251
|
console.assert(buffer && buffer instanceof Buffer);
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
252
|
+
let dataType = buffer.subarray(0, 1).toString('utf8');
|
|
253
|
+
if (dataType == exports.CMD_COMPRESSED)
|
|
254
|
+
throw new Error('Compressed data should be decompressed before parsing');
|
|
255
|
+
if (dataType == exports.CMD_ROWSET_CHUNK)
|
|
256
|
+
throw new Error('Chunked data should be parsed by parseRowsetChunks');
|
|
246
257
|
let spaceIndex = buffer.indexOf(' ');
|
|
247
258
|
if (spaceIndex === -1) {
|
|
248
259
|
spaceIndex = buffer.length - 1;
|
|
249
260
|
}
|
|
250
|
-
let commandEnd = -1;
|
|
261
|
+
let commandEnd = -1, commandLength = -1;
|
|
251
262
|
if (dataType === exports.CMD_INT || dataType === exports.CMD_FLOAT || dataType === exports.CMD_NULL) {
|
|
252
263
|
commandEnd = spaceIndex + 1;
|
|
253
264
|
}
|
|
254
265
|
else {
|
|
255
|
-
|
|
266
|
+
commandLength = parseInt(buffer.subarray(1, spaceIndex).toString());
|
|
256
267
|
commandEnd = spaceIndex + 1 + commandLength;
|
|
257
268
|
}
|
|
269
|
+
// console.debug(`popData - dataType: ${dataType}, spaceIndex: ${spaceIndex}, commandLength: ${commandLength}, commandEnd: ${commandEnd}`)
|
|
258
270
|
switch (dataType) {
|
|
259
271
|
case exports.CMD_INT:
|
|
260
272
|
return popResults(parseInt(buffer.subarray(1, spaceIndex).toString()));
|
|
@@ -268,6 +280,8 @@ function popData(buffer) {
|
|
|
268
280
|
return popResults(buffer.subarray(spaceIndex + 1, commandEnd - 1).toString('utf8'));
|
|
269
281
|
case exports.CMD_COMMAND:
|
|
270
282
|
return popResults(buffer.subarray(spaceIndex + 1, commandEnd).toString('utf8'));
|
|
283
|
+
case exports.CMD_PUBSUB:
|
|
284
|
+
return popResults(buffer.subarray(spaceIndex + 1, commandEnd).toString('utf8'));
|
|
271
285
|
case exports.CMD_JSON:
|
|
272
286
|
return popResults(JSON.parse(buffer.subarray(spaceIndex + 1, commandEnd).toString('utf8')));
|
|
273
287
|
case exports.CMD_BLOB:
|
|
@@ -280,7 +294,9 @@ function popData(buffer) {
|
|
|
280
294
|
parseError(buffer, spaceIndex); // throws custom error
|
|
281
295
|
break;
|
|
282
296
|
}
|
|
283
|
-
|
|
297
|
+
const msg = `popData - Data type: ${Number(dataType)} '${dataType}' is not defined in SCSP, spaceIndex: ${spaceIndex}, commandLength: ${commandLength}, commandEnd: ${commandEnd}`;
|
|
298
|
+
console.error(msg);
|
|
299
|
+
throw new TypeError(msg);
|
|
284
300
|
}
|
|
285
301
|
exports.popData = popData;
|
|
286
302
|
/** Format a command to be sent via SCSP protocol */
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { SQLiteCloudConnection } from './connection';
|
|
2
|
+
import { PubSubCallback } from './types';
|
|
3
|
+
export declare enum PUBSUB_ENTITY_TYPE {
|
|
4
|
+
TABLE = "TABLE",
|
|
5
|
+
CHANNEL = "CHANNEL"
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Pub/Sub class to receive changes on database tables or to send messages to channels.
|
|
9
|
+
*/
|
|
10
|
+
export declare class PubSub {
|
|
11
|
+
constructor(connection: SQLiteCloudConnection);
|
|
12
|
+
private connection;
|
|
13
|
+
private connectionPubSub;
|
|
14
|
+
/**
|
|
15
|
+
* Listen for a table or channel and start to receive messages to the provided callback.
|
|
16
|
+
* @param entityType One of TABLE or CHANNEL'
|
|
17
|
+
* @param entityName Name of the table or the channel
|
|
18
|
+
* @param callback Callback to be called when a message is received
|
|
19
|
+
* @param data Extra data to be passed to the callback
|
|
20
|
+
*/
|
|
21
|
+
listen(entityType: PUBSUB_ENTITY_TYPE, entityName: string, callback: PubSubCallback, data?: any): Promise<any>;
|
|
22
|
+
/**
|
|
23
|
+
* Stop receive messages from a table or channel.
|
|
24
|
+
* @param entityType One of TABLE or CHANNEL
|
|
25
|
+
* @param entityName Name of the table or the channel
|
|
26
|
+
*/
|
|
27
|
+
unlisten(entityType: string, entityName: string): Promise<any>;
|
|
28
|
+
/**
|
|
29
|
+
* Create a channel to send messages to.
|
|
30
|
+
* @param name Channel name
|
|
31
|
+
* @param failIfExists Raise an error if the channel already exists
|
|
32
|
+
*/
|
|
33
|
+
createChannel(name: string, failIfExists?: boolean): Promise<any>;
|
|
34
|
+
/**
|
|
35
|
+
* Send a message to the channel.
|
|
36
|
+
*/
|
|
37
|
+
notifyChannel(channelName: string, message: string): Promise<any>;
|
|
38
|
+
/**
|
|
39
|
+
* Ask the server to close the connection to the database and
|
|
40
|
+
* to keep only open the Pub/Sub connection.
|
|
41
|
+
* Only interaction with Pub/Sub commands will be allowed.
|
|
42
|
+
*/
|
|
43
|
+
setPubSubOnly(): Promise<any>;
|
|
44
|
+
/** True if Pub/Sub connection is open. */
|
|
45
|
+
connected(): boolean;
|
|
46
|
+
/** Close Pub/Sub connection. */
|
|
47
|
+
close(): void;
|
|
48
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.PubSub = exports.PUBSUB_ENTITY_TYPE = void 0;
|
|
16
|
+
const connection_tls_1 = __importDefault(require("./connection-tls"));
|
|
17
|
+
var PUBSUB_ENTITY_TYPE;
|
|
18
|
+
(function (PUBSUB_ENTITY_TYPE) {
|
|
19
|
+
PUBSUB_ENTITY_TYPE["TABLE"] = "TABLE";
|
|
20
|
+
PUBSUB_ENTITY_TYPE["CHANNEL"] = "CHANNEL";
|
|
21
|
+
})(PUBSUB_ENTITY_TYPE = exports.PUBSUB_ENTITY_TYPE || (exports.PUBSUB_ENTITY_TYPE = {}));
|
|
22
|
+
/**
|
|
23
|
+
* Pub/Sub class to receive changes on database tables or to send messages to channels.
|
|
24
|
+
*/
|
|
25
|
+
class PubSub {
|
|
26
|
+
constructor(connection) {
|
|
27
|
+
this.connection = connection;
|
|
28
|
+
this.connectionPubSub = new connection_tls_1.default(connection.getConfig());
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Listen for a table or channel and start to receive messages to the provided callback.
|
|
32
|
+
* @param entityType One of TABLE or CHANNEL'
|
|
33
|
+
* @param entityName Name of the table or the channel
|
|
34
|
+
* @param callback Callback to be called when a message is received
|
|
35
|
+
* @param data Extra data to be passed to the callback
|
|
36
|
+
*/
|
|
37
|
+
listen(entityType, entityName, callback, data) {
|
|
38
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
39
|
+
const entity = entityType === 'TABLE' ? 'TABLE ' : '';
|
|
40
|
+
const authCommand = yield this.connection.sql(`LISTEN ${entity}${entityName};`);
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
this.connectionPubSub.sendCommands(authCommand, (error, results) => {
|
|
43
|
+
if (error) {
|
|
44
|
+
callback.call(this, error, null, data);
|
|
45
|
+
reject(error);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// skip results from pubSub auth command
|
|
49
|
+
if (results !== 'OK') {
|
|
50
|
+
callback.call(this, null, results, data);
|
|
51
|
+
}
|
|
52
|
+
resolve(results);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Stop receive messages from a table or channel.
|
|
60
|
+
* @param entityType One of TABLE or CHANNEL
|
|
61
|
+
* @param entityName Name of the table or the channel
|
|
62
|
+
*/
|
|
63
|
+
unlisten(entityType, entityName) {
|
|
64
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
65
|
+
const subject = entityType === 'TABLE' ? 'TABLE ' : '';
|
|
66
|
+
return this.connection.sql(`UNLISTEN ${subject}?;`, entityName);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Create a channel to send messages to.
|
|
71
|
+
* @param name Channel name
|
|
72
|
+
* @param failIfExists Raise an error if the channel already exists
|
|
73
|
+
*/
|
|
74
|
+
createChannel(name, failIfExists = true) {
|
|
75
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
76
|
+
let notExistsCommand = '';
|
|
77
|
+
if (!failIfExists) {
|
|
78
|
+
notExistsCommand = 'IF NOT EXISTS;';
|
|
79
|
+
}
|
|
80
|
+
return this.connection.sql(`CREATE CHANNEL ? ${notExistsCommand}`, name);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Send a message to the channel.
|
|
85
|
+
*/
|
|
86
|
+
notifyChannel(channelName, message) {
|
|
87
|
+
return this.connection.sql `NOTIFY ${channelName} ${message};`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Ask the server to close the connection to the database and
|
|
91
|
+
* to keep only open the Pub/Sub connection.
|
|
92
|
+
* Only interaction with Pub/Sub commands will be allowed.
|
|
93
|
+
*/
|
|
94
|
+
setPubSubOnly() {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
this.connection.sendCommands('PUBSUB ONLY;', (error, results) => {
|
|
97
|
+
if (error) {
|
|
98
|
+
reject(error);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
this.connection.close();
|
|
102
|
+
resolve(results);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/** True if Pub/Sub connection is open. */
|
|
108
|
+
connected() {
|
|
109
|
+
return this.connectionPubSub.connected;
|
|
110
|
+
}
|
|
111
|
+
/** Close Pub/Sub connection. */
|
|
112
|
+
close() {
|
|
113
|
+
this.connectionPubSub.close();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.PubSub = PubSub;
|
package/lib/drivers/types.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import tls from 'tls';
|
|
|
7
7
|
/** Default timeout value for queries */
|
|
8
8
|
export declare const DEFAULT_TIMEOUT: number;
|
|
9
9
|
/** Default tls connection port */
|
|
10
|
-
export declare const DEFAULT_PORT =
|
|
10
|
+
export declare const DEFAULT_PORT = 8860;
|
|
11
11
|
/**
|
|
12
12
|
* Configuration for SQLite cloud connection
|
|
13
13
|
* @note Options are all lowecase so they 1:1 compatible with C SDK
|
|
@@ -108,6 +108,7 @@ export type ResultsCallback<T = any> = (error: Error | null, results?: T) => voi
|
|
|
108
108
|
export type RowsCallback<T = Record<string, any>> = (error: Error | null, rows?: T[]) => void;
|
|
109
109
|
export type RowCallback<T = Record<string, any>> = (error: Error | null, row?: T) => void;
|
|
110
110
|
export type RowCountCallback = (error: Error | null, rowCount?: number) => void;
|
|
111
|
+
export type PubSubCallback<T = any> = (error: Error | null, results?: T, extraData?: T) => void;
|
|
111
112
|
/**
|
|
112
113
|
* Certain responses include arrays with various types of metadata.
|
|
113
114
|
* The first entry is always an array type from this list. This enum
|
package/lib/drivers/types.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.SQLiteCloudArrayType = exports.SQLiteCloudError = exports.DEFAULT_PORT =
|
|
|
7
7
|
/** Default timeout value for queries */
|
|
8
8
|
exports.DEFAULT_TIMEOUT = 300 * 1000;
|
|
9
9
|
/** Default tls connection port */
|
|
10
|
-
exports.DEFAULT_PORT =
|
|
10
|
+
exports.DEFAULT_PORT = 8860;
|
|
11
11
|
/** Custom error reported by SQLiteCloud drivers */
|
|
12
12
|
class SQLiteCloudError extends Error {
|
|
13
13
|
constructor(message, args) {
|
package/lib/drivers/utilities.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.parseBooleanToZeroOne = exports.parseBoolean = exports.parseconnectionstring = exports.validateConfiguration = exports.popCallback = exports.getUpdateResults = 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
|
const types_2 = require("./types");
|
|
9
|
+
const whatwg_url_1 = require("whatwg-url");
|
|
9
10
|
//
|
|
10
11
|
// determining running environment, thanks to browser-or-node
|
|
11
12
|
// https://www.npmjs.com/package/browser-or-node
|
|
@@ -200,7 +201,7 @@ function validateConfiguration(config) {
|
|
|
200
201
|
config.clientid || (config.clientid = 'SQLiteCloud');
|
|
201
202
|
config.verbose = parseBoolean(config.verbose);
|
|
202
203
|
config.noblob = parseBoolean(config.noblob);
|
|
203
|
-
config.compression = parseBoolean(config.compression);
|
|
204
|
+
config.compression = config.compression != undefined && config.compression != null ? parseBoolean(config.compression) : true; // default: true
|
|
204
205
|
config.create = parseBoolean(config.create);
|
|
205
206
|
config.non_linearizable = parseBoolean(config.non_linearizable);
|
|
206
207
|
config.insecure = parseBoolean(config.insecure);
|
|
@@ -235,7 +236,7 @@ function parseconnectionstring(connectionstring) {
|
|
|
235
236
|
// the sqlitecloud: protocol is not recognized by the URL constructor in browsers
|
|
236
237
|
// so we need to replace it with https: to make it work
|
|
237
238
|
const knownProtocolUrl = connectionstring.replace('sqlitecloud:', 'https:');
|
|
238
|
-
const url = new URL(knownProtocolUrl);
|
|
239
|
+
const url = new whatwg_url_1.URL(knownProtocolUrl);
|
|
239
240
|
// all lowecase options
|
|
240
241
|
const options = {};
|
|
241
242
|
url.searchParams.forEach((value, key) => {
|