Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM oven/bun:debian

# Config Bun
ENV PATH="~/.bun/bin:${PATH}"
RUN ln -s /usr/local/bin/bun /usr/local/bin/node

# Update packages
RUN if [ "debian" == "alpine" ] ; then apk update ; else apt-get update ; fi

# Install Git
RUN if [ "debian" == "alpine" ] ; then apk add git ; else apt-get install -y git ; fi

19 changes: 19 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/marcosgomesneto/bun-devcontainers/tree/main/src/basic-bun
{
"name": "Bun",
"dockerFile": "Dockerfile",
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"oven.bun-vscode"
]
}
},
"features": {
"ghcr.io/devcontainers/features/node:1": {}
}
}
1 change: 0 additions & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
bun install
bun install --production
bun .husky/pre-commit.js
git add package.json
git add bun.lockb
6 changes: 5 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
{
"recommendations": ["kavod-io.vscode-jest-test-adapter", "esbenp.prettier-vscode", "github.vscode-github-actions", "hbenl.vscode-test-explorer"]
"recommendations": [
"esbenp.prettier-vscode",
"github.vscode-github-actions",
"hbenl.vscode-test-explorer"
]
}
17 changes: 3 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 7 additions & 5 deletions src/drivers/connection-tls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* connection-tls.ts - connection via tls socket and sqlitecloud protocol
*/

import { type SQLiteCloudConfig, SQLiteCloudError, type ErrorCallback, type ResultsCallback } from './types'
import { type SQLiteCloudConfig, SQLiteCloudError, type ErrorCallback, type ResultsCallback, SQLiteCloudCommand } from './types'
import { SQLiteCloudConnection } from './connection'
import { getInitializationCommands } from './utilities'
import {
Expand All @@ -23,8 +23,6 @@ import { Buffer } from 'buffer'

import * as tls from 'tls'

import fs from 'fs'

/**
* Implementation of SQLiteCloudConnection that connects to the database using specific tls APIs
* that connect to native sockets or tls sockets and communicates via raw, binary protocol.
Expand Down Expand Up @@ -104,13 +102,17 @@ export class SQLiteCloudTlsConnection extends SQLiteCloudConnection {
}

/** Will send a command immediately (no queueing), return the rowset/result or throw an error */
transportCommands(commands: string, callback?: ResultsCallback): this {
transportCommands(commands: string | SQLiteCloudCommand, callback?: ResultsCallback): this {
// connection needs to be established?
if (!this.socket) {
callback?.call(this, new SQLiteCloudError('Connection not established', { errorCode: 'ERR_CONNECTION_NOT_ESTABLISHED' }))
return this
}

if (typeof commands === 'string') {
commands = { query: commands } as SQLiteCloudCommand
}

// reset buffer and rowset chunks, define response callback
this.buffer = Buffer.alloc(0)
this.startedOn = new Date()
Expand Down Expand Up @@ -148,7 +150,7 @@ export class SQLiteCloudTlsConnection extends SQLiteCloudConnection {
// buffer to accumulate incoming data until an whole command is received and can be parsed
private buffer: Buffer = Buffer.alloc(0)
private startedOn: Date = new Date()
private executingCommands?: string
private executingCommands?: SQLiteCloudCommand

// callback to be called when a command is finished processing
private processCallback?: ResultsCallback
Expand Down
4 changes: 2 additions & 2 deletions src/drivers/connection-ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* transport-ws.ts - handles low level communication with sqlitecloud server via socket.io websocket
*/

import { SQLiteCloudConfig, SQLiteCloudError, ErrorCallback, ResultsCallback } from './types'
import { SQLiteCloudConfig, SQLiteCloudError, ErrorCallback, ResultsCallback, SQLiteCloudCommand } from './types'
import { SQLiteCloudRowset } from './rowset'
import { SQLiteCloudConnection } from './connection'
import { io, Socket } from 'socket.io-client'
Expand Down Expand Up @@ -41,7 +41,7 @@ export class SQLiteCloudWebsocketConnection extends SQLiteCloudConnection {
}

/** Will send a command immediately (no queueing), return the rowset/result or throw an error */
transportCommands(commands: string, callback?: ResultsCallback): this {
transportCommands(commands: string | SQLiteCloudCommand, callback?: ResultsCallback): this {
// connection needs to be established?
if (!this.socket) {
callback?.call(this, new SQLiteCloudError('Connection not established', { errorCode: 'ERR_CONNECTION_NOT_ESTABLISHED' }))
Expand Down
37 changes: 19 additions & 18 deletions src/drivers/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
* connection.ts - base abstract class for sqlitecloud server connections
*/

import { SQLiteCloudConfig, SQLiteCloudError, ErrorCallback, ResultsCallback } from './types'
import { validateConfiguration, prepareSql } from './utilities'
import { SQLiteCloudConfig, SQLiteCloudError, ErrorCallback, ResultsCallback, SQLiteCloudCommand } from './types'
import { validateConfiguration } from './utilities'
import { OperationsQueue } from './queue'
import { anonimizeCommand, getUpdateResults } from './utilities'

Expand Down Expand Up @@ -62,7 +62,7 @@ export abstract class SQLiteCloudConnection {
protected abstract connectTransport(config: SQLiteCloudConfig, callback?: ErrorCallback): this

/** Send a command, return the rowset/result or throw an error */
protected abstract transportCommands(commands: string, callback?: ResultsCallback): this
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 {
Expand All @@ -85,7 +85,7 @@ export abstract class SQLiteCloudConnection {
}

/** Will enquee a command to be executed and callback with the resulting rowset/result/error */
public sendCommands(commands: string, callback?: ResultsCallback): this {
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' })
Expand All @@ -108,32 +108,33 @@ export abstract class SQLiteCloudConnection {
* 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, ...values: any[]): Promise<any> {
let preparedSql = ''
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) => {
preparedSql += string + (i < values.length ? '?' : '')
// 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 ? '?' : '')
})
preparedSql = prepareSql(preparedSql, ...values)
commands = { query, parameters: values }
} else if (typeof sql === 'string') {
commands = { query: sql, parameters: values }
} else if (typeof sql === 'object') {
commands = sql as SQLiteCloudCommand
} else {
if (typeof sql === 'string') {
if (values?.length > 0) {
preparedSql = prepareSql(sql, ...values)
} else {
preparedSql = sql
}
} else {
throw new Error('Invalid sql')
}
throw new Error('Invalid sql')
}

return new Promise((resolve, reject) => {
this.sendCommands(preparedSql, (error, results) => {
this.sendCommands(commands, (error, results) => {
if (error) {
reject(error)
} else {
Expand Down
54 changes: 26 additions & 28 deletions src/drivers/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@

import { SQLiteCloudConnection } from './connection'
import { SQLiteCloudRowset } from './rowset'
import { SQLiteCloudConfig, SQLiteCloudError, RowCountCallback, SQLiteCloudArrayType } from './types'
import { prepareSql, popCallback } from './utilities'
import { Statement } from './statement'
import { SQLiteCloudConfig, SQLiteCloudError, RowCountCallback, SQLiteCloudArrayType, SQLiteCloudCommand } from './types'
import { popCallback } from './utilities'
import { ErrorCallback, ResultsCallback, RowCallback, RowsCallback } from './types'
import EventEmitter from 'eventemitter3'
import { isBrowser } from './utilities'
import { PubSub } from './pubsub'
import { Statement } from './statement'

// Uses eventemitter3 instead of node events for browser compatibility
// https://github.com/primus/eventemitter3
Expand Down Expand Up @@ -204,12 +204,12 @@ export class Database extends EventEmitter {
public run<T>(sql: string, params: any, callback?: ResultsCallback<T>): this
public run(sql: string, ...params: any[]): this {
const { args, callback } = popCallback<ResultsCallback>(params)
const preparedSql = args?.length > 0 ? prepareSql(sql, ...args) : sql
const command: SQLiteCloudCommand = { query: sql, parameters: args?.flat() }
this.getConnection((error, connection) => {
if (error || !connection) {
this.handleError(null, error as Error, callback)
} else {
connection.sendCommands(preparedSql, (error, results) => {
connection.sendCommands(command, (error, results) => {
if (error) {
this.handleError(connection, error, callback)
} else {
Expand Down Expand Up @@ -237,12 +237,12 @@ export class Database extends EventEmitter {
public get<T>(sql: string, params: any, callback?: RowCallback<T>): this
public get(sql: string, ...params: any[]): this {
const { args, callback } = popCallback<RowCallback>(params)
const preparedSql = args?.length > 0 ? prepareSql(sql, ...args) : sql
const command: SQLiteCloudCommand = { query: sql, parameters: args?.flat() }
this.getConnection((error, connection) => {
if (error || !connection) {
this.handleError(null, error as Error, callback)
} else {
connection.sendCommands(preparedSql, (error, results) => {
connection.sendCommands(command, (error, results) => {
if (error) {
this.handleError(connection, error, callback)
} else {
Expand Down Expand Up @@ -275,12 +275,12 @@ export class Database extends EventEmitter {
public all<T>(sql: string, params: any, callback?: RowsCallback<T>): this
public all(sql: string, ...params: any[]): this {
const { args, callback } = popCallback<RowsCallback>(params)
const preparedSql = args?.length > 0 ? prepareSql(sql, ...args) : sql
const command: SQLiteCloudCommand = { query: sql, parameters: args?.flat() }
this.getConnection((error, connection) => {
if (error || !connection) {
this.handleError(null, error as Error, callback)
} else {
connection.sendCommands(preparedSql, (error, results) => {
connection.sendCommands(command, (error, results) => {
if (error) {
this.handleError(connection, error, callback)
} else {
Expand Down Expand Up @@ -316,12 +316,12 @@ export class Database extends EventEmitter {
// extract optional parameters and one or two callbacks
const { args, callback, complete } = popCallback<RowCallback>(params)

const preparedSql = args?.length > 0 ? prepareSql(sql, ...args) : sql
const command: SQLiteCloudCommand = { query: sql, parameters: args?.flat() }
this.getConnection((error, connection) => {
if (error || !connection) {
this.handleError(null, error as Error, callback)
} else {
connection.sendCommands(preparedSql, (error, rowset) => {
connection.sendCommands(command, (error, rowset) => {
if (error) {
this.handleError(connection, error, callback)
} else {
Expand Down Expand Up @@ -352,8 +352,7 @@ export class Database extends EventEmitter {
* they are bound to the prepared statement before calling the callback.
*/
public prepare<T = any>(sql: string, ...params: any[]): Statement<T> {
const { args, callback } = popCallback(params)
return new Statement(this, sql, ...args, callback)
return new Statement(this, sql, ...params)
}

/**
Expand Down Expand Up @@ -444,34 +443,33 @@ export class Database extends EventEmitter {
* @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, ...values: any[]): Promise<any> {
let preparedSql = ''
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) => {
preparedSql += string + (i < values.length ? '?' : '')
// 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 ? '?' : '')
})
preparedSql = prepareSql(preparedSql, ...values)
commands = { query, parameters: values }
} else if (typeof sql === 'string') {
commands = { query: sql, parameters: values }
} else if (typeof sql === 'object') {
commands = sql as SQLiteCloudCommand
} else {
if (typeof sql === 'string') {
if (values?.length > 0) {
preparedSql = prepareSql(sql, ...values)
} else {
preparedSql = sql
}
} else {
throw new Error('Invalid sql')
}
throw new Error('Invalid sql')
}

return new Promise((resolve, reject) => {
this.getConnection((error, connection) => {
if (error || !connection) {
reject(error)
} else {
connection.sendCommands(preparedSql, (error, results) => {
connection.sendCommands(commands, (error, results) => {
if (error) {
reject(error)
} else {
Expand Down
Loading
Loading