-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconnection.ts
More file actions
151 lines (132 loc) · 5.52 KB
/
connection.ts
File metadata and controls
151 lines (132 loc) · 5.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/**
* connection.ts - base abstract class for sqlitecloud server connections
*/
import { SQLiteCloudConfig, SQLiteCloudError, ErrorCallback, ResultsCallback, SQLiteCloudCommand, SQLiteCloudDataTypes } from './types'
import { validateConfiguration } from './utilities'
import { OperationsQueue } from './queue'
import { anonimizeCommand, getUpdateResults } from './utilities'
/**
* Base class for SQLiteCloudConnection handles basics and defines methods.
* Actual connection management and communication with the server in concrete classes.
*/
export abstract class SQLiteCloudConnection {
/** Parse and validate provided connectionstring or configuration */
constructor(config: SQLiteCloudConfig | string, callback?: ErrorCallback) {
if (typeof config === 'string') {
this.config = validateConfiguration({ connectionstring: config })
} else {
this.config = validateConfiguration(config)
}
// connect transport layer to server
this.connect(callback)
}
/** Configuration passed by client or extracted from connection string */
protected config: SQLiteCloudConfig
/** Returns the connection's configuration */
public getConfig(): SQLiteCloudConfig {
return { ...this.config }
}
/** Operations are serialized by waiting an any pending promises */
protected operations = new OperationsQueue()
//
// internal methods (some are implemented in concrete classes using different transport layers)
//
/** Connect will establish a tls or websocket transport to the server based on configuration and environment */
protected connect(callback?: ErrorCallback): this {
this.operations.enqueue(done => {
this.connectTransport(this.config, error => {
if (error) {
console.error(
`SQLiteCloudConnection.connect - error connecting ${this.config.host as string}:${this.config.port as number} ${error.toString()}`,
error
)
this.close()
}
if (callback) {
callback.call(this, error || null)
}
done(error)
})
})
return this
}
/* Opens a connection with the server and sends the initialization commands */
protected abstract connectTransport(config: SQLiteCloudConfig, callback?: ErrorCallback): this
/** Send a command, return the rowset/result or throw an error */
protected abstract transportCommands(commands: string | SQLiteCloudCommand, callback?: ResultsCallback): this
/** Will log to console if verbose mode is enabled */
protected log(message: string, ...optionalParams: any[]): void {
if (this.config.verbose) {
message = anonimizeCommand(message)
console.log(`${new Date().toISOString()} ${this.config.clientid as string}: ${message}`, ...optionalParams)
}
}
//
// public methods (some are abstract and implemented in concrete classes)
//
/** Returns true if connection is open */
public abstract get connected(): boolean
/** Enable verbose logging for debug purposes */
public verbose(): void {
this.config.verbose = true
}
/** Will enquee a command to be executed and callback with the resulting rowset/result/error */
public sendCommands(commands: string | SQLiteCloudCommand, callback?: ResultsCallback): this {
this.operations.enqueue(done => {
if (!this.connected) {
const error = new SQLiteCloudError('Connection not established', { errorCode: 'ERR_CONNECTION_NOT_ESTABLISHED' })
callback?.call(this, error)
done(error)
} else {
this.transportCommands(commands, (error, result) => {
callback?.call(this, error, result)
done(error)
})
}
})
return this
}
/**
* Sql is a promise based API for executing SQL statements. You can
* pass a simple string with a SQL statement or a template string
* using backticks and parameters in ${parameter} format. These parameters
* will be properly escaped and quoted like when using a prepared statement.
* @param sql A sql string or a template string in `backticks` format
* A SQLiteCloudCommand when the query is defined with question marks and bindings.
* @returns An array of rows in case of selections or an object with
* metadata in case of insert, update, delete.
*/
public async sql(sql: TemplateStringsArray | string | SQLiteCloudCommand, ...values: any[]): Promise<any> {
let commands = { query: '' } as SQLiteCloudCommand
// sql is a TemplateStringsArray, the 'raw' property is specific to TemplateStringsArray
if (Array.isArray(sql) && 'raw' in sql) {
let query = ''
sql.forEach((string, i) => {
// TemplateStringsArray splits the string before each variable
// used in the template. Add the question mark
// to the end of the string for the number of used variables.
query += string + (i < values.length ? '?' : '')
})
commands = { query, parameters: values }
} else if (typeof sql === 'string') {
commands = { query: sql, parameters: values }
} else if (typeof sql === 'object') {
commands = sql as SQLiteCloudCommand
} else {
throw new Error('Invalid sql')
}
return new Promise((resolve, reject) => {
this.sendCommands(commands, (error, results) => {
if (error) {
reject(error)
} else {
// metadata for operations like insert, update, delete?
const context = getUpdateResults(results)
resolve(context ? context : results)
}
})
})
}
/** Disconnect from server, release transport. */
public abstract close(): this
}