kalam-link 0.2.0-alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,683 @@
1
+ /**
2
+ * kalam-link - Official TypeScript/JavaScript client for KalamDB
3
+ *
4
+ * This package provides a type-safe wrapper around the KalamDB WASM bindings
5
+ * for use in Node.js and browser environments.
6
+ *
7
+ * Features:
8
+ * - SQL query execution via HTTP
9
+ * - Real-time subscriptions via WebSocket (single connection, multiple subscriptions)
10
+ * - Subscription management with modern patterns (unsubscribe functions)
11
+ * - Cross-platform support (Node.js & Browser)
12
+ * - Type-safe authentication with multiple providers (Basic Auth, JWT, Anonymous)
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { createClient, Auth } from 'kalam-link';
17
+ *
18
+ * // Basic Auth (username/password)
19
+ * const client = createClient({
20
+ * url: 'http://localhost:8080',
21
+ * auth: Auth.basic('admin', 'admin')
22
+ * });
23
+ *
24
+ * // JWT Token Auth
25
+ * const jwtClient = createClient({
26
+ * url: 'http://localhost:8080',
27
+ * auth: Auth.jwt('eyJhbGciOiJIUzI1NiIs...')
28
+ * });
29
+ *
30
+ * // Anonymous (localhost bypass)
31
+ * const anonClient = createClient({
32
+ * url: 'http://localhost:8080',
33
+ * auth: Auth.none()
34
+ * });
35
+ *
36
+ * await client.connect();
37
+ *
38
+ * // Subscribe to changes (returns unsubscribe function - Firebase/Supabase style)
39
+ * const unsubscribe = await client.subscribe('messages', (event) => {
40
+ * console.log('Change:', event);
41
+ * });
42
+ *
43
+ * // Check subscription count
44
+ * console.log(`Active subscriptions: ${client.getSubscriptionCount()}`);
45
+ *
46
+ * // Later: unsubscribe when done
47
+ * await unsubscribe();
48
+ * ```
49
+ */
50
+ import init, { KalamClient as WasmClient } from './wasm/kalam_link.js';
51
+ // Re-export authentication types
52
+ export { Auth, buildAuthHeader, encodeBasicAuth, isAuthenticated, isBasicAuth, isJwtAuth, isNoAuth } from './auth.js';
53
+ /**
54
+ * Message type enum for WebSocket subscription events
55
+ */
56
+ export var MessageType;
57
+ (function (MessageType) {
58
+ MessageType["SubscriptionAck"] = "subscription_ack";
59
+ MessageType["InitialDataBatch"] = "initial_data_batch";
60
+ MessageType["Change"] = "change";
61
+ MessageType["Error"] = "error";
62
+ })(MessageType || (MessageType = {}));
63
+ /**
64
+ * Change type enum for live subscription change events
65
+ */
66
+ export var ChangeType;
67
+ (function (ChangeType) {
68
+ ChangeType["Insert"] = "insert";
69
+ ChangeType["Update"] = "update";
70
+ ChangeType["Delete"] = "delete";
71
+ })(ChangeType || (ChangeType = {}));
72
+ /**
73
+ * Batch loading status enum
74
+ */
75
+ export var BatchStatus;
76
+ (function (BatchStatus) {
77
+ BatchStatus["Loading"] = "loading";
78
+ BatchStatus["LoadingBatch"] = "loading_batch";
79
+ BatchStatus["Ready"] = "ready";
80
+ })(BatchStatus || (BatchStatus = {}));
81
+ /**
82
+ * Type guard to check if options use the new auth API
83
+ */
84
+ function isAuthOptions(options) {
85
+ return 'auth' in options;
86
+ }
87
+ /**
88
+ * KalamDB Client - TypeScript wrapper around WASM bindings
89
+ *
90
+ * Provides a type-safe interface to KalamDB with support for:
91
+ * - SQL query execution
92
+ * - Real-time WebSocket subscriptions
93
+ * - Multiple authentication methods (Basic Auth, JWT, Anonymous)
94
+ * - Cross-platform (Node.js & Browser)
95
+ * - Subscription tracking and management
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * // New API with type-safe auth (recommended)
100
+ * import { createClient, Auth } from 'kalam-link';
101
+ *
102
+ * const client = createClient({
103
+ * url: 'http://localhost:8080',
104
+ * auth: Auth.basic('alice', 'password123')
105
+ * });
106
+ *
107
+ * // JWT authentication
108
+ * const jwtClient = createClient({
109
+ * url: 'http://localhost:8080',
110
+ * auth: Auth.jwt('eyJhbGciOiJIUzI1NiIs...')
111
+ * });
112
+ *
113
+ * // Anonymous (no auth - localhost bypass)
114
+ * const anonClient = createClient({
115
+ * url: 'http://localhost:8080',
116
+ * auth: Auth.none()
117
+ * });
118
+ *
119
+ * await client.connect();
120
+ *
121
+ * // Execute queries
122
+ * const users = await client.query('SELECT * FROM users WHERE active = true');
123
+ * console.log(users.results[0].rows);
124
+ *
125
+ * // Subscribe to changes (returns unsubscribe function)
126
+ * const unsubscribe = await client.subscribe('messages', (event) => {
127
+ * if (event.type === 'change') {
128
+ * console.log('New message:', event.rows);
129
+ * }
130
+ * });
131
+ *
132
+ * // Check subscription count
133
+ * console.log(`Active: ${client.getSubscriptionCount()}`);
134
+ *
135
+ * // Cleanup
136
+ * await unsubscribe();
137
+ * await client.disconnect();
138
+ * ```
139
+ */
140
+ export class KalamDBClient {
141
+ constructor(urlOrOptions, username, password) {
142
+ this.wasmClient = null;
143
+ this.initialized = false;
144
+ /** Track active subscriptions for management */
145
+ this.subscriptions = new Map();
146
+ // Handle new options-based API
147
+ if (typeof urlOrOptions === 'object') {
148
+ if (!urlOrOptions.url)
149
+ throw new Error('KalamDBClient: url is required');
150
+ if (!urlOrOptions.auth)
151
+ throw new Error('KalamDBClient: auth is required');
152
+ this.url = urlOrOptions.url;
153
+ this.auth = urlOrOptions.auth;
154
+ }
155
+ // Handle legacy API (string url, username, password)
156
+ else {
157
+ if (!urlOrOptions)
158
+ throw new Error('KalamDBClient: url parameter is required');
159
+ if (!username)
160
+ throw new Error('KalamDBClient: username parameter is required');
161
+ if (!password)
162
+ throw new Error('KalamDBClient: password parameter is required');
163
+ this.url = urlOrOptions;
164
+ // Convert legacy API to new auth format
165
+ this.auth = { type: 'basic', username, password };
166
+ }
167
+ }
168
+ /**
169
+ * Get the current authentication type
170
+ *
171
+ * @returns 'basic', 'jwt', or 'none'
172
+ */
173
+ getAuthType() {
174
+ return this.auth.type;
175
+ }
176
+ /**
177
+ * Initialize WASM module and create client instance
178
+ *
179
+ * Must be called before any other operations. Automatically called by connect()
180
+ * if not already initialized.
181
+ *
182
+ * @throws Error if WASM initialization fails
183
+ */
184
+ async initialize() {
185
+ if (this.initialized)
186
+ return;
187
+ try {
188
+ // Browser environment - WASM will be fetched automatically
189
+ await init();
190
+ // Create WASM client based on auth type
191
+ switch (this.auth.type) {
192
+ case 'basic':
193
+ this.wasmClient = new WasmClient(this.url, this.auth.username, this.auth.password);
194
+ break;
195
+ case 'jwt':
196
+ this.wasmClient = WasmClient.withJwt(this.url, this.auth.token);
197
+ break;
198
+ case 'none':
199
+ this.wasmClient = WasmClient.anonymous(this.url);
200
+ break;
201
+ }
202
+ this.initialized = true;
203
+ }
204
+ catch (error) {
205
+ throw new Error(`Failed to initialize WASM client: ${error}`);
206
+ }
207
+ }
208
+ /**
209
+ * Connect to KalamDB server via WebSocket
210
+ *
211
+ * Establishes a persistent WebSocket connection for real-time subscriptions.
212
+ * Also initializes the WASM module if not already done.
213
+ *
214
+ * @throws Error if connection fails
215
+ */
216
+ async connect() {
217
+ await this.initialize();
218
+ if (!this.wasmClient) {
219
+ throw new Error('WASM client not initialized');
220
+ }
221
+ await this.wasmClient.connect();
222
+ }
223
+ /**
224
+ * Enable or disable automatic reconnection
225
+ *
226
+ * When enabled, the client will automatically attempt to reconnect
227
+ * if the WebSocket connection is lost, and will re-subscribe to all
228
+ * active subscriptions with resume_from_seq_id to catch up on missed events.
229
+ *
230
+ * @param enabled - Whether to automatically reconnect on connection loss
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * client.setAutoReconnect(true); // Enable (default)
235
+ * client.setAutoReconnect(false); // Disable for manual control
236
+ * ```
237
+ */
238
+ setAutoReconnect(enabled) {
239
+ this.ensureInitialized();
240
+ this.wasmClient.setAutoReconnect(enabled);
241
+ }
242
+ /**
243
+ * Configure reconnection delay parameters
244
+ *
245
+ * The client uses exponential backoff starting from initialDelayMs,
246
+ * doubling each attempt up to maxDelayMs.
247
+ *
248
+ * @param initialDelayMs - Initial delay between reconnection attempts (default: 1000ms)
249
+ * @param maxDelayMs - Maximum delay for exponential backoff (default: 30000ms)
250
+ *
251
+ * @example
252
+ * ```typescript
253
+ * // Start with 500ms delay, max out at 10 seconds
254
+ * client.setReconnectDelay(500, 10000);
255
+ * ```
256
+ */
257
+ setReconnectDelay(initialDelayMs, maxDelayMs) {
258
+ this.ensureInitialized();
259
+ this.wasmClient.setReconnectDelay(BigInt(initialDelayMs), BigInt(maxDelayMs));
260
+ }
261
+ /**
262
+ * Set maximum number of reconnection attempts
263
+ *
264
+ * @param maxAttempts - Maximum attempts before giving up (0 = infinite)
265
+ *
266
+ * @example
267
+ * ```typescript
268
+ * client.setMaxReconnectAttempts(5); // Give up after 5 attempts
269
+ * client.setMaxReconnectAttempts(0); // Never give up (default)
270
+ * ```
271
+ */
272
+ setMaxReconnectAttempts(maxAttempts) {
273
+ this.ensureInitialized();
274
+ this.wasmClient.setMaxReconnectAttempts(maxAttempts);
275
+ }
276
+ /**
277
+ * Get the current number of reconnection attempts
278
+ *
279
+ * Resets to 0 after a successful reconnection.
280
+ *
281
+ * @returns Current reconnection attempt count
282
+ */
283
+ getReconnectAttempts() {
284
+ this.ensureInitialized();
285
+ return this.wasmClient.getReconnectAttempts();
286
+ }
287
+ /**
288
+ * Check if the client is currently attempting to reconnect
289
+ *
290
+ * @returns true if a reconnection is in progress
291
+ */
292
+ isReconnecting() {
293
+ this.ensureInitialized();
294
+ return this.wasmClient.isReconnecting();
295
+ }
296
+ /**
297
+ * Get the last received sequence ID for a subscription
298
+ *
299
+ * Useful for debugging or manual tracking of subscription progress.
300
+ * This seq_id is automatically used during reconnection to resume
301
+ * from where the subscription left off.
302
+ *
303
+ * @param subscriptionId - The subscription ID to query
304
+ * @returns The last seq_id as a string, or undefined if not set
305
+ *
306
+ * @example
307
+ * ```typescript
308
+ * const lastSeq = client.getLastSeqId(subscriptionId);
309
+ * console.log(`Last received seq: ${lastSeq}`);
310
+ * ```
311
+ */
312
+ getLastSeqId(subscriptionId) {
313
+ this.ensureInitialized();
314
+ return this.wasmClient.getLastSeqId(subscriptionId) ?? undefined;
315
+ }
316
+ /**
317
+ * Helper to ensure WASM client is initialized
318
+ * @private
319
+ */
320
+ ensureInitialized() {
321
+ if (!this.wasmClient) {
322
+ throw new Error('WASM client not initialized. Call connect() first.');
323
+ }
324
+ }
325
+ /**
326
+ * Disconnect from KalamDB server
327
+ *
328
+ * Closes the WebSocket connection and cleans up all active subscriptions.
329
+ */
330
+ async disconnect() {
331
+ if (this.wasmClient) {
332
+ await this.wasmClient.disconnect();
333
+ }
334
+ // Clear subscription tracking
335
+ this.subscriptions.clear();
336
+ }
337
+ /**
338
+ * Check if client is currently connected
339
+ *
340
+ * @returns true if WebSocket connection is active, false otherwise
341
+ */
342
+ isConnected() {
343
+ return this.wasmClient?.isConnected() ?? false;
344
+ }
345
+ /**
346
+ * Execute a SQL query with optional parameters
347
+ *
348
+ * Supports all SQL statements: SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, etc.
349
+ * Use parameterized queries to prevent SQL injection.
350
+ *
351
+ * @param sql - SQL query string (may contain $1, $2, ... placeholders)
352
+ * @param params - Optional array of parameter values for placeholders
353
+ * @returns Parsed query response with results
354
+ *
355
+ * @throws Error if query execution fails
356
+ *
357
+ * @example
358
+ * ```typescript
359
+ * // Simple query
360
+ * const result = await client.query('SELECT * FROM users');
361
+ *
362
+ * // Parameterized query (recommended for user input)
363
+ * const users = await client.query(
364
+ * 'SELECT * FROM users WHERE id = $1 AND age > $2',
365
+ * [42, 18]
366
+ * );
367
+ * console.log(users.results[0].rows);
368
+ *
369
+ * // INSERT with parameters
370
+ * await client.query(
371
+ * "INSERT INTO users (name, email) VALUES ($1, $2)",
372
+ * ['Alice', 'alice@example.com']
373
+ * );
374
+ *
375
+ * // DDL statements (no params)
376
+ * await client.query('CREATE TABLE products (id BIGINT PRIMARY KEY, name TEXT)');
377
+ * ```
378
+ */
379
+ async query(sql, params) {
380
+ await this.initialize();
381
+ if (!this.wasmClient) {
382
+ throw new Error('WASM client not initialized');
383
+ }
384
+ let resultStr;
385
+ if (params && params.length > 0) {
386
+ resultStr = await this.wasmClient.queryWithParams(sql, JSON.stringify(params));
387
+ }
388
+ else {
389
+ resultStr = await this.wasmClient.query(sql);
390
+ }
391
+ return JSON.parse(resultStr);
392
+ }
393
+ /**
394
+ * Insert data into a table (convenience method)
395
+ *
396
+ * @param tableName - Name of the table (can include namespace, e.g., 'app.users')
397
+ * @param data - Object containing column values
398
+ * @returns Query response
399
+ *
400
+ * @throws Error if insert fails
401
+ *
402
+ * @example
403
+ * ```typescript
404
+ * await client.insert('todos', {
405
+ * title: 'Buy groceries',
406
+ * completed: false
407
+ * });
408
+ * ```
409
+ */
410
+ async insert(tableName, data) {
411
+ const dataJson = JSON.stringify(data);
412
+ const resultStr = await this.wasmClient.insert(tableName, dataJson);
413
+ return JSON.parse(resultStr);
414
+ }
415
+ /**
416
+ * Delete a row from a table (convenience method)
417
+ *
418
+ * @param tableName - Name of the table
419
+ * @param rowId - ID of the row to delete
420
+ *
421
+ * @throws Error if delete fails
422
+ *
423
+ * @example
424
+ * ```typescript
425
+ * await client.delete('todos', '123456789');
426
+ * ```
427
+ */
428
+ async delete(tableName, rowId) {
429
+ await this.wasmClient.delete(tableName, String(rowId));
430
+ }
431
+ /**
432
+ * Subscribe to real-time changes in a table
433
+ *
434
+ * The callback will be invoked for:
435
+ * - Initial data batches (type: 'initial_data_batch')
436
+ * - Live changes (type: 'change')
437
+ * - Errors (type: 'error')
438
+ *
439
+ * Returns an unsubscribe function (Firebase/Supabase style) for easy cleanup.
440
+ *
441
+ * @param tableName - Name of the table to subscribe to
442
+ * @param callback - Function called when changes occur
443
+ * @param options - Optional subscription options (batch_size, last_rows, from_seq_id)
444
+ * @returns Unsubscribe function to stop receiving updates
445
+ *
446
+ * @throws Error if subscription fails or not connected
447
+ *
448
+ * @example
449
+ * ```typescript
450
+ * // Simple subscription
451
+ * const unsubscribe = await client.subscribe('messages', (event) => {
452
+ * if (event.type === 'change') {
453
+ * console.log('New data:', event.rows);
454
+ * }
455
+ * });
456
+ *
457
+ * // With options
458
+ * const unsubscribe = await client.subscribe('messages', callback, {
459
+ * batch_size: 100, // Load initial data in batches of 100
460
+ * last_rows: 50 // Only fetch last 50 rows initially
461
+ * });
462
+ *
463
+ * // Later: unsubscribe when done
464
+ * await unsubscribe();
465
+ * ```
466
+ */
467
+ async subscribe(tableName, callback, options) {
468
+ // Use subscribeWithSql internally with SELECT * FROM tableName
469
+ const sql = `SELECT * FROM ${tableName}`;
470
+ return this.subscribeWithSql(sql, callback, options);
471
+ }
472
+ /**
473
+ * Subscribe to a SQL query with real-time updates
474
+ *
475
+ * More flexible than subscribe() - allows custom SQL queries with WHERE clauses,
476
+ * JOINs, and other SQL features.
477
+ *
478
+ * @param sql - SQL SELECT query to subscribe to
479
+ * @param callback - Function called when changes occur
480
+ * @param options - Optional subscription options (batch_size, last_rows, from_seq_id)
481
+ * @returns Unsubscribe function to stop receiving updates
482
+ *
483
+ * @throws Error if subscription fails or not connected
484
+ *
485
+ * @example
486
+ * ```typescript
487
+ * // Subscribe to filtered query
488
+ * const unsubscribe = await client.subscribeWithSql(
489
+ * 'SELECT * FROM chat.messages WHERE conversation_id = 1',
490
+ * (event) => {
491
+ * if (event.type === 'change') {
492
+ * console.log('New message:', event.rows);
493
+ * }
494
+ * },
495
+ * { batch_size: 50, last_rows: 100 }
496
+ * );
497
+ *
498
+ * // Later: unsubscribe when done
499
+ * await unsubscribe();
500
+ * ```
501
+ */
502
+ async subscribeWithSql(sql, callback, options) {
503
+ await this.initialize();
504
+ if (!this.wasmClient) {
505
+ throw new Error('WASM client not initialized');
506
+ }
507
+ // Wrap callback to parse JSON and provide typed event
508
+ const wrappedCallback = (eventJson) => {
509
+ try {
510
+ console.log('[KalamClient SDK] Received event JSON:', eventJson.substring(0, 200));
511
+ const event = JSON.parse(eventJson);
512
+ console.log('[KalamClient SDK] Parsed event type:', event.type);
513
+ callback(event);
514
+ }
515
+ catch (error) {
516
+ console.error('Failed to parse subscription event:', error);
517
+ }
518
+ };
519
+ // Convert options to JSON string if provided
520
+ const optionsJson = options ? JSON.stringify(options) : undefined;
521
+ const subscriptionId = await this.wasmClient.subscribeWithSql(sql, optionsJson, wrappedCallback);
522
+ // Track the subscription (use SQL as tableName for tracking)
523
+ this.subscriptions.set(subscriptionId, {
524
+ id: subscriptionId,
525
+ tableName: sql,
526
+ createdAt: new Date()
527
+ });
528
+ // Return unsubscribe function (Firebase/Supabase style)
529
+ return async () => {
530
+ await this.unsubscribe(subscriptionId);
531
+ };
532
+ }
533
+ /**
534
+ * Unsubscribe from table changes
535
+ *
536
+ * @param subscriptionId - ID returned from subscribe()
537
+ *
538
+ * @throws Error if unsubscribe fails or not connected
539
+ *
540
+ * @example
541
+ * ```typescript
542
+ * // Using the returned unsubscribe function (preferred)
543
+ * const unsubscribe = await client.subscribe('messages', handleChange);
544
+ * await unsubscribe();
545
+ *
546
+ * // Or manually with subscription ID
547
+ * const unsubscribe = await client.subscribe('messages', handleChange);
548
+ * const subs = client.getSubscriptions();
549
+ * await client.unsubscribe(subs[0].id);
550
+ * ```
551
+ */
552
+ async unsubscribe(subscriptionId) {
553
+ if (!this.wasmClient) {
554
+ throw new Error('WASM client not initialized');
555
+ }
556
+ await this.wasmClient.unsubscribe(subscriptionId);
557
+ // Remove from tracking
558
+ this.subscriptions.delete(subscriptionId);
559
+ }
560
+ /**
561
+ * Get the number of active subscriptions
562
+ *
563
+ * @returns Number of active subscriptions
564
+ *
565
+ * @example
566
+ * ```typescript
567
+ * console.log(`Active subscriptions: ${client.getSubscriptionCount()}`);
568
+ *
569
+ * // Prevent too many subscriptions
570
+ * if (client.getSubscriptionCount() >= 10) {
571
+ * console.warn('Too many subscriptions!');
572
+ * }
573
+ * ```
574
+ */
575
+ getSubscriptionCount() {
576
+ return this.subscriptions.size;
577
+ }
578
+ /**
579
+ * Get information about all active subscriptions
580
+ *
581
+ * @returns Array of subscription info objects
582
+ *
583
+ * @example
584
+ * ```typescript
585
+ * const subs = client.getSubscriptions();
586
+ * for (const sub of subs) {
587
+ * console.log(`Subscribed to ${sub.tableName} since ${sub.createdAt}`);
588
+ * }
589
+ * ```
590
+ */
591
+ getSubscriptions() {
592
+ return Array.from(this.subscriptions.values());
593
+ }
594
+ /**
595
+ * Check if subscribed to a specific table
596
+ *
597
+ * @param tableName - Name of the table to check
598
+ * @returns true if there's an active subscription to this table
599
+ *
600
+ * @example
601
+ * ```typescript
602
+ * if (!client.isSubscribedTo('messages')) {
603
+ * await client.subscribe('messages', handleChange);
604
+ * }
605
+ * ```
606
+ */
607
+ isSubscribedTo(tableName) {
608
+ for (const sub of this.subscriptions.values()) {
609
+ if (sub.tableName === tableName) {
610
+ return true;
611
+ }
612
+ }
613
+ return false;
614
+ }
615
+ /**
616
+ * Unsubscribe from all active subscriptions
617
+ *
618
+ * Useful for cleanup before disconnecting or switching contexts.
619
+ *
620
+ * @example
621
+ * ```typescript
622
+ * // Cleanup all subscriptions
623
+ * await client.unsubscribeAll();
624
+ * console.log(`Subscriptions remaining: ${client.getSubscriptionCount()}`); // 0
625
+ * ```
626
+ */
627
+ async unsubscribeAll() {
628
+ const subscriptionIds = Array.from(this.subscriptions.keys());
629
+ for (const id of subscriptionIds) {
630
+ await this.unsubscribe(id);
631
+ }
632
+ }
633
+ }
634
+ /**
635
+ * Create a KalamDB client with the given configuration
636
+ *
637
+ * Factory function that supports both the new type-safe auth API and legacy API.
638
+ *
639
+ * @param options - Client configuration with URL and authentication
640
+ * @returns Configured KalamDB client
641
+ *
642
+ * @example
643
+ * ```typescript
644
+ * import { createClient, Auth } from 'kalam-link';
645
+ *
646
+ * // New type-safe API (recommended)
647
+ * const client = createClient({
648
+ * url: 'http://localhost:8080',
649
+ * auth: Auth.basic('admin', 'admin')
650
+ * });
651
+ *
652
+ * // JWT authentication
653
+ * const jwtClient = createClient({
654
+ * url: 'http://localhost:8080',
655
+ * auth: Auth.jwt('eyJhbGciOiJIUzI1NiIs...')
656
+ * });
657
+ *
658
+ * // Anonymous (no authentication)
659
+ * const anonClient = createClient({
660
+ * url: 'http://localhost:8080',
661
+ * auth: Auth.none()
662
+ * });
663
+ *
664
+ * // Legacy API (deprecated but still works)
665
+ * const legacyClient = createClient({
666
+ * url: 'http://localhost:8080',
667
+ * username: 'admin',
668
+ * password: 'admin'
669
+ * });
670
+ * ```
671
+ */
672
+ export function createClient(options) {
673
+ // Handle both new and legacy API
674
+ if (isAuthOptions(options)) {
675
+ return new KalamDBClient(options);
676
+ }
677
+ else {
678
+ // Legacy API: convert to new format
679
+ return new KalamDBClient(options.url, options.username, options.password);
680
+ }
681
+ }
682
+ // Default export
683
+ export default KalamDBClient;