gbase-sdk
v1.0.1
Published
TypeScript SDK for GBase JSON-over-TCP database engine
Readme
GBase Node.js TypeScript SDK
A high-performance, strongly-typed TypeScript SDK for GBase — an in-memory JSON-over-TCP database engine supporting Key-Value, List, Set, Sorted Set (ZSet), atomic Transactions, and real-time Pub/Sub.
- For in-depth technical architecture, TCP wire protocol framing, request pipeline design, and decision rationales, see TECH.md.
- For complete method-by-method JSON wire request/response payload mapping, see API_CONTRACT.md.
Table of Contents
- Features
- Architecture Overview
- Installation
- Getting Started
- Usage and Examples
- API Contract & Wire Mapping
- Running Tests
- Technical Architecture Documentation
Features
- Async/Await Native: Promise-based API mapping TCP socket communication into standard
async/awaitcontrol flow. - Light OOP Paradigm: Clean data-holder classes (
KVStorage,ListStorage,SetStorage,ZSetStorage) encapsulating instance UUIDs. - Type-Safe: Full TypeScript type definitions supporting generic payload types (
<T>). - Full GBase Feature Support: Supports all server engines, pattern matching operations (
PREFIX,SUFFIX,REGEX), TTL expirations, multi-key operations, and set algebra. - Atomic Transactions: Full support for
MULTI,EXEC, andDISCARDacross multiple data structures. - Real-Time Pub/Sub: Event-driven subscriber client (
PubSubClient) operating on dedicated sockets to isolate subscriber mode. - Custom Error Hierarchy: Explicit error classes for network timeouts, connection drops, and server-side command errors.
Architecture Overview
flowchart TD
App["Application Code"]
subgraph SDK ["GBase Node.js SDK"]
GBaseClient["GBase Client Connection"]
subgraph StorageEngines ["Light OOP Storage Engines"]
KV["KVStorage"]
List["ListStorage"]
SetEngine["SetStorage"]
ZSet["ZSetStorage"]
end
Tx["Transaction Manager"]
PubSub["PubSubClient (Dedicated Socket)"]
end
subgraph Server ["GBase Server (:6380)"]
TCPListener["TCP Line-Delimited JSON Listener"]
Mux["Multiplexer Engine Router"]
DBInstances["Sub-Database Instances (UUIDs)"]
PubSubHub["Pub/Sub Hub"]
end
App -->|invoke methods| StorageEngines
App -->|multi / exec| Tx
App -->|subscribe / listen| PubSub
StorageEngines -->|queue request| GBaseClient
Tx -->|queue commands| GBaseClient
GBaseClient <-->|TCP \n JSON Frames| TCPListener
PubSub <-->|Dedicated TCP Push Socket| PubSubHub
TCPListener --> Mux
Mux --> DBInstancesInstallation
Ensure you have Node.js (v16+) installed. Add the SDK to your project:
npm install gbase-sdkEnsure a GBase TCP server is running on port 6380 (or your configured port).
Getting Started
import { GBase } from 'gbase-sdk';
async function main() {
// Connect to local GBase server
const gbase = await GBase.connect('127.0.0.1:6380');
// Create a new KV storage sub-instance on server
const kv = await gbase.createKvStorage();
// Store and retrieve typed JSON data
await kv.set('user:101', { name: 'Alice', score: 95 });
const user = await kv.get<{ name: string; score: number }>('user:101');
console.log('User:', user); // { name: 'Alice', score: 95 }
await gbase.disconnect(); // or gbase.close()
}
main();Usage and Examples
Connecting to GBase
GBase.connect() and new GBase() support full connection strings, host/port strings, or options objects:
// Full Connection String (with owner UUID & port)
const gbase1 = await GBase.connect('gbase://[email protected]:6381');
// Simple Connection String
const gbase2 = await GBase.connect('gbase://127.0.0.1:6380');
// Constructor Instantiation
const gbase3 = new GBase('gbase://[email protected]:6381');
// Host and Port String
const gbase4 = await GBase.connect('127.0.0.1:6380');
// Options Object
const gbase5 = await GBase.connect({
host: '127.0.0.1',
port: 6380,
timeoutMs: 5000,
userId: 'b4b1d3ab-71a7-4f4c-a25a-ec2cbe4276c8'
});System Telemetry and Instance Management
GBase supports dynamic creation and deletion of isolated sub-database instances:
// Get server diagnostics & memory allocation
const info = await gbase.info();
console.log('Go Version:', info.server.go_version);
console.log('Total Instances:', info.server.total_instances);
// Get total count of active instances across all engines
const count = await gbase.totalInstances();
// Create raw sub-instance UUID
const uuid = await gbase.createInstance('kv');
// Delete sub-instance by UUID
await gbase.deleteInstance('kv', uuid);Key-Value (KV) Storage
Supports primitives, complex objects, TTL expiration, atomic counters, bulk operations, and pattern matching.
// Factory: Create new KV instance or attach to existing UUID
const kv = await gbase.createKvStorage();
// or attach: const existingKv = gbase.useKvStorage("uuid-string");
// Basic SET & GET
await kv.set('config', { theme: 'dark', notifications: true });
const config = await kv.get<{ theme: string }>('config');
// SET with TTL (in milliseconds)
await kv.setTtl('session:abc', 'user_token', 5000); // expires in 5 seconds
// Non-existent key returns null
const missing = await kv.get('non_existent_key'); // null
// Atomic Counters
await kv.set('counter', 10);
await kv.incr('counter'); // 11
await kv.decr('counter'); // 10
await kv.incrBy('counter', 5); // 15
await kv.decrBy('counter', 3); // 12
// Bulk MSET and MGET
await kv.mset({ k1: 'val1', k2: 'val2', k3: 100 });
const batch = await kv.mget(['k1', 'k2', 'k3', 'missing']);
// { k1: 'val1', k2: 'val2', k3: 100, missing: null }
// Pattern Matching (PREFIX, SUFFIX, REGEX)
await kv.countPrefix('user:');
const userMap = await kv.findPrefix<string>('user:'); // { 'user:1': 'Alice', 'user:2': 'Bob' }
const deletedCount = await kv.delPrefix('user:');List Storage
Doubly linked list storage supporting left/right pushes/pops, ranged slicing, index access, pivot insertion, and atomic moves.
const list = await gbase.createListStorage();
// Push items to head (left) or tail (right)
await list.rpush('tasks', { title: 'Task 1' }, { title: 'Task 2' });
await list.lpush('tasks', { title: 'Priority Task' });
// Length and Ranged Slices
const len = await list.llen('tasks'); // 3
const allTasks = await list.lrange('tasks', 0, -1);
const allTasksLGet = await list.lget('tasks'); // Full list fetch
// Pop items
const priority = await list.lpop('tasks'); // Left pop
const last = await list.rpop('tasks'); // Right pop
// Indexed Access & Updates
const itemAtIndex = await list.lindex('tasks', 0);
await list.lset('tasks', 0, { title: 'Updated Task' });
// Insert relative to pivot element (BEFORE or AFTER)
await list.linsert('tasks', 'BEFORE', { title: 'Task 2' }, { title: 'Middle Task' });
// Atomic RPOPLPUSH (pop tail of queue1, push to head of queue2)
const movedItem = await list.rpoplpush('queue1', 'queue2');
// Pattern Matching
const foundLists = await list.findPrefix('logs:');Set Storage
Unordered collection of unique elements supporting set algebra (intersections, unions, differences), store variants, and random sampling.
const set = await gbase.createSetStorage();
// Add & Remove unique members
await set.sadd('tags', 'javascript', 'typescript', 'go');
await set.srem('tags', 'go');
// Cardinality & Membership
const count = await set.scard('tags'); // 2
const isMember = await set.sismember('tags', 'typescript'); // true
const memberFlags = await set.smismember('tags', 'javascript', 'python'); // [true, false]
// Random Popping & Sampling
const popped = await set.spop('tags', 1); // Pops 1 random element
const randomSample = await set.srandmember('tags', 2); // Samples without removing
// Move member between sets
await set.smove('setA', 'setB', 'member1');
// Set Algebra: SINTER, SUNION, SDIFF
await set.sadd('groupA', 'alice', 'bob', 'charlie');
await set.sadd('groupB', 'bob', 'charlie', 'dave');
const common = await set.sinter('groupA', 'groupB'); // ['bob', 'charlie']
const allUsers = await set.sunion('groupA', 'groupB'); // ['alice', 'bob', 'charlie', 'dave']
const uniqueToA = await set.sdiff('groupA', 'groupB'); // ['alice']
// Intersection Cardinality without fetching all members
const interCard = await set.sintercard(['groupA', 'groupB'], 10);
// Store algebra results into a new destination key
await set.sinterstore('destKey', 'groupA', 'groupB');Sorted Set (ZSet) Storage
Elements sorted by floating-point scores, supporting rank lookups, score range queries, and min/max popping.
const zset = await gbase.createZSetStorage();
// Add members with scores
await zset.zadd(
'leaderboard',
{ score: 100, member: 'player1' },
{ score: 250, member: 'player2' },
{ score: 180, member: 'player3' }
);
// Get score & rank
const score = await zset.zscore('leaderboard', 'player1'); // 100
const rank = await zset.zrank('leaderboard', 'player2'); // 2 (0-indexed, ascending)
const revRank = await zset.zrevrank('leaderboard', 'player2'); // 0 (descending)
// Increment score
await zset.zincrby('leaderboard', 50, 'player1'); // 150
// Pop Max / Min scored elements
const topPlayer = await zset.zpopmax('leaderboard', 1); // [{ member: 'player2', score: 250 }]
const lowestPlayer = await zset.zpopmin('leaderboard', 1);
// Range by Rank
const top3 = await zset.zrevrange('leaderboard', 0, 2);
// Range by Score with Offset & Limit
const scoredRange = await zset.zrangebyscore('leaderboard', 100, 200, 0, 10);Atomic Transactions (MULTI / EXEC / DISCARD)
Executes multiple commands atomically across any storage engine in a single transaction block.
sequenceDiagram
autonumber
actor App as Application Code
participant Client as GBase Client
participant Server as GBase Server
App->>Client: gbase.multi()
Client->>Server: {"method": "MULTI"}
Server-->>Client: {"ok": true}
App->>Client: kv.set("k1", "v1")
Client->>Server: {"ds": "kv", "uuid": "...", "method": "SET", "args": [...]}
Server-->>Client: {"ok": true, "queued": true}
App->>Client: list.rpush("l1", "item1")
Client->>Server: {"ds": "list", "uuid": "...", "method": "RPUSH", "args": [...]}
Server-->>Client: {"ok": true, "queued": true}
alt Commit Transaction
App->>Client: tx.exec()
Client->>Server: {"method": "EXEC"}
Server-->>Client: {"ok": true, "responses": [res1, res2]}
Client-->>App: returns [res1, res2]
else Rollback Transaction
App->>Client: tx.discard()
Client->>Server: {"method": "DISCARD"}
Server-->>Client: {"ok": true}
Client-->>App: transaction cleared
end// Initiate transaction on connection
const tx = await gbase.multi();
// Queue commands across your storage engines
await kv.set('acc:1', 100);
await kv.set('acc:2', 200);
await list.rpush('tx_log', 'Transferred funds');
// Commit and execute all queued commands atomically
const responses = await tx.exec();
console.log('Atomic execution results:', responses);
// Discard transaction (roll back queued commands)
const tx2 = await gbase.multi();
await kv.set('temp', 'value');
await tx2.discard(); // Cancels queued operationsReal-Time Pub/Sub Messaging
Subscriber mode operates on a dedicated TCP connection (PubSubClient) to prevent blocking normal database commands:
sequenceDiagram
autonumber
actor SubscriberApp as Subscriber App
participant SubClient as PubSubClient (Dedicated Socket)
participant Server as GBase Server (:6380)
participant PubClient as GBase Client (Command Socket)
actor PublisherApp as Publisher App
SubscriberApp->>SubClient: createPubSubClient()
SubClient->>Server: TCP Connect
SubscriberApp->>SubClient: subscribe("news.sports")
SubClient->>Server: {"method": "SUBSCRIBE", "args": ["news.sports"]}
Server-->>SubClient: {"ok": true, "pubsub": {"action": "subscribe", ...}}
PublisherApp->>PubClient: gbase.publish("news.sports", "Goal!")
PubClient->>Server: {"method": "PUBLISH", "args": ["news.sports", "Goal!"]}
Server-->>PubClient: {"ok": true, "integer": 1}
Note over Server,SubClient: Asynchronous Push Frame
Server-->>SubClient: {"type": "message", "channel": "news.sports", "data": "Goal!"}
SubClient-->>SubscriberApp: emit("message", "news.sports", "Goal!")import { GBase } from 'gbase-sdk';
async function main() {
const gbase = await GBase.connect('127.0.0.1:6380');
// Dedicated subscriber socket connection
const subscriber = await gbase.createPubSubClient();
// Listen to exact channel messages
subscriber.on('message', (channel: string, data: any) => {
console.log(`[${channel}]:`, data);
});
// Listen to glob pattern messages
subscriber.on('pmessage', (pattern: string, channel: string, data: any) => {
console.log(`[Pattern ${pattern} on ${channel}]:`, data);
});
// Subscribe
await subscriber.subscribe('news.sports', 'news.tech');
await subscriber.psubscribe('orders.*');
// Publish from main client (returns subscriber recipient count)
const recipients = await gbase.publish('news.sports', { event: 'Goal!', minute: 90 });
console.log(`Recipients reached: ${recipients}`);
// Unsubscribe & Close
await subscriber.unsubscribe('news.sports');
await subscriber.close();
await gbase.close();
}
main();Error Handling
The SDK provides an explicit error hierarchy extending standard JavaScript Error:
import {
GBaseError,
GBaseConnectionError,
GBaseCommandError,
GBaseTimeoutError
} from 'gbase-sdk';
try {
const data = await kv.get('key');
} catch (err) {
if (err instanceof GBaseConnectionError) {
console.error('Network drop or TCP socket error:', err.message);
} else if (err instanceof GBaseCommandError) {
console.error('Server rejected command:', err.serverError);
} else if (err instanceof GBaseTimeoutError) {
console.error('Request timed out:', err.message);
} else if (err instanceof GBaseError) {
console.error('General GBase error:', err.message);
}
}API Contract & Wire Mapping
For complete details on exact JSON request payloads (ds, method, args) and response payload fields unwrapped by each SDK function, see API_CONTRACT.md.
Running Tests
Run the complete Jest integration & unit test suite:
npm testThe test suite runs 62+ tests covering connection pipeline telemetry, KV, List, Set, ZSet engines, pattern matching, atomic transactions, real-time Pub/Sub, and edge cases.
Technical Architecture Documentation
For detailed information on the architectural design, wire protocol framing, light OOP data holders, request pipeline queue, and socket management, refer to TECH.md.
