-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2838977
commit 23635fb
Showing
11 changed files
with
394 additions
and
100 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
Refactor Summary | ||
- Added SQLiteCloudClient class and createClient function | ||
- Extracted PubSub from Database to SQLiteCloudClient | ||
- Added fetch and fetchWithAuth | ||
- Added Weblite endpoint methods for upload, download, delete, and listDatabases | ||
- Refactored PubSub to be more intuitive and easier to use | ||
- Added FileClient class and methods for file upload and download | ||
|
||
TODO: Polish code, add error handling, Write tests |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
|
||
/** | ||
* Developer experience - current | ||
* | ||
*/ | ||
|
||
import { Database } from '@sqlitecloud/drivers' | ||
import { PUBSUB_ENTITY_TYPE } from '@sqlitecloud/drivers/lib/drivers/pubsub' // forces user to import pubsub constants from hard to remember location | ||
|
||
const db = new Database('connection-string') | ||
const pubSub = await db.getPubSub() // couples database to pubsub | ||
|
||
/* Database methods */ | ||
await db.sql`SELECT * FROM users` | ||
db.exec('command') | ||
db.run('command') | ||
db.all('command') | ||
db.each('command') | ||
db.close() | ||
|
||
/* PubSub usage */ | ||
/** Listen to a table */ | ||
pubSub.listen(PUBSUB_ENTITY_TYPE.TABLE, 'users', (error, results, data) => { // note extraneous "data" | ||
console.log(error, results, data) | ||
}, ['extra data']) | ||
|
||
/** Listen to a channel */ | ||
pubSub.listen(PUBSUB_ENTITY_TYPE.CHANNEL, 'messages', (error, results, data) => { | ||
console.log(error, results, data) | ||
}, ['extra data']) | ||
|
||
/** Create a channel */ | ||
pubSub.createChannel('messages') | ||
|
||
/** Unlisten to a table */ | ||
pubSub.unlisten(PUBSUB_ENTITY_TYPE.TABLE, 'users') | ||
|
||
/** Remove a channel (not currently exposed) */ | ||
// @ts-ignore | ||
pubSub.removeChannel('messages') | ||
|
||
/** Notify a channel */ | ||
pubSub.notifyChannel('messages', 'my message') | ||
|
||
|
||
/** | ||
* Developer experience - refactored | ||
* In the refactor, Database still exists and works as before. | ||
*/ | ||
|
||
import { createClient } from './src/refactor/SQLiteCloudClient' | ||
|
||
const client = createClient('connection-string/chinook.db') | ||
|
||
// Promise sql query | ||
const { data, error } = await client.sql`SELECT * FROM albums`; | ||
|
||
client.defaultDb = 'users'; // helper to set default database for SQL queries | ||
|
||
const { data: sessions, error: sessionsError } = await client.sql`SELECT * FROM sessions`; | ||
// or | ||
const result = client.db.exec('SELECT * FROM sessions') | ||
|
||
// Weblite | ||
// upload database | ||
const uploadDatabaseResponse = await client.weblite.upload('new_chinook.db', new File([''], 'new_chinook.db'), { replace: false }); | ||
|
||
// download database | ||
const downloadDatabaseResponse = await client.weblite.download('new_chinook.db'); | ||
|
||
// delete database | ||
const deleteDatabaseResponse = await client.weblite.delete('new_chinook.db'); | ||
|
||
// list databases | ||
const listDatabasesResponse = await client.weblite.listDatabases(); | ||
|
||
// create database | ||
const createDatabaseResponse = await client.weblite.create('new_chinook.db'); | ||
|
||
// SQLiteCloudFileClient | ||
const createBucketResponse = await client.files.createBucket('myBucket'); | ||
const getBucketResponse = await client.files.getBucket('myBucket'); | ||
const deleteBucketResponse = await client.files.deleteBucket('myBucket'); | ||
const listBucketsResponse = await client.files.listBuckets(); | ||
|
||
// upload file | ||
const uploadFileResponse = await client.files.upload('myBucket', 'myPath', new File([''], 'myFile.txt'), { contentType: 'text/plain' }); | ||
|
||
// download file | ||
const downloadFileResponse = await client.files.download('myBucket', 'myPath'); | ||
|
||
// remove file | ||
const removeFileResponse = await client.files.remove('myBucket', 'myPath'); | ||
|
||
|
||
// SQLiteCloudPubSubClient Refactor | ||
await client.pubSub.create('messages') | ||
await client.pubSub.notify('messages', 'my message') | ||
await client.pubSub.subscribe('messages', (error, results) => { | ||
console.log(error, results) | ||
}) | ||
client.pubSub.unsubscribe('messages') | ||
await client.pubSub.delete('messages') | ||
|
||
await client.pubSub.listen({ tableName: 'users' }, (error, results) => { | ||
console.log(error, results) | ||
}) | ||
|
||
await client.pubSub.listen({ tableName: 'users', dbName: 'chinook.sqlite' }, (error, results) => { // note optional dbName | ||
console.log(error, results) | ||
}) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { Database } from '../drivers/database' | ||
import { Fetch, fetchWithAuth } from './fetch' | ||
import { SQLiteCloudPubSubClient } from './SQLiteCloudPubSubClient' | ||
import { SQLiteCloudWebliteClient } from './SQLiteCloudWebliteClient' | ||
import { SQLiteCloudFileClient } from './SQLiteCloudFileClient' | ||
import { SQLiteCloudCommand } from '../drivers/types' | ||
import { getDefaultDatabase } from './utils' | ||
|
||
interface SQLiteCloudClientConfig { | ||
connectionString: string | ||
fetch?: Fetch | ||
} | ||
|
||
|
||
|
||
export class SQLiteCloudClient { | ||
private connectionString: string | ||
private fetch: Fetch | ||
|
||
constructor(config: SQLiteCloudClientConfig | string) { | ||
let connectionString: string | ||
let customFetch: Fetch | undefined | ||
|
||
if (typeof config === 'string') { | ||
connectionString = config | ||
} else { | ||
connectionString = config.connectionString | ||
customFetch = config.fetch | ||
} | ||
|
||
this.connectionString = connectionString | ||
this.fetch = fetchWithAuth(this.connectionString, customFetch) | ||
this.defaultDb = getDefaultDatabase(this.connectionString) ?? '' | ||
} | ||
|
||
async sql(sql: TemplateStringsArray | string | SQLiteCloudCommand, ...values: any[]) { | ||
this.db.exec(`USE DATABASE ${this.defaultDb}`) | ||
try { | ||
const result = await this.db.sql(sql, ...values) | ||
return { data: result, error: null } | ||
} catch (error) { | ||
return { error, data: null } | ||
} | ||
} | ||
|
||
get pubSub() { | ||
return new SQLiteCloudPubSubClient(this.db) | ||
} | ||
|
||
get db() { | ||
return new Database(this.connectionString) | ||
} | ||
|
||
get weblite() { | ||
return new SQLiteCloudWebliteClient(this.connectionString, this.fetch) | ||
} | ||
|
||
get files() { | ||
return new SQLiteCloudFileClient(this.connectionString, this.fetch) | ||
} | ||
|
||
set defaultDb(dbName: string) { | ||
this.defaultDb = dbName | ||
} | ||
} | ||
|
||
export function createClient(config: SQLiteCloudClientConfig | string): SQLiteCloudClient { | ||
return new SQLiteCloudClient(config) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { SQLiteCloudError } from "../drivers/types" | ||
import { getAPIUrl } from "./utils" | ||
import { Fetch, fetchWithAuth } from "./fetch" | ||
|
||
interface SQLiteCloudFile { | ||
createBucket(bucket: string, path: string): Promise<Response> | ||
getBucket(bucket: string): Promise<any> | ||
deleteBucket(bucket: string): Promise<Response> | ||
listBuckets(): Promise<any> | ||
upload(bucket: string, pathname: string, file: File | Buffer | Blob | string, options: { contentType: string }): Promise<Response> | ||
download(bucket: string, pathname: string): Promise<Blob> | ||
remove(bucket: string, pathName: string): Promise<Response> | ||
list(bucket: string): Promise<any> | ||
} | ||
|
||
const FILES_DATABASE = 'files.sqlite' | ||
|
||
export class SQLiteCloudFileClient implements SQLiteCloudFile { | ||
private filesUrl: string | ||
private webliteSQLUrl: string | ||
private fetch: Fetch | ||
|
||
constructor(connectionString: string, sql?: Fetch) { | ||
this.filesUrl = getAPIUrl(connectionString, 'files') | ||
this.webliteSQLUrl = getAPIUrl(connectionString, 'weblite/sql') | ||
this.fetch = fetchWithAuth(connectionString, fetch) | ||
} | ||
|
||
async createBucket(bucket: string) { | ||
const url = `${this.webliteSQLUrl}?sql=USE DATABASE files; INSERT INTO files (Bucket) VALUES ('${bucket}');` | ||
const response = await this.fetch(url, { method: 'POST' }) | ||
if (!response.ok) { | ||
throw new SQLiteCloudError(`Failed to create bucket: ${response.statusText}`) | ||
} | ||
return response.json() | ||
} | ||
|
||
async getBucket(bucket: string) { | ||
const url = `${this.filesUrl}/${bucket}` | ||
const response = await this.fetch(url, { method: 'GET' }) | ||
if (!response.ok) { | ||
throw new SQLiteCloudError(`Failed to get bucket: ${response.statusText}`) | ||
} | ||
|
||
return response.json() | ||
} | ||
|
||
async deleteBucket(bucket: string) { | ||
const url = `${this.filesUrl}/${bucket}` | ||
const response = await this.fetch(url, { method: 'DELETE' }) | ||
if (!response.ok) { | ||
throw new SQLiteCloudError(`Failed to delete bucket: ${response.statusText}`) | ||
} | ||
return response.json() | ||
} | ||
|
||
async listBuckets() { | ||
const encodedUrl = encodeURIComponent(`${this.webliteSQLUrl}?sql=USE DATABASE files.sqlite; SELECT * FROM files`) | ||
const response = await this.fetch(encodedUrl) | ||
if (!response.ok) { | ||
throw new SQLiteCloudError(`Failed to list buckets: ${response.statusText}`) | ||
} | ||
return response.json() | ||
} | ||
|
||
async upload(bucket: string, pathname: string, file: File | Buffer | Blob | string, options: { contentType: string }) { | ||
const url = `${this.filesUrl}/${bucket}/${pathname}`; | ||
const headers = { | ||
'Content-Type': options?.contentType || 'application/octet-stream' | ||
} | ||
const response = await this.fetch(url, { method: 'POST', body: file, headers }) | ||
if (!response.ok) { | ||
throw new SQLiteCloudError(`Failed to upload file: ${response.statusText}`) | ||
} | ||
return response.json() | ||
} | ||
|
||
async download(bucket: string, pathname: string) { | ||
const url = `${this.filesUrl}/${bucket}/${pathname}`; | ||
const response = await this.fetch(url, { method: 'GET' }) | ||
if (!response.ok) { | ||
throw new SQLiteCloudError(`Failed to download file: ${response.statusText}`) | ||
} | ||
return response.blob() | ||
} | ||
|
||
async remove(bucket: string, pathName: string) { | ||
const url = `${this.filesUrl}/${bucket}/${pathName}` | ||
const response = await this.fetch(url, { method: 'DELETE' }) | ||
if (!response.ok) { | ||
throw new SQLiteCloudError(`Failed to remove file: ${response.statusText}`) | ||
} | ||
return response.json() | ||
} | ||
|
||
async list(bucket: string) { | ||
const encodedUrl = encodeURIComponent(`${this.webliteSQLUrl}?sql=USE DATABASE files.sqlite; SELECT * FROM files WHERE bucket = '${bucket}'`) | ||
const response = await this.fetch(encodedUrl) | ||
if (!response.ok) { | ||
throw new SQLiteCloudError(`Failed to list files: ${response.statusText}`) | ||
} | ||
return response.json() | ||
} | ||
} | ||
|
Oops, something went wrong.