bytex-sdk 5.4.0 → 5.5.0
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/index.js +138 -28
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -407,77 +407,187 @@ export class BytexAI {
|
|
|
407
407
|
}
|
|
408
408
|
|
|
409
409
|
// ════════════════════════════════════════════════════════════════
|
|
410
|
-
// ByteX REALTIME — Database Realtime
|
|
411
|
-
// Strategy:
|
|
412
|
-
//
|
|
410
|
+
// ByteX REALTIME — Database Realtime (BYOC Model)
|
|
411
|
+
// Strategy: ALWAYS uses user's own Supabase (BYOC)
|
|
412
|
+
// Falls back to HTTP polling if no Supabase configured
|
|
413
|
+
// Free tier: 200 concurrent connections per USER's Supabase project
|
|
413
414
|
// ════════════════════════════════════════════════════════════════
|
|
414
415
|
/**
|
|
415
|
-
* BytexRealtime — Subscribe to database changes
|
|
416
|
+
* BytexRealtime — Subscribe to database changes and broadcast events.
|
|
416
417
|
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
418
|
+
* ⚠️ IMPORTANT: Always use BYOC (user's own Supabase), NOT ByteX's central Supabase.
|
|
419
|
+
* ByteX's Supabase has a limit of 200 concurrent connections total.
|
|
420
|
+
* With BYOC, EACH USER gets their own 200 free concurrent connections.
|
|
421
|
+
*
|
|
422
|
+
* Usage (BYOC — Recommended):
|
|
423
|
+
* const rt = new BytexRealtime({
|
|
424
|
+
* supabaseUrl: 'https://your-project.supabase.co',
|
|
425
|
+
* supabaseKey: 'your-anon-key'
|
|
426
|
+
* });
|
|
427
|
+
*
|
|
428
|
+
* Usage (Polling fallback — no Supabase needed):
|
|
429
|
+
* const rt = new BytexRealtime({ pollUrl: 'https://api.bytex.work/', pollInterval: 5000 });
|
|
420
430
|
*/
|
|
421
431
|
export class BytexRealtime {
|
|
422
|
-
constructor(
|
|
423
|
-
if (!supabaseClient) throw new Error('[BytexRealtime] Pass a Supabase client. Use: new BytexRealtime(bytex.supabase)');
|
|
424
|
-
this.client = supabaseClient;
|
|
432
|
+
constructor(config = {}) {
|
|
425
433
|
this._channels = new Map();
|
|
434
|
+
this._pollers = new Map();
|
|
435
|
+
this._mode = 'idle';
|
|
436
|
+
|
|
437
|
+
// Mode 1: BYOC Supabase client passed directly (legacy support)
|
|
438
|
+
if (config && typeof config.from === 'function') {
|
|
439
|
+
console.warn('[BytexRealtime] ⚠️ Passing ByteX\'s own Supabase client is not recommended for production. Use BYOC credentials to avoid shared connection limits.');
|
|
440
|
+
this.client = config;
|
|
441
|
+
this._mode = 'supabase';
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Mode 2: BYOC — user provides their own Supabase credentials
|
|
446
|
+
if (config.supabaseUrl && config.supabaseKey) {
|
|
447
|
+
this.client = createClient(config.supabaseUrl, config.supabaseKey, {
|
|
448
|
+
realtime: { params: { eventsPerSecond: 10 } }
|
|
449
|
+
});
|
|
450
|
+
this._mode = 'supabase';
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Mode 3: Polling fallback — no Supabase required
|
|
455
|
+
if (config.pollUrl) {
|
|
456
|
+
this._pollUrl = config.pollUrl;
|
|
457
|
+
this._pollInterval = config.pollInterval || 5000;
|
|
458
|
+
this._mode = 'polling';
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
throw new Error(
|
|
463
|
+
'[BytexRealtime] Configuration required. Options:\n' +
|
|
464
|
+
' 1. BYOC (recommended): { supabaseUrl, supabaseKey } — user\'s own Supabase\n' +
|
|
465
|
+
' 2. Polling fallback: { pollUrl, pollInterval? } — works without Supabase'
|
|
466
|
+
);
|
|
426
467
|
}
|
|
427
468
|
|
|
428
469
|
/**
|
|
429
|
-
* Subscribe to realtime changes on a table.
|
|
430
|
-
* @param {string} table - Table name (e.g. '
|
|
470
|
+
* Subscribe to realtime database changes on a table (Supabase mode only).
|
|
471
|
+
* @param {string} table - Table name (e.g. 'messages', 'orders')
|
|
431
472
|
* @param {function} callback - Receives { event, new, old }
|
|
432
473
|
* @param {{ event?: 'INSERT'|'UPDATE'|'DELETE'|'*', filter?: string }} options
|
|
433
474
|
*/
|
|
434
475
|
on(table, callback, options = {}) {
|
|
476
|
+
if (this._mode === 'polling') {
|
|
477
|
+
return this._pollTable(table, callback, options);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (this._mode !== 'supabase') throw new Error('[BytexRealtime] No Supabase client. Provide supabaseUrl + supabaseKey.');
|
|
481
|
+
|
|
435
482
|
const event = options.event || '*';
|
|
436
483
|
const channelName = `bytex-rt-${table}-${Date.now()}`;
|
|
437
484
|
const channel = this.client
|
|
438
485
|
.channel(channelName)
|
|
439
486
|
.on('postgres_changes', {
|
|
440
487
|
event,
|
|
441
|
-
schema: 'public',
|
|
488
|
+
schema: options.schema || 'public',
|
|
442
489
|
table,
|
|
443
490
|
...(options.filter && { filter: options.filter })
|
|
444
491
|
}, (payload) => callback(payload))
|
|
445
|
-
.subscribe()
|
|
492
|
+
.subscribe((status) => {
|
|
493
|
+
if (status === 'CHANNEL_ERROR') {
|
|
494
|
+
console.error(`[BytexRealtime] Channel error on table "${table}". Check your Supabase config.`);
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
|
|
446
498
|
this._channels.set(channelName, channel);
|
|
447
499
|
return channelName;
|
|
448
500
|
}
|
|
449
501
|
|
|
450
502
|
/**
|
|
451
|
-
* Subscribe to a broadcast channel
|
|
452
|
-
* @param {string}
|
|
503
|
+
* Subscribe to a broadcast channel — acts like a room/WebSocket.
|
|
504
|
+
* @param {string} roomName - Unique room identifier
|
|
453
505
|
* @param {function} callback - Receives { event, payload }
|
|
506
|
+
* @returns {{ send, leave }} - Methods to send messages or leave the room
|
|
454
507
|
*/
|
|
455
|
-
join(
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
508
|
+
join(roomName, callback) {
|
|
509
|
+
if (this._mode !== 'supabase') {
|
|
510
|
+
throw new Error('[BytexRealtime] Broadcast channels require Supabase. Provide supabaseUrl + supabaseKey.');
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const channel = this.client.channel(`bytex-room-${roomName}`, {
|
|
514
|
+
config: { broadcast: { self: false } }
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
channel
|
|
518
|
+
.on('broadcast', { event: '*' }, (msg) => callback(msg))
|
|
519
|
+
.subscribe();
|
|
520
|
+
|
|
521
|
+
this._channels.set(`bytex-room-${roomName}`, channel);
|
|
522
|
+
|
|
459
523
|
return {
|
|
524
|
+
/** Send a message to all members of this room. */
|
|
460
525
|
send: (event, payload) => channel.send({ type: 'broadcast', event, payload }),
|
|
461
|
-
|
|
526
|
+
/** Leave the room and clean up. */
|
|
527
|
+
leave: () => this.off(`bytex-room-${roomName}`)
|
|
462
528
|
};
|
|
463
529
|
}
|
|
464
530
|
|
|
465
531
|
/**
|
|
466
|
-
*
|
|
467
|
-
*
|
|
532
|
+
* Polling fallback — polls a URL and calls callback when response changes.
|
|
533
|
+
* Used when no Supabase is configured.
|
|
534
|
+
* @private
|
|
468
535
|
*/
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
536
|
+
_pollTable(table, callback, options) {
|
|
537
|
+
let lastHash = null;
|
|
538
|
+
const pollerId = `poll-${table}-${Date.now()}`;
|
|
539
|
+
|
|
540
|
+
const poll = async () => {
|
|
541
|
+
try {
|
|
542
|
+
const url = `${this._pollUrl}?action=list&table=${table}`;
|
|
543
|
+
const res = await fetch(url);
|
|
544
|
+
const text = await res.text();
|
|
545
|
+
const hash = btoa(text.substring(0, 200));
|
|
546
|
+
if (lastHash !== null && hash !== lastHash) {
|
|
547
|
+
callback({ event: '*', source: 'poll', data: text });
|
|
548
|
+
}
|
|
549
|
+
lastHash = hash;
|
|
550
|
+
} catch (e) {
|
|
551
|
+
console.warn(`[BytexRealtime] Poll error: ${e.message}`);
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
poll();
|
|
556
|
+
const timer = setInterval(poll, this._pollInterval);
|
|
557
|
+
this._pollers.set(pollerId, timer);
|
|
558
|
+
return pollerId;
|
|
472
559
|
}
|
|
473
560
|
|
|
474
|
-
/**
|
|
561
|
+
/**
|
|
562
|
+
* Check current realtime mode.
|
|
563
|
+
* @returns {'supabase'|'polling'|'idle'}
|
|
564
|
+
*/
|
|
565
|
+
get mode() { return this._mode; }
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Get current active connection count (Supabase mode only).
|
|
569
|
+
*/
|
|
570
|
+
get connectionCount() { return this._channels.size; }
|
|
571
|
+
|
|
572
|
+
/** Unsubscribe from a specific channel or poller. */
|
|
573
|
+
off(channelId) {
|
|
574
|
+
const ch = this._channels.get(channelId);
|
|
575
|
+
if (ch) { this.client?.removeChannel(ch); this._channels.delete(channelId); return; }
|
|
576
|
+
|
|
577
|
+
const timer = this._pollers.get(channelId);
|
|
578
|
+
if (timer) { clearInterval(timer); this._pollers.delete(channelId); }
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/** Disconnect all subscriptions and clean up. */
|
|
475
582
|
destroy() {
|
|
476
|
-
this._channels.forEach((ch) => this.client
|
|
583
|
+
this._channels.forEach((ch) => this.client?.removeChannel(ch));
|
|
477
584
|
this._channels.clear();
|
|
585
|
+
this._pollers.forEach((t) => clearInterval(t));
|
|
586
|
+
this._pollers.clear();
|
|
478
587
|
}
|
|
479
588
|
}
|
|
480
589
|
|
|
590
|
+
|
|
481
591
|
// ════════════════════════════════════════════════════════════════
|
|
482
592
|
// ByteX CRON — Scheduled Jobs via Cloudflare Workers Cron Triggers
|
|
483
593
|
// Strategy: BYOC — user provides their Cloudflare API token
|