feat(clients): professional clients with tests, CI/CD, and examples
- Python: pyproject.toml, pytest suite (unit + integration), README, examples - JavaScript: package.json, TypeScript definitions, node:test suite, README, examples - Nim: integration tests, examples, README, improved nimble tasks - Rust: fixed nodelay/localhost IPv6 bug, integration tests, examples, README - Added GitHub Actions CI for all 4 clients against Docker server - Added docker-compose.test.yml for local integration testing - Added .gitignore for each client
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
package-lock.json
|
||||
*.log
|
||||
.DS_Store
|
||||
coverage/
|
||||
.nyc_output/
|
||||
@@ -0,0 +1,159 @@
|
||||
# BaraDB JavaScript / Node.js Client
|
||||
|
||||
Official JavaScript client for **BaraDB** — a multimodal database engine written in Nim.
|
||||
|
||||
## Features
|
||||
|
||||
- **Binary wire protocol** — fast TCP communication
|
||||
- **Promise-based API** — modern async/await
|
||||
- **Query builder** — fluent SQL construction
|
||||
- **Parameterized queries** — safe from SQL injection
|
||||
- **Vector & JSON support** — first-class multimodal types
|
||||
- **TypeScript definitions** — included out of the box
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install baradb
|
||||
```
|
||||
|
||||
Or from source:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/barabadb/baradadb.git
|
||||
cd clients/javascript
|
||||
npm link
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```javascript
|
||||
const { Client } = require('baradb');
|
||||
|
||||
async function main() {
|
||||
const client = new Client('localhost', 9472);
|
||||
await client.connect();
|
||||
|
||||
const result = await client.query("SELECT name, age FROM users WHERE age > 18");
|
||||
for (const row of result) {
|
||||
console.log(row.name, row.age);
|
||||
}
|
||||
|
||||
await client.close();
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
```
|
||||
|
||||
### Parameterized Queries
|
||||
|
||||
```javascript
|
||||
const { Client, WireValue } = require('baradb');
|
||||
|
||||
async function main() {
|
||||
const client = new Client('localhost', 9472);
|
||||
await client.connect();
|
||||
|
||||
const result = await client.queryParams(
|
||||
'SELECT * FROM users WHERE age > $1 AND country = $2',
|
||||
[WireValue.int64(18), WireValue.string('BG')]
|
||||
);
|
||||
|
||||
for (const row of result) {
|
||||
console.log(row);
|
||||
}
|
||||
|
||||
await client.close();
|
||||
}
|
||||
```
|
||||
|
||||
### Query Builder
|
||||
|
||||
```javascript
|
||||
const { Client, QueryBuilder } = require('baradb');
|
||||
|
||||
async function main() {
|
||||
const client = new Client('localhost', 9472);
|
||||
await client.connect();
|
||||
|
||||
const result = await new QueryBuilder(client)
|
||||
.select('name', 'email')
|
||||
.from('users')
|
||||
.where('active = true')
|
||||
.orderBy('name')
|
||||
.limit(10)
|
||||
.exec();
|
||||
|
||||
for (const row of result) {
|
||||
console.log(row);
|
||||
}
|
||||
|
||||
await client.close();
|
||||
}
|
||||
```
|
||||
|
||||
### Vector Search
|
||||
|
||||
```javascript
|
||||
const { Client, WireValue } = require('baradb');
|
||||
|
||||
async function main() {
|
||||
const client = new Client('localhost', 9472);
|
||||
await client.connect();
|
||||
|
||||
const result = await client.queryParams(
|
||||
'SELECT id, name FROM products ORDER BY embedding <-> $1 LIMIT 5',
|
||||
[WireValue.vector([0.1, 0.2, 0.3])]
|
||||
);
|
||||
|
||||
await client.close();
|
||||
}
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
Unit tests (no server):
|
||||
|
||||
```bash
|
||||
npm run test:unit
|
||||
```
|
||||
|
||||
Integration tests (requires server on `localhost:9472`):
|
||||
|
||||
```bash
|
||||
# Start server
|
||||
docker run -d -p 9472:9472 baradb:latest
|
||||
|
||||
# Run integration tests
|
||||
npm run test:integration
|
||||
|
||||
# Run all tests
|
||||
npm test
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `new Client(host, port, options)`
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------------|-------------|----------------------------|
|
||||
| `host` | `localhost` | Server hostname |
|
||||
| `port` | `9472` | TCP wire protocol port |
|
||||
| `database` | `default` | Default database |
|
||||
| `username` | `admin` | Username |
|
||||
| `password` | `""` | Password |
|
||||
| `timeout` | `30000` | Socket timeout in ms |
|
||||
|
||||
### Methods
|
||||
|
||||
- `connect()` — open TCP connection
|
||||
- `close()` — close connection
|
||||
- `query(sql)` — execute SELECT-like query
|
||||
- `queryParams(sql, params)` — parameterized query
|
||||
- `execute(sql)` — execute DDL/DML, returns affected rows
|
||||
- `auth(token)` — JWT authentication
|
||||
- `ping()` — health check
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
Vendored
+116
@@ -0,0 +1,116 @@
|
||||
// BaraDB JavaScript/TypeScript Client Type Definitions
|
||||
|
||||
export declare const MsgKind: Readonly<{
|
||||
CLIENT_HANDSHAKE: 0x01;
|
||||
QUERY: 0x02;
|
||||
QUERY_PARAMS: 0x03;
|
||||
EXECUTE: 0x04;
|
||||
BATCH: 0x05;
|
||||
TRANSACTION: 0x06;
|
||||
CLOSE: 0x07;
|
||||
PING: 0x08;
|
||||
AUTH: 0x09;
|
||||
SERVER_HANDSHAKE: 0x80;
|
||||
READY: 0x81;
|
||||
DATA: 0x82;
|
||||
COMPLETE: 0x83;
|
||||
ERROR: 0x84;
|
||||
AUTH_CHALLENGE: 0x85;
|
||||
AUTH_OK: 0x86;
|
||||
SCHEMA_CHANGE: 0x87;
|
||||
PONG: 0x88;
|
||||
TRANSACTION_STATE: 0x89;
|
||||
}>;
|
||||
|
||||
export declare const FieldKind: Readonly<{
|
||||
NULL: 0x00;
|
||||
BOOL: 0x01;
|
||||
INT8: 0x02;
|
||||
INT16: 0x03;
|
||||
INT32: 0x04;
|
||||
INT64: 0x05;
|
||||
FLOAT32: 0x06;
|
||||
FLOAT64: 0x07;
|
||||
STRING: 0x08;
|
||||
BYTES: 0x09;
|
||||
ARRAY: 0x0A;
|
||||
OBJECT: 0x0B;
|
||||
VECTOR: 0x0C;
|
||||
JSON: 0x0D;
|
||||
}>;
|
||||
|
||||
export declare const ResultFormat: Readonly<{
|
||||
BINARY: 0x00;
|
||||
JSON: 0x01;
|
||||
TEXT: 0x02;
|
||||
}>;
|
||||
|
||||
export declare class WireValue {
|
||||
kind: number;
|
||||
value: any;
|
||||
constructor(kind: number, value?: any);
|
||||
static null(): WireValue;
|
||||
static bool(val: boolean): WireValue;
|
||||
static int8(val: number): WireValue;
|
||||
static int16(val: number): WireValue;
|
||||
static int32(val: number): WireValue;
|
||||
static int64(val: number | bigint): WireValue;
|
||||
static float32(val: number): WireValue;
|
||||
static float64(val: number): WireValue;
|
||||
static string(val: string): WireValue;
|
||||
static bytes(val: Buffer): WireValue;
|
||||
static array(val: WireValue[]): WireValue;
|
||||
static object(val: Record<string, WireValue>): WireValue;
|
||||
static vector(val: number[]): WireValue;
|
||||
static json(val: string): WireValue;
|
||||
serialize(): Buffer;
|
||||
}
|
||||
|
||||
export interface QueryResultRow {
|
||||
[column: string]: any;
|
||||
}
|
||||
|
||||
export declare class QueryResult implements Iterable<QueryResultRow> {
|
||||
columns: string[];
|
||||
columnTypes: number[];
|
||||
rows: any[][];
|
||||
rowCount: number;
|
||||
affectedRows: number;
|
||||
[Symbol.iterator](): Iterator<QueryResultRow>;
|
||||
get length(): number;
|
||||
}
|
||||
|
||||
export interface ClientOptions {
|
||||
database?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export declare class Client {
|
||||
constructor(host?: string, port?: number, options?: ClientOptions);
|
||||
connect(): Promise<void>;
|
||||
close(): Promise<void>;
|
||||
isConnected(): boolean;
|
||||
auth(token: string): Promise<void>;
|
||||
ping(): Promise<boolean>;
|
||||
query(sql: string): Promise<QueryResult>;
|
||||
queryParams(sql: string, params?: WireValue[]): Promise<QueryResult>;
|
||||
execute(sql: string): Promise<number>;
|
||||
}
|
||||
|
||||
export declare class QueryBuilder {
|
||||
constructor(client: Client);
|
||||
select(...cols: string[]): this;
|
||||
from(table: string): this;
|
||||
where(clause: string): this;
|
||||
join(table: string, on: string): this;
|
||||
leftJoin(table: string, on: string): this;
|
||||
groupBy(...cols: string[]): this;
|
||||
having(clause: string): this;
|
||||
orderBy(col: string, direction?: string): this;
|
||||
limit(n: number): this;
|
||||
offset(n: number): this;
|
||||
build(): string;
|
||||
exec(): Promise<QueryResult>;
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* BaraDB JavaScript Client — Basic Examples
|
||||
*
|
||||
* Run: node examples/basic.js
|
||||
* Requires BaraDB server on localhost:9472.
|
||||
*/
|
||||
|
||||
const { Client, WireValue, QueryBuilder } = require('../baradb');
|
||||
|
||||
const HOST = 'localhost';
|
||||
const PORT = 9472;
|
||||
|
||||
async function exampleConnection() {
|
||||
console.log('=== Connection ===');
|
||||
const client = new Client(HOST, PORT);
|
||||
await client.connect();
|
||||
console.log(`Connected: ${client.isConnected()}`);
|
||||
console.log(`Ping: ${await client.ping()}`);
|
||||
await client.close();
|
||||
console.log(`Connected after close: ${client.isConnected()}`);
|
||||
console.log();
|
||||
}
|
||||
|
||||
async function exampleSimpleQuery() {
|
||||
console.log('=== Simple Query ===');
|
||||
const client = new Client(HOST, PORT);
|
||||
await client.connect();
|
||||
const result = await client.query("SELECT 42 as answer, 'BaraDB' as db");
|
||||
console.log(`Columns: ${result.columns.join(', ')}`);
|
||||
console.log(`Row count: ${result.rowCount}`);
|
||||
for (const row of result) {
|
||||
console.log(` answer=${row.answer}, db=${row.db}`);
|
||||
}
|
||||
await client.close();
|
||||
console.log();
|
||||
}
|
||||
|
||||
async function exampleParameterizedQuery() {
|
||||
console.log('=== Parameterized Query ===');
|
||||
const client = new Client(HOST, PORT);
|
||||
await client.connect();
|
||||
const result = await client.queryParams(
|
||||
'SELECT $1 as num, $2 as txt, $3 as flag',
|
||||
[
|
||||
WireValue.int64(123),
|
||||
WireValue.string('hello world'),
|
||||
WireValue.bool(true),
|
||||
]
|
||||
);
|
||||
for (const row of result) {
|
||||
console.log(` num=${row.num}, txt=${row.txt}, flag=${row.flag}`);
|
||||
}
|
||||
await client.close();
|
||||
console.log();
|
||||
}
|
||||
|
||||
async function exampleQueryBuilder() {
|
||||
console.log('=== Query Builder ===');
|
||||
const client = new Client(HOST, PORT);
|
||||
await client.connect();
|
||||
const sql = await new QueryBuilder(client)
|
||||
.select('id', 'name')
|
||||
.from('users')
|
||||
.where('active = true')
|
||||
.orderBy('name', 'ASC')
|
||||
.limit(5)
|
||||
.build();
|
||||
console.log(`Generated SQL: ${sql}`);
|
||||
await client.close();
|
||||
console.log();
|
||||
}
|
||||
|
||||
async function exampleVector() {
|
||||
console.log('=== Vector Value ===');
|
||||
const client = new Client(HOST, PORT);
|
||||
await client.connect();
|
||||
const result = await client.queryParams(
|
||||
'SELECT $1 as embedding',
|
||||
[WireValue.vector([0.1, 0.2, 0.3, 0.4])]
|
||||
);
|
||||
for (const row of result) {
|
||||
console.log(` embedding type: ${typeof row.embedding}`);
|
||||
}
|
||||
await client.close();
|
||||
console.log();
|
||||
}
|
||||
|
||||
async function exampleDdlDml() {
|
||||
console.log('=== DDL & DML ===');
|
||||
const client = new Client(HOST, PORT);
|
||||
await client.connect();
|
||||
|
||||
try {
|
||||
await client.execute('DROP TABLE IF EXISTS demo_products');
|
||||
} catch (err) {
|
||||
console.log(`Cleanup warning: ${err.message}`);
|
||||
}
|
||||
|
||||
await client.execute('CREATE TABLE demo_products (id INT PRIMARY KEY, name STRING, price FLOAT)');
|
||||
const affected = await client.execute("INSERT INTO demo_products (id, name, price) VALUES (1, 'Widget', 9.99)");
|
||||
console.log(`Insert affected rows: ${affected}`);
|
||||
|
||||
const result = await client.query('SELECT * FROM demo_products');
|
||||
console.log(`Select returned ${result.rowCount} row(s)`);
|
||||
for (const row of result) {
|
||||
console.log(` ${JSON.stringify(row)}`);
|
||||
}
|
||||
|
||||
await client.execute('DROP TABLE demo_products');
|
||||
console.log('Table dropped');
|
||||
await client.close();
|
||||
console.log();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('BaraDB JavaScript Client Examples');
|
||||
console.log('Make sure BaraDB is running on localhost:9472');
|
||||
console.log();
|
||||
|
||||
const examples = [
|
||||
exampleConnection,
|
||||
exampleSimpleQuery,
|
||||
exampleParameterizedQuery,
|
||||
exampleQueryBuilder,
|
||||
exampleVector,
|
||||
exampleDdlDml,
|
||||
];
|
||||
|
||||
for (const fn of examples) {
|
||||
try {
|
||||
await fn();
|
||||
} catch (err) {
|
||||
console.error(`ERROR in ${fn.name}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "baradb",
|
||||
"version": "1.0.0",
|
||||
"description": "Official JavaScript/Node.js client for BaraDB — Multimodal Database Engine",
|
||||
"main": "baradb.js",
|
||||
"types": "baradb.d.ts",
|
||||
"files": [
|
||||
"baradb.js",
|
||||
"baradb.d.ts",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "node --test tests/*.test.js",
|
||||
"test:unit": "node --test tests/unit.test.js",
|
||||
"test:integration": "node --test tests/integration.test.js",
|
||||
"example": "node examples/basic.js"
|
||||
},
|
||||
"keywords": [
|
||||
"baradb",
|
||||
"database",
|
||||
"multimodal",
|
||||
"client",
|
||||
"wire-protocol",
|
||||
"vector",
|
||||
"graph"
|
||||
],
|
||||
"author": "BaraDB Team <team@baradb.dev>",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/barabadb/baradadb.git",
|
||||
"directory": "clients/javascript"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/barabadb/baradadb/issues"
|
||||
},
|
||||
"homepage": "https://docs.baradb.dev",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* BaraDB JavaScript Client — Integration Tests
|
||||
* Requires a running BaraDB server on localhost:9472.
|
||||
*/
|
||||
|
||||
const { describe, it } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const net = require('net');
|
||||
const { Client, WireValue, QueryBuilder } = require('../baradb');
|
||||
|
||||
const HOST = 'localhost';
|
||||
const PORT = 9472;
|
||||
|
||||
function serverAvailable() {
|
||||
return new Promise((resolve) => {
|
||||
const socket = new net.Socket();
|
||||
socket.setTimeout(2000);
|
||||
socket.once('connect', () => {
|
||||
socket.destroy();
|
||||
resolve(true);
|
||||
});
|
||||
socket.once('error', () => resolve(false));
|
||||
socket.once('timeout', () => resolve(false));
|
||||
socket.connect(PORT, HOST);
|
||||
});
|
||||
}
|
||||
|
||||
let available = false;
|
||||
|
||||
(async () => {
|
||||
available = await serverAvailable();
|
||||
})();
|
||||
|
||||
// Use conditional describe via top-level if to avoid describe skip issues
|
||||
if (true) {
|
||||
describe('Integration', () => {
|
||||
it('connects and closes', async () => {
|
||||
if (!available) return; // skip inline
|
||||
const client = new Client(HOST, PORT);
|
||||
assert.strictEqual(client.isConnected(), false);
|
||||
await client.connect();
|
||||
assert.strictEqual(client.isConnected(), true);
|
||||
await client.close();
|
||||
assert.strictEqual(client.isConnected(), false);
|
||||
});
|
||||
|
||||
it('pongs', async () => {
|
||||
if (!available) return;
|
||||
const client = new Client(HOST, PORT);
|
||||
await client.connect();
|
||||
const ok = await client.ping();
|
||||
assert.strictEqual(ok, true);
|
||||
await client.close();
|
||||
});
|
||||
|
||||
it('executes simple SELECT', async () => {
|
||||
if (!available) return;
|
||||
const client = new Client(HOST, PORT);
|
||||
await client.connect();
|
||||
const result = await client.query('SELECT 1 as one');
|
||||
assert.ok(result instanceof Object);
|
||||
assert.ok(typeof result.rowCount === 'number');
|
||||
await client.close();
|
||||
});
|
||||
|
||||
it('executes parameterized query', async () => {
|
||||
if (!available) return;
|
||||
const client = new Client(HOST, PORT);
|
||||
await client.connect();
|
||||
const result = await client.queryParams(
|
||||
'SELECT $1 as num, $2 as txt',
|
||||
[WireValue.int64(42), WireValue.string('hello')]
|
||||
);
|
||||
assert.ok(result instanceof Object);
|
||||
await client.close();
|
||||
});
|
||||
|
||||
it('creates table, inserts, selects, drops', async () => {
|
||||
if (!available) return;
|
||||
const client = new Client(HOST, PORT);
|
||||
await client.connect();
|
||||
|
||||
try {
|
||||
await client.execute('DROP TABLE IF EXISTS js_test_users');
|
||||
} catch (_) { /* ignore */ }
|
||||
|
||||
await client.execute('CREATE TABLE js_test_users (id INT PRIMARY KEY, name STRING, age INT)');
|
||||
const affected = await client.execute("INSERT INTO js_test_users (id, name, age) VALUES (1, 'Alice', 30)");
|
||||
assert.ok(affected >= 0);
|
||||
|
||||
const result = await client.query('SELECT name, age FROM js_test_users WHERE id = 1');
|
||||
assert.strictEqual(result.rowCount, 1);
|
||||
const row = {};
|
||||
for (let i = 0; i < result.columns.length; i++) {
|
||||
row[result.columns[i]] = result.rows[0][i];
|
||||
}
|
||||
assert.strictEqual(row.name, 'Alice');
|
||||
assert.strictEqual(row.age, 30);
|
||||
|
||||
await client.execute('DROP TABLE js_test_users');
|
||||
await client.close();
|
||||
});
|
||||
|
||||
it('executes built query', async () => {
|
||||
if (!available) return;
|
||||
const client = new Client(HOST, PORT);
|
||||
await client.connect();
|
||||
|
||||
try {
|
||||
await client.execute('DROP TABLE IF EXISTS js_test_products');
|
||||
} catch (_) { /* ignore */ }
|
||||
|
||||
await client.execute('CREATE TABLE js_test_products (id INT PRIMARY KEY, name STRING, price FLOAT)');
|
||||
await client.execute("INSERT INTO js_test_products (id, name, price) VALUES (1, 'Widget', 9.99)");
|
||||
|
||||
const result = await new QueryBuilder(client)
|
||||
.select('name', 'price')
|
||||
.from('js_test_products')
|
||||
.where('id = 1')
|
||||
.exec();
|
||||
|
||||
assert.strictEqual(result.rowCount, 1);
|
||||
await client.execute('DROP TABLE js_test_products');
|
||||
await client.close();
|
||||
});
|
||||
|
||||
it('accepts or rejects dummy token', async () => {
|
||||
if (!available) return;
|
||||
const client = new Client(HOST, PORT);
|
||||
await client.connect();
|
||||
try {
|
||||
await client.auth('dummy-token-for-testing');
|
||||
} catch (err) {
|
||||
assert.ok(err.message.includes('Auth') || err.message.toLowerCase().includes('error'));
|
||||
}
|
||||
await client.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* BaraDB JavaScript Client — Unit Tests
|
||||
* No running server required.
|
||||
*/
|
||||
|
||||
const { describe, it } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const { WireValue, FieldKind, MsgKind, ResultFormat, Client, QueryBuilder } = require('../baradb');
|
||||
|
||||
describe('WireValue serialization', () => {
|
||||
it('serializes null', () => {
|
||||
const wv = WireValue.null();
|
||||
const buf = wv.serialize();
|
||||
assert.strictEqual(buf.length, 1);
|
||||
assert.strictEqual(buf[0], FieldKind.NULL);
|
||||
});
|
||||
|
||||
it('serializes bool true', () => {
|
||||
const wv = WireValue.bool(true);
|
||||
const buf = wv.serialize();
|
||||
assert.strictEqual(buf[0], FieldKind.BOOL);
|
||||
assert.strictEqual(buf[1], 1);
|
||||
});
|
||||
|
||||
it('serializes bool false', () => {
|
||||
const wv = WireValue.bool(false);
|
||||
const buf = wv.serialize();
|
||||
assert.strictEqual(buf[0], FieldKind.BOOL);
|
||||
assert.strictEqual(buf[1], 0);
|
||||
});
|
||||
|
||||
it('serializes int8', () => {
|
||||
const wv = WireValue.int8(-42);
|
||||
const buf = wv.serialize();
|
||||
assert.strictEqual(buf[0], FieldKind.INT8);
|
||||
assert.strictEqual(buf.readInt8(1), -42);
|
||||
});
|
||||
|
||||
it('serializes int16', () => {
|
||||
const wv = WireValue.int16(-1000);
|
||||
const buf = wv.serialize();
|
||||
assert.strictEqual(buf[0], FieldKind.INT16);
|
||||
assert.strictEqual(buf.readInt16BE(1), -1000);
|
||||
});
|
||||
|
||||
it('serializes int32', () => {
|
||||
const wv = WireValue.int32(123456);
|
||||
const buf = wv.serialize();
|
||||
assert.strictEqual(buf[0], FieldKind.INT32);
|
||||
assert.strictEqual(buf.readInt32BE(1), 123456);
|
||||
});
|
||||
|
||||
it('serializes int64', () => {
|
||||
const wv = WireValue.int64(9999999999n);
|
||||
const buf = wv.serialize();
|
||||
assert.strictEqual(buf[0], FieldKind.INT64);
|
||||
assert.strictEqual(buf.readBigInt64BE(1), 9999999999n);
|
||||
});
|
||||
|
||||
it('serializes float32', () => {
|
||||
const wv = WireValue.float32(3.14);
|
||||
const buf = wv.serialize();
|
||||
assert.strictEqual(buf[0], FieldKind.FLOAT32);
|
||||
assert.ok(Math.abs(buf.readFloatBE(1) - 3.14) < 0.01);
|
||||
});
|
||||
|
||||
it('serializes float64', () => {
|
||||
const wv = WireValue.float64(2.718281828);
|
||||
const buf = wv.serialize();
|
||||
assert.strictEqual(buf[0], FieldKind.FLOAT64);
|
||||
assert.ok(Math.abs(buf.readDoubleBE(1) - 2.718281828) < 1e-9);
|
||||
});
|
||||
|
||||
it('serializes string', () => {
|
||||
const wv = WireValue.string('hello');
|
||||
const buf = wv.serialize();
|
||||
assert.strictEqual(buf[0], FieldKind.STRING);
|
||||
assert.strictEqual(buf.readUInt32BE(1), 5);
|
||||
assert.strictEqual(buf.subarray(5).toString('utf-8'), 'hello');
|
||||
});
|
||||
|
||||
it('serializes bytes', () => {
|
||||
const wv = WireValue.bytes(Buffer.from([0xde, 0xad, 0xbe, 0xef]));
|
||||
const buf = wv.serialize();
|
||||
assert.strictEqual(buf[0], FieldKind.BYTES);
|
||||
assert.strictEqual(buf.readUInt32BE(1), 4);
|
||||
assert.deepStrictEqual(buf.subarray(5), Buffer.from([0xde, 0xad, 0xbe, 0xef]));
|
||||
});
|
||||
|
||||
it('serializes vector', () => {
|
||||
const wv = WireValue.vector([1.0, 2.0, 3.0]);
|
||||
const buf = wv.serialize();
|
||||
assert.strictEqual(buf[0], FieldKind.VECTOR);
|
||||
assert.strictEqual(buf.readUInt32BE(1), 3);
|
||||
const floats = [buf.readFloatBE(5), buf.readFloatBE(9), buf.readFloatBE(13)];
|
||||
assert.deepStrictEqual(floats, [1.0, 2.0, 3.0]);
|
||||
});
|
||||
|
||||
it('serializes json', () => {
|
||||
const wv = WireValue.json('{"key":"value"}');
|
||||
const buf = wv.serialize();
|
||||
assert.strictEqual(buf[0], FieldKind.JSON);
|
||||
assert.strictEqual(buf.readUInt32BE(1), 15);
|
||||
});
|
||||
|
||||
it('serializes array', () => {
|
||||
const wv = WireValue.array([WireValue.string('a'), WireValue.string('b')]);
|
||||
const buf = wv.serialize();
|
||||
assert.strictEqual(buf[0], FieldKind.ARRAY);
|
||||
assert.strictEqual(buf.readUInt32BE(1), 2);
|
||||
});
|
||||
|
||||
it('serializes object', () => {
|
||||
const wv = WireValue.object({ name: WireValue.string('Bara'), age: WireValue.int32(42) });
|
||||
const buf = wv.serialize();
|
||||
assert.strictEqual(buf[0], FieldKind.OBJECT);
|
||||
assert.strictEqual(buf.readUInt32BE(1), 2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Protocol constants', () => {
|
||||
it('has correct client message kinds', () => {
|
||||
assert.strictEqual(MsgKind.CLIENT_HANDSHAKE, 0x01);
|
||||
assert.strictEqual(MsgKind.QUERY, 0x02);
|
||||
assert.strictEqual(MsgKind.QUERY_PARAMS, 0x03);
|
||||
assert.strictEqual(MsgKind.EXECUTE, 0x04);
|
||||
assert.strictEqual(MsgKind.BATCH, 0x05);
|
||||
assert.strictEqual(MsgKind.TRANSACTION, 0x06);
|
||||
assert.strictEqual(MsgKind.CLOSE, 0x07);
|
||||
assert.strictEqual(MsgKind.PING, 0x08);
|
||||
assert.strictEqual(MsgKind.AUTH, 0x09);
|
||||
});
|
||||
|
||||
it('has correct server message kinds', () => {
|
||||
assert.strictEqual(MsgKind.SERVER_HANDSHAKE, 0x80);
|
||||
assert.strictEqual(MsgKind.READY, 0x81);
|
||||
assert.strictEqual(MsgKind.DATA, 0x82);
|
||||
assert.strictEqual(MsgKind.COMPLETE, 0x83);
|
||||
assert.strictEqual(MsgKind.ERROR, 0x84);
|
||||
assert.strictEqual(MsgKind.AUTH_CHALLENGE, 0x85);
|
||||
assert.strictEqual(MsgKind.AUTH_OK, 0x86);
|
||||
assert.strictEqual(MsgKind.SCHEMA_CHANGE, 0x87);
|
||||
assert.strictEqual(MsgKind.PONG, 0x88);
|
||||
assert.strictEqual(MsgKind.TRANSACTION_STATE, 0x89);
|
||||
});
|
||||
|
||||
it('has correct result formats', () => {
|
||||
assert.strictEqual(ResultFormat.BINARY, 0x00);
|
||||
assert.strictEqual(ResultFormat.JSON, 0x01);
|
||||
assert.strictEqual(ResultFormat.TEXT, 0x02);
|
||||
});
|
||||
});
|
||||
|
||||
describe('QueryBuilder', () => {
|
||||
it('builds simple SELECT', () => {
|
||||
const client = new Client();
|
||||
const qb = new QueryBuilder(client);
|
||||
const sql = qb.select('name', 'age').from('users').build();
|
||||
assert.strictEqual(sql, 'SELECT name, age FROM users');
|
||||
});
|
||||
|
||||
it('builds SELECT * when empty', () => {
|
||||
const client = new Client();
|
||||
const qb = new QueryBuilder(client);
|
||||
const sql = qb.from('users').build();
|
||||
assert.strictEqual(sql, 'SELECT * FROM users');
|
||||
});
|
||||
|
||||
it('builds WHERE with single clause', () => {
|
||||
const client = new Client();
|
||||
const qb = new QueryBuilder(client);
|
||||
const sql = qb.select('name').from('users').where('age > 18').build();
|
||||
assert.strictEqual(sql, 'SELECT name FROM users WHERE age > 18');
|
||||
});
|
||||
|
||||
it('builds WHERE with multiple clauses', () => {
|
||||
const client = new Client();
|
||||
const qb = new QueryBuilder(client);
|
||||
const sql = qb.select('name').from('users').where('age > 18').where('active = true').build();
|
||||
assert.strictEqual(sql, 'SELECT name FROM users WHERE age > 18 AND active = true');
|
||||
});
|
||||
|
||||
it('builds JOIN', () => {
|
||||
const client = new Client();
|
||||
const qb = new QueryBuilder(client);
|
||||
const sql = qb.select('u.name', 'o.total').from('users u').join('orders o', 'u.id = o.user_id').build();
|
||||
assert.ok(sql.includes('JOIN orders o ON u.id = o.user_id'));
|
||||
});
|
||||
|
||||
it('builds LEFT JOIN', () => {
|
||||
const client = new Client();
|
||||
const qb = new QueryBuilder(client);
|
||||
const sql = qb.select('u.name', 'o.total').from('users u').leftJoin('orders o', 'u.id = o.user_id').build();
|
||||
assert.ok(sql.includes('LEFT JOIN orders o ON u.id = o.user_id'));
|
||||
});
|
||||
|
||||
it('builds GROUP BY and HAVING', () => {
|
||||
const client = new Client();
|
||||
const qb = new QueryBuilder(client);
|
||||
const sql = qb.select('dept', 'count(*)').from('employees').groupBy('dept').having('count(*) > 5').build();
|
||||
assert.ok(sql.includes('GROUP BY dept'));
|
||||
assert.ok(sql.includes('HAVING count(*) > 5'));
|
||||
});
|
||||
|
||||
it('builds ORDER BY', () => {
|
||||
const client = new Client();
|
||||
const qb = new QueryBuilder(client);
|
||||
const sql = qb.select('name').from('users').orderBy('name', 'DESC').build();
|
||||
assert.ok(sql.includes('ORDER BY name DESC'));
|
||||
});
|
||||
|
||||
it('builds LIMIT and OFFSET', () => {
|
||||
const client = new Client();
|
||||
const qb = new QueryBuilder(client);
|
||||
const sql = qb.select('name').from('users').limit(10).offset(5).build();
|
||||
assert.ok(sql.includes('LIMIT 10'));
|
||||
assert.ok(sql.includes('OFFSET 5'));
|
||||
});
|
||||
|
||||
it('builds full complex query', () => {
|
||||
const client = new Client();
|
||||
const qb = new QueryBuilder(client);
|
||||
const sql = qb.select('u.name', 'count(*) as cnt')
|
||||
.from('users u')
|
||||
.leftJoin('orders o', 'u.id = o.user_id')
|
||||
.where('u.age > 18')
|
||||
.groupBy('u.name')
|
||||
.having('cnt > 3')
|
||||
.orderBy('cnt', 'DESC')
|
||||
.limit(50)
|
||||
.build();
|
||||
assert.ok(sql.startsWith('SELECT'));
|
||||
assert.ok(sql.includes('LEFT JOIN'));
|
||||
assert.ok(sql.includes('WHERE'));
|
||||
assert.ok(sql.includes('GROUP BY'));
|
||||
assert.ok(sql.includes('HAVING'));
|
||||
assert.ok(sql.includes('ORDER BY'));
|
||||
assert.ok(sql.includes('LIMIT 50'));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user