Skip to content

Commit

Permalink
feat: docpage credits + @ulixee/client package
Browse files Browse the repository at this point in the history
  • Loading branch information
calebjclark committed Jan 25, 2023
1 parent 0947862 commit 21c0175
Show file tree
Hide file tree
Showing 40 changed files with 1,383 additions and 294 deletions.
120 changes: 120 additions & 0 deletions client/connection-string/index.ts
@@ -0,0 +1,120 @@
import * as url from 'url';
import * as fs from 'fs';

// Parse method copied from https://github.com/brianc/node-postgres
// Copyright (c) 2010-2014 Brian Carlson (brian.m.carlson@gmail.com)
// MIT License

interface IConfig {
host: string;
database: string;
client_encoding?: string;
user?: string;
password?: string;
port?: string;
sslcert?: string;
sslkey?: string;
sslrootcert?: string;
sslmode?: string;
ssl?: boolean | {
cert?: string;
key?: string;
ca?: string;
rejectUnauthorized?: boolean;
};
}

// parses a connection string
export default class ConnectionString {
public static parse(str: string): IConfig {
// unix socket
if (str.charAt(0) === '/') {
const config = str.split(' ');
return { host: config[0], database: config[1] };
}

// url parse expects spaces encoded as %20
const result = url.parse(
/ |%[^a-f0-9]|%[a-f0-9][^a-f0-9]/i.test(str) ? encodeURI(str).replace(/%25(\d\d)/g, '%$1') : str,
true
);
const config = result.query as unknown as IConfig;
for (const k in config) {
if (Array.isArray(config[k])) {
config[k] = config[k][config[k].length - 1];
}
}

const auth = (result.auth || ':').split(':');
config.user = auth[0];
config.password = auth.splice(1).join(':');

config.port = result.port || undefined;
if (result.protocol === 'socket:') {
if (result.pathname) config.host = decodeURI(result.pathname);
config.database = result.query.db as string;
config.client_encoding = result.query.encoding as string;
return config;
}
if (!config.host) {
// Only set the host if there is no equivalent query param.
config.host = result.hostname || '';
}

// If the host is missing it might be a URL-encoded path to a socket.
let pathname = result.pathname;
if (!config.host && pathname && /^%2f/i.test(pathname)) {
const pathnameSplit = pathname.split('/');
config.host = decodeURIComponent(pathnameSplit[0])
pathname = pathnameSplit.splice(1).join('/');
}
// result.pathname is not always guaranteed to have a '/' prefix (e.g. relative urls)
// only strip the slash if it is present.
if (pathname && pathname.charAt(0) === '/') {
pathname = pathname.slice(1) || null
}
config.database = pathname && decodeURI(pathname) || '';

if ((config.ssl as any) === 'true' || (config.ssl as any) === '1') {
config.ssl = true
}

if ((config.ssl as any) === '0') {
config.ssl = false
}

if (config.sslcert || config.sslkey || config.sslrootcert || config.sslmode) {
config.ssl = {}
if (config.sslcert) {
config.ssl.cert = fs.readFileSync(config.sslcert).toString()
}

if (config.sslkey) {
config.ssl.key = fs.readFileSync(config.sslkey).toString()
}

if (config.sslrootcert) {
config.ssl.ca = fs.readFileSync(config.sslrootcert).toString()
}

switch (config.sslmode) {
case 'disable': {
config.ssl = false
break
}
case 'prefer':
case 'require':
case 'verify-ca':
case 'verify-full': {
break
}
case 'no-verify': {
config.ssl.rejectUnauthorized = false
break
}
}
}

return config
}
}
9 changes: 9 additions & 0 deletions client/connection-string/package.json
@@ -0,0 +1,9 @@
{
"name": "@ulixee/client-connection-string",
"version": "2.0.0-alpha.18",
"main": "index.js",
"repository": "git@github.com:ulixee/platform.git",
"license": "MIT",
"scripts": {},
"dependencies": {}
}
21 changes: 21 additions & 0 deletions client/docs/links.yaml
@@ -0,0 +1,21 @@
- title: Overview
items:
- Introduction

- title: API
items:
- Client
- ConnectionParameters

- title: Features
items:
- Connecting
- Queries
- Data Types
- SQL Spec

- title: Help
items:
- Prerequisites
- How to upgrade
- Troubleshooting
93 changes: 93 additions & 0 deletions client/main/index.ts
@@ -0,0 +1,93 @@
import { EventEmitter } from 'events';
import Log from '@ulixee/commons/lib/Logger';
import DatastoreApiClient from '@ulixee/datastore/lib/DatastoreApiClient';
import ConnectionParameters from './lib/ConnectionParameters';

// ulx://paymentHash@ip:port/versionHash

const { log } = Log(module);

interface IConfig {
connectionString?: string;
user?: string;
password?: string;
host?: string;
port?: string | number;
database?: string;
}

export default class Client extends EventEmitter {
private connectionParameters;
private user: string;
private password: string;
private host: string;
private port: string;
private database: string;

#apiClient: DatastoreApiClient;

constructor(config: string | IConfig = {}) {
super();

this.connectionParameters = new ConnectionParameters(config)
this.user = this.connectionParameters.user
this.database = this.connectionParameters.database
this.port = this.connectionParameters.port
this.host = this.connectionParameters.host
this.password = this.connectionParameters.password
}

public get apiClient(): DatastoreApiClient {
if (!this.#apiClient) {
const address = `${this.host}:${this.port}`;
this.#apiClient = new DatastoreApiClient(address);
// const onError = this.onConnectionError.bind(this);
}
return this.#apiClient;
}

public async run(functionOrTableName: string, filter?: Record<string, any>): Promise<any[]> {
return await this.fetch(functionOrTableName, filter);
}

public async fetch(functionOrTableName: string, filter?: Record<string, any>): Promise<any[]> {
const options = {
payment: this.user ? {
credits: {
id: this.user,
secret: this.password,
}
} : undefined,
};

return await this.apiClient.stream(this.database, functionOrTableName, filter, options);
}

public async query<TResultType = any>(
sql: string,
boundValues: any[] = [],
): Promise<TResultType> {
const options = {
boundValues,
payment: this.user ? {
credits: {
id: this.user,
secret: this.password,
}
} : undefined,
};
const response = await this.apiClient.query(this.database, sql, options);

return response.outputs as any;
}

private onConnectionError(error: Error): void {
if (error) {
log.error('Error connecting to core', {
error,
sessionId: null,
});
this.emit('error', error);
}
};
}
67 changes: 67 additions & 0 deletions client/main/lib/ConnectionParameters.ts
@@ -0,0 +1,67 @@
import ConnectionString from '@ulixee/client-connection-string';
import defaults from './defaults';

export default class ConnectionParameters {
user: string;
database: string;
port: number;
host: string;
binary: string;
options: any;
query_timeout: string | number;
connect_timeout: string | number;

constructor(config) {
// if a string is passed, it is a raw connection string so we parse it into a config
config = typeof config === 'string' ? ConnectionString.parse(config) : config || {}

// if the config has a connectionString defined, parse IT into the config we use
// this will override other default values with what is stored in connectionString
if (config.connectionString) {
config = { ...config, ...ConnectionString.parse(config.connectionString) };
}

this.user = val('user', config)
this.database = val('database', config)

if (this.database === undefined) {
this.database = null;
}

this.port = parseInt(val('port', config), 10)
this.host = val('host', config)

// "hiding" the password so it doesn't show up in stack traces
// or if the client is console.logged
Object.defineProperty(this, 'password', {
configurable: true,
enumerable: false,
writable: true,
value: val('password', config),
})

this.binary = val('binary', config)
this.options = val('options', config)

this.query_timeout = val('query_timeout', config, false)

if (config.connectionTimeoutMillis === undefined) {
this.connect_timeout = process.env.ULX_CONNECT_TIMEOUT || 0
} else {
this.connect_timeout = Math.floor(config.connectionTimeoutMillis / 1000)
}
}
}


function val(key: string, config: any, envVar?: string | boolean): string {
if (envVar === undefined) {
envVar = process.env[`ULX_${key.toUpperCase()}`];
} else if (envVar === false) {
// do nothing ... use false
} else {
envVar = process.env[envVar as string]
}

return config[key] || envVar || defaults[key]
}
38 changes: 38 additions & 0 deletions client/main/lib/defaults.ts
@@ -0,0 +1,38 @@
export default {
// database host. defaults to localhost
host: 'localhost',

// database user's name
user: undefined,

// name of database to connect
database: undefined,

// database user's password
password: undefined,

// a Ulixee connection string to be used instead of setting individual connection items
// NOTE: Setting this value will cause it to override any other value (such as database or user) defined
// in the defaults object.
connectionString: undefined,

// database port
port: 1818,

// number of rows to return at a time from a prepared statement's
// portal. 0 will return all rows at once
rows: 0,

options: undefined,

parseInputDatesAsUTC: false,

// max milliseconds any query using this connection will execute for before timing out in error.
// false=unlimited
statement_timeout: false,

// max milliseconds to wait for query to complete (client side)
query_timeout: false,

connect_timeout: 0,
}
23 changes: 23 additions & 0 deletions client/main/package.json
@@ -0,0 +1,23 @@
{
"name": "@ulixee/sql",
"version": "2.0.0-alpha.18",
"main": "index.js",
"repository": "git@github.com:ulixee/platform.git",
"license": "MIT",
"scripts": {},
"dependencies": {
"@ulixee/client-connection-string": "2.0.0-alpha.18",
"@ulixee/net": "2.0.0-alpha.18",
"@ulixee/datastore": "2.0.0-alpha.18",
"@ulixee/commons": "2.0.0-alpha.18"
},
"devDependencies": {
"@ulixee/schema": "2.0.0-alpha.18",
"@ulixee/sql-ast": "2.0.0-alpha.18",
"@ulixee/sql-engine": "2.0.0-alpha.18",
"@ulixee/specification": "2.0.0-alpha.18",
"@ulixee/datastore-packager": "2.0.0-alpha.18",
"@ulixee/miner": "2.0.0-alpha.18",
"@ulixee/crypto": "2.0.0-alpha.18"
}
}

0 comments on commit 21c0175

Please sign in to comment.