@aurabase/realtime 0.5.3 → 0.6.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/dist/index.cjs +127 -23
- package/dist/index.d.cts +75 -10
- package/dist/index.d.ts +75 -10
- package/dist/index.js +127 -23
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -226,6 +226,7 @@ var AuraChannel = class {
|
|
|
226
226
|
name;
|
|
227
227
|
_projectPrefix;
|
|
228
228
|
_listeners = [];
|
|
229
|
+
_listenerIdCounter = 0;
|
|
229
230
|
_subscribed = false;
|
|
230
231
|
_sendFn = null;
|
|
231
232
|
_subscribeCallback = null;
|
|
@@ -287,8 +288,69 @@ var AuraChannel = class {
|
|
|
287
288
|
get fullName() {
|
|
288
289
|
return `${this._projectPrefix()}${this.name}`;
|
|
289
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* Signature d'implémentation de `.on()`.
|
|
293
|
+
* Retourne une fonction de désabonnement direct `() => void` qui proxy également l'instance du canal pour le chaînage fluide.
|
|
294
|
+
*/
|
|
290
295
|
on(type, filter, callback) {
|
|
291
|
-
|
|
296
|
+
const id = ++this._listenerIdCounter;
|
|
297
|
+
this._listeners.push({
|
|
298
|
+
id,
|
|
299
|
+
type,
|
|
300
|
+
filter,
|
|
301
|
+
callback
|
|
302
|
+
});
|
|
303
|
+
const unsubscribe = () => {
|
|
304
|
+
const idx = this._listeners.findIndex((l) => l.id === id);
|
|
305
|
+
if (idx !== -1) {
|
|
306
|
+
this._listeners.splice(idx, 1);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
return new Proxy(unsubscribe, {
|
|
310
|
+
get: (_target, prop) => {
|
|
311
|
+
const val = this[prop];
|
|
312
|
+
if (typeof val === "function") {
|
|
313
|
+
return (...args) => Reflect.apply(val, this, args);
|
|
314
|
+
}
|
|
315
|
+
return val;
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Supprime un ou plusieurs listeners du canal sans clore le canal ni couper le WebSocket.
|
|
321
|
+
*
|
|
322
|
+
* - `off(type, filter, callback)` : supprime le listener exact.
|
|
323
|
+
* - `off(type, filter)` : supprime les listeners correspondant à ce type et ce filtre.
|
|
324
|
+
* - `off(type)` : supprime tous les listeners du type spécifié.
|
|
325
|
+
* - `off()` : supprime tous les listeners locaux du canal.
|
|
326
|
+
*/
|
|
327
|
+
off(type, filter, callback) {
|
|
328
|
+
if (!type) {
|
|
329
|
+
this._listeners.length = 0;
|
|
330
|
+
return this;
|
|
331
|
+
}
|
|
332
|
+
const filtered = this._listeners.filter((l) => {
|
|
333
|
+
if (l.type !== type) return true;
|
|
334
|
+
if (filter) {
|
|
335
|
+
const filterMeta = filter;
|
|
336
|
+
const listenerFilter = l.filter;
|
|
337
|
+
if ("event" in filter && "event" in l.filter && listenerFilter.event !== filterMeta.event) {
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
if ("schema" in filter && listenerFilter.schema !== filterMeta.schema) {
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
if ("table" in filter && listenerFilter.table !== filterMeta.table) {
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (callback && l.callback !== callback) {
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
return false;
|
|
351
|
+
});
|
|
352
|
+
this._listeners.length = 0;
|
|
353
|
+
this._listeners.push(...filtered);
|
|
292
354
|
return this;
|
|
293
355
|
}
|
|
294
356
|
// ─── Subscribe ──────────────────────────────────────────────────────────
|
|
@@ -698,32 +760,57 @@ var AuraChannel = class {
|
|
|
698
760
|
}
|
|
699
761
|
}
|
|
700
762
|
const cdcPayload = payload && typeof payload === "object" ? payload : {};
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
763
|
+
const rawRecord = cdcPayload.record ?? cdcPayload.new ?? null;
|
|
764
|
+
const rawOldRecord = cdcPayload.old_record ?? cdcPayload.old ?? null;
|
|
765
|
+
const rawType = cdcPayload.type ?? cdcPayload.eventType;
|
|
766
|
+
const eventTypeString = typeof rawType === "string" ? rawType : typeof eventType === "string" ? eventType : "INSERT";
|
|
767
|
+
const cdcType = eventTypeString.toUpperCase();
|
|
768
|
+
const recordNew = cdcType === "DELETE" ? null : rawRecord;
|
|
769
|
+
const recordOld = cdcType === "INSERT" ? null : rawOldRecord;
|
|
770
|
+
const enrichedPayload = {
|
|
771
|
+
...cdcPayload,
|
|
772
|
+
record: rawRecord,
|
|
773
|
+
old_record: rawOldRecord,
|
|
774
|
+
new: recordNew,
|
|
775
|
+
old: recordOld,
|
|
776
|
+
eventType: cdcType,
|
|
777
|
+
schema: cdcPayload.schema ?? filter.schema ?? "public",
|
|
778
|
+
table: cdcPayload.table ?? filter.table ?? "",
|
|
779
|
+
commit_timestamp: cdcPayload.commit_timestamp ?? event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
780
|
+
};
|
|
781
|
+
const enrichedEvent = {
|
|
782
|
+
...event,
|
|
783
|
+
schema: cdcPayload.schema ?? filter.schema ?? "public",
|
|
784
|
+
table: cdcPayload.table ?? filter.table ?? "",
|
|
785
|
+
commit_timestamp: cdcPayload.commit_timestamp ?? event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
786
|
+
errors: cdcPayload.errors,
|
|
787
|
+
id: event.id,
|
|
788
|
+
topic: event.topic,
|
|
789
|
+
timestamp: event.timestamp,
|
|
790
|
+
eventType: cdcType,
|
|
791
|
+
event: cdcType === "INSERT" ? "insert" : cdcType === "UPDATE" ? "update" : "delete",
|
|
792
|
+
new: recordNew,
|
|
793
|
+
old: recordOld,
|
|
794
|
+
payload: enrichedPayload
|
|
795
|
+
};
|
|
717
796
|
listener.callback(enrichedEvent);
|
|
718
797
|
}
|
|
719
798
|
}
|
|
720
799
|
} else if (listener.type === "broadcast") {
|
|
721
800
|
if (eventType === "broadcast") {
|
|
722
801
|
const filter = listener.filter;
|
|
723
|
-
const broadcastPayload = event.payload && typeof event.payload === "object" ? event.payload :
|
|
724
|
-
const broadcastEvent = broadcastPayload
|
|
802
|
+
const broadcastPayload = event.payload && typeof event.payload === "object" ? event.payload : {};
|
|
803
|
+
const broadcastEvent = broadcastPayload.event ?? "broadcast";
|
|
725
804
|
if (!filter.event || filter.event === "*" || broadcastEvent === filter.event) {
|
|
726
|
-
|
|
805
|
+
const enrichedBroadcast = {
|
|
806
|
+
id: event.id,
|
|
807
|
+
topic: event.topic,
|
|
808
|
+
timestamp: event.timestamp,
|
|
809
|
+
allowed_sub_ids: event.allowed_sub_ids,
|
|
810
|
+
event: broadcastEvent,
|
|
811
|
+
payload: broadcastPayload
|
|
812
|
+
};
|
|
813
|
+
listener.callback(enrichedBroadcast);
|
|
727
814
|
}
|
|
728
815
|
}
|
|
729
816
|
} else if (listener.type === "presence") {
|
|
@@ -1372,8 +1459,25 @@ var RealtimeService = class _RealtimeService {
|
|
|
1372
1459
|
* await aura.realtime.httpBroadcast(`${PROJECT_ID}:room-1`, 'cursor', { x: 10, y: 20 })
|
|
1373
1460
|
* ```
|
|
1374
1461
|
*/
|
|
1375
|
-
|
|
1376
|
-
|
|
1462
|
+
/**
|
|
1463
|
+
* Envoie un broadcast par HTTP (REST), sans connexion WebSocket ouverte
|
|
1464
|
+
* (parité `@supabase/realtime-js`). Utile côté serveur/edge.
|
|
1465
|
+
*
|
|
1466
|
+
* POST `/v1/realtime/broadcast` avec le contrat du handler backend
|
|
1467
|
+
* (`broadcast.rs`) : `{ channel, payload }`, `channel` préfixé par le projet,
|
|
1468
|
+
* `event` replié dans `payload` (aligné sur le broadcast WS).
|
|
1469
|
+
*
|
|
1470
|
+
* Le canal peut être fourni :
|
|
1471
|
+
* - sous forme courte (`'room-1'`) si une session utilisateur est présente,
|
|
1472
|
+
* - sous forme courte avec l'option `{ projectId: '<uuid>' }`,
|
|
1473
|
+
* - ou déjà qualifié (`'<project_id>:room-1'`).
|
|
1474
|
+
*/
|
|
1475
|
+
httpBroadcast(channel, event, payload = {}, options) {
|
|
1476
|
+
const explicitProjectId = options?.projectId ?? this.client.projectId;
|
|
1477
|
+
let prefix = this._projectPrefix();
|
|
1478
|
+
if (!prefix && explicitProjectId) {
|
|
1479
|
+
prefix = explicitProjectId.endsWith(":") ? explicitProjectId : `${explicitProjectId}:`;
|
|
1480
|
+
}
|
|
1377
1481
|
let fullChannel;
|
|
1378
1482
|
if (prefix) {
|
|
1379
1483
|
fullChannel = channel.startsWith(prefix) ? channel : `${prefix}${channel}`;
|
|
@@ -1385,7 +1489,7 @@ var RealtimeService = class _RealtimeService {
|
|
|
1385
1489
|
error: new core.AuraError(0, {
|
|
1386
1490
|
error: {
|
|
1387
1491
|
code: "realtime_channel_non_qualifie",
|
|
1388
|
-
message: `httpBroadcast: le canal "${channel}" n'est pas qualifi\xE9 et aucune session n'est ouverte \u2014 le SDK n'a donc aucun \`project_id\` \xE0 lui pr\xE9fixer, alors que le serveur l'EXIGE (403 \xAB Le canal n'appartient pas \xE0 ce projet \xBB). Passez le canal sous la forme "<project_id>:` + channel + `" \u2014 c'est la forme attendue c\xF4t\xE9 serveur/edge, o\xF9 il n'y a pas de session utilisateur.`
|
|
1492
|
+
message: `httpBroadcast: le canal "${channel}" n'est pas qualifi\xE9 et aucune session n'est ouverte \u2014 le SDK n'a donc aucun \`project_id\` \xE0 lui pr\xE9fixer, alors que le serveur l'EXIGE (403 \xAB Le canal n'appartient pas \xE0 ce projet \xBB). Passez le canal sous la forme "<project_id>:` + channel + `" ou sp\xE9cifiez l'option { projectId: "<uuid>" } \u2014 c'est la forme attendue c\xF4t\xE9 serveur/edge, o\xF9 il n'y a pas de session utilisateur.`
|
|
1389
1493
|
}
|
|
1390
1494
|
})
|
|
1391
1495
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
import { ChannelConfig, PostgresChangesFilter,
|
|
1
|
+
import { ChannelConfig, PostgresChangesFilter, RealtimePostgresInsertPayload, RealtimePostgresUpdatePayload, RealtimePostgresDeletePayload, RealtimePostgresChangesPayload, BroadcastFilter, RealtimeBroadcastEvent, PresenceFilter, PresenceState, RealtimeEvent, RealtimeListenType, SubscribeCallback, BroadcastPayload, BroadcastSendStatus, ServerMessage, AuraClientLike, AuraResponse } from '@aurabase/core';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Fonction de désabonnement retournée par `.on()`, enrichie avec les méthodes du canal pour permettre le chaînage fluide.
|
|
5
|
+
*/
|
|
6
|
+
type AuraChannelSubscription = (() => void) & AuraChannel;
|
|
3
7
|
type ListenerEntry = {
|
|
8
|
+
id: number;
|
|
4
9
|
type: 'postgres_changes';
|
|
5
10
|
filter: PostgresChangesFilter;
|
|
6
|
-
callback: (payload:
|
|
11
|
+
callback: (payload: RealtimePostgresChangesPayload<Record<string, unknown>>) => void;
|
|
7
12
|
} | {
|
|
13
|
+
id: number;
|
|
8
14
|
type: 'broadcast';
|
|
9
15
|
filter: BroadcastFilter;
|
|
10
|
-
callback: (payload:
|
|
16
|
+
callback: (payload: RealtimeBroadcastEvent<Record<string, unknown>>) => void;
|
|
11
17
|
} | {
|
|
18
|
+
id: number;
|
|
12
19
|
type: 'presence';
|
|
13
20
|
filter: PresenceFilter;
|
|
14
21
|
callback: (payload: PresenceState | RealtimeEvent | Record<string, unknown>) => void;
|
|
@@ -52,6 +59,7 @@ declare class AuraChannel {
|
|
|
52
59
|
readonly name: string;
|
|
53
60
|
private readonly _projectPrefix;
|
|
54
61
|
private readonly _listeners;
|
|
62
|
+
private _listenerIdCounter;
|
|
55
63
|
private _subscribed;
|
|
56
64
|
private _sendFn;
|
|
57
65
|
private _subscribeCallback;
|
|
@@ -101,12 +109,49 @@ declare class AuraChannel {
|
|
|
101
109
|
/** Full channel name with project prefix (for server protocol) */
|
|
102
110
|
get fullName(): string;
|
|
103
111
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
112
|
+
* Enregistre un listener pour les événements PostgreSQL (CDC) avec filtrage INSERT.
|
|
113
|
+
* Typé spécifiquement avec `new: TRow` et `old: null`.
|
|
114
|
+
*/
|
|
115
|
+
on<TRow = Record<string, unknown>>(type: 'postgres_changes', filter: PostgresChangesFilter & {
|
|
116
|
+
event: 'INSERT';
|
|
117
|
+
}, callback: (payload: RealtimePostgresInsertPayload<TRow>) => void): AuraChannelSubscription;
|
|
118
|
+
/**
|
|
119
|
+
* Enregistre un listener pour les événements PostgreSQL (CDC) avec filtrage UPDATE.
|
|
120
|
+
* Typé spécifiquement avec `new: TRow` et `old: Partial<TRow>`.
|
|
121
|
+
*/
|
|
122
|
+
on<TRow = Record<string, unknown>>(type: 'postgres_changes', filter: PostgresChangesFilter & {
|
|
123
|
+
event: 'UPDATE';
|
|
124
|
+
}, callback: (payload: RealtimePostgresUpdatePayload<TRow>) => void): AuraChannelSubscription;
|
|
125
|
+
/**
|
|
126
|
+
* Enregistre un listener pour les événements PostgreSQL (CDC) avec filtrage DELETE.
|
|
127
|
+
* Typé spécifiquement avec `new: null` et `old: Partial<TRow> | null`.
|
|
128
|
+
*/
|
|
129
|
+
on<TRow = Record<string, unknown>>(type: 'postgres_changes', filter: PostgresChangesFilter & {
|
|
130
|
+
event: 'DELETE';
|
|
131
|
+
}, callback: (payload: RealtimePostgresDeletePayload<TRow>) => void): AuraChannelSubscription;
|
|
132
|
+
/**
|
|
133
|
+
* Enregistre un listener pour les événements PostgreSQL (CDC) (INSERT, UPDATE, DELETE ou '*').
|
|
134
|
+
* Typé comme union discriminée par `eventType`.
|
|
135
|
+
*/
|
|
136
|
+
on<TRow = Record<string, unknown>>(type: 'postgres_changes', filter: PostgresChangesFilter, callback: (payload: RealtimePostgresChangesPayload<TRow>) => void): AuraChannelSubscription;
|
|
137
|
+
/**
|
|
138
|
+
* Enregistre un listener broadcast avec typage générique du payload applicatif.
|
|
139
|
+
* Permet l'accès direct et typé à `({ payload }) => payload.x` ainsi qu'aux métadonnées.
|
|
106
140
|
*/
|
|
107
|
-
on(type: '
|
|
108
|
-
|
|
109
|
-
|
|
141
|
+
on<TPayload = Record<string, unknown>>(type: 'broadcast', filter: BroadcastFilter, callback: (payload: RealtimeBroadcastEvent<TPayload>) => void): AuraChannelSubscription;
|
|
142
|
+
/**
|
|
143
|
+
* Enregistre un listener pour la présence.
|
|
144
|
+
*/
|
|
145
|
+
on(type: 'presence', filter: PresenceFilter, callback: (payload: PresenceState | RealtimeEvent | Record<string, unknown>) => void): AuraChannelSubscription;
|
|
146
|
+
/**
|
|
147
|
+
* Supprime un ou plusieurs listeners du canal sans clore le canal ni couper le WebSocket.
|
|
148
|
+
*
|
|
149
|
+
* - `off(type, filter, callback)` : supprime le listener exact.
|
|
150
|
+
* - `off(type, filter)` : supprime les listeners correspondant à ce type et ce filtre.
|
|
151
|
+
* - `off(type)` : supprime tous les listeners du type spécifié.
|
|
152
|
+
* - `off()` : supprime tous les listeners locaux du canal.
|
|
153
|
+
*/
|
|
154
|
+
off(type?: RealtimeListenType, filter?: PostgresChangesFilter | BroadcastFilter | PresenceFilter, callback?: (payload: unknown) => void): this;
|
|
110
155
|
/**
|
|
111
156
|
* Subscribe to the channel. Sends the Subscribe message to the server
|
|
112
157
|
* with the full config (postgres_changes, broadcast, presence).
|
|
@@ -371,6 +416,13 @@ declare const HEARTBEAT_INTERVAL_MS = 25000;
|
|
|
371
416
|
* })
|
|
372
417
|
* ```
|
|
373
418
|
*/
|
|
419
|
+
interface HttpBroadcastOptions {
|
|
420
|
+
/**
|
|
421
|
+
* Identifiant UUID du projet pour qualifier le canal lorsque le SDK fonctionne sans session
|
|
422
|
+
* utilisateur (ex: côté serveur ou edge avec une clé API service_role).
|
|
423
|
+
*/
|
|
424
|
+
projectId?: string;
|
|
425
|
+
}
|
|
374
426
|
declare class RealtimeService {
|
|
375
427
|
private readonly client;
|
|
376
428
|
private _ws;
|
|
@@ -596,7 +648,20 @@ declare class RealtimeService {
|
|
|
596
648
|
* await aura.realtime.httpBroadcast(`${PROJECT_ID}:room-1`, 'cursor', { x: 10, y: 20 })
|
|
597
649
|
* ```
|
|
598
650
|
*/
|
|
599
|
-
|
|
651
|
+
/**
|
|
652
|
+
* Envoie un broadcast par HTTP (REST), sans connexion WebSocket ouverte
|
|
653
|
+
* (parité `@supabase/realtime-js`). Utile côté serveur/edge.
|
|
654
|
+
*
|
|
655
|
+
* POST `/v1/realtime/broadcast` avec le contrat du handler backend
|
|
656
|
+
* (`broadcast.rs`) : `{ channel, payload }`, `channel` préfixé par le projet,
|
|
657
|
+
* `event` replié dans `payload` (aligné sur le broadcast WS).
|
|
658
|
+
*
|
|
659
|
+
* Le canal peut être fourni :
|
|
660
|
+
* - sous forme courte (`'room-1'`) si une session utilisateur est présente,
|
|
661
|
+
* - sous forme courte avec l'option `{ projectId: '<uuid>' }`,
|
|
662
|
+
* - ou déjà qualifié (`'<project_id>:room-1'`).
|
|
663
|
+
*/
|
|
664
|
+
httpBroadcast(channel: string, event: string, payload?: Record<string, unknown>, options?: HttpBroadcastOptions): Promise<AuraResponse<null>>;
|
|
600
665
|
/**
|
|
601
666
|
* Schedule proactive token refresh at 80% of the JWT TTL.
|
|
602
667
|
* Prevents token expiration during an active WebSocket session.
|
|
@@ -628,4 +693,4 @@ declare class RealtimeService {
|
|
|
628
693
|
private _dispatch;
|
|
629
694
|
}
|
|
630
695
|
|
|
631
|
-
export { AuraChannel, type ConnectionState, DEFAULT_SUBSCRIBE_TIMEOUT_MS, HEARTBEAT_INTERVAL_MS, HeartbeatManager, RealtimeConnectionError, type RealtimeErrorPhase, RealtimeService };
|
|
696
|
+
export { AuraChannel, type AuraChannelSubscription, type ConnectionState, DEFAULT_SUBSCRIBE_TIMEOUT_MS, HEARTBEAT_INTERVAL_MS, HeartbeatManager, type HttpBroadcastOptions, RealtimeConnectionError, type RealtimeErrorPhase, RealtimeService };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
import { ChannelConfig, PostgresChangesFilter,
|
|
1
|
+
import { ChannelConfig, PostgresChangesFilter, RealtimePostgresInsertPayload, RealtimePostgresUpdatePayload, RealtimePostgresDeletePayload, RealtimePostgresChangesPayload, BroadcastFilter, RealtimeBroadcastEvent, PresenceFilter, PresenceState, RealtimeEvent, RealtimeListenType, SubscribeCallback, BroadcastPayload, BroadcastSendStatus, ServerMessage, AuraClientLike, AuraResponse } from '@aurabase/core';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Fonction de désabonnement retournée par `.on()`, enrichie avec les méthodes du canal pour permettre le chaînage fluide.
|
|
5
|
+
*/
|
|
6
|
+
type AuraChannelSubscription = (() => void) & AuraChannel;
|
|
3
7
|
type ListenerEntry = {
|
|
8
|
+
id: number;
|
|
4
9
|
type: 'postgres_changes';
|
|
5
10
|
filter: PostgresChangesFilter;
|
|
6
|
-
callback: (payload:
|
|
11
|
+
callback: (payload: RealtimePostgresChangesPayload<Record<string, unknown>>) => void;
|
|
7
12
|
} | {
|
|
13
|
+
id: number;
|
|
8
14
|
type: 'broadcast';
|
|
9
15
|
filter: BroadcastFilter;
|
|
10
|
-
callback: (payload:
|
|
16
|
+
callback: (payload: RealtimeBroadcastEvent<Record<string, unknown>>) => void;
|
|
11
17
|
} | {
|
|
18
|
+
id: number;
|
|
12
19
|
type: 'presence';
|
|
13
20
|
filter: PresenceFilter;
|
|
14
21
|
callback: (payload: PresenceState | RealtimeEvent | Record<string, unknown>) => void;
|
|
@@ -52,6 +59,7 @@ declare class AuraChannel {
|
|
|
52
59
|
readonly name: string;
|
|
53
60
|
private readonly _projectPrefix;
|
|
54
61
|
private readonly _listeners;
|
|
62
|
+
private _listenerIdCounter;
|
|
55
63
|
private _subscribed;
|
|
56
64
|
private _sendFn;
|
|
57
65
|
private _subscribeCallback;
|
|
@@ -101,12 +109,49 @@ declare class AuraChannel {
|
|
|
101
109
|
/** Full channel name with project prefix (for server protocol) */
|
|
102
110
|
get fullName(): string;
|
|
103
111
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
112
|
+
* Enregistre un listener pour les événements PostgreSQL (CDC) avec filtrage INSERT.
|
|
113
|
+
* Typé spécifiquement avec `new: TRow` et `old: null`.
|
|
114
|
+
*/
|
|
115
|
+
on<TRow = Record<string, unknown>>(type: 'postgres_changes', filter: PostgresChangesFilter & {
|
|
116
|
+
event: 'INSERT';
|
|
117
|
+
}, callback: (payload: RealtimePostgresInsertPayload<TRow>) => void): AuraChannelSubscription;
|
|
118
|
+
/**
|
|
119
|
+
* Enregistre un listener pour les événements PostgreSQL (CDC) avec filtrage UPDATE.
|
|
120
|
+
* Typé spécifiquement avec `new: TRow` et `old: Partial<TRow>`.
|
|
121
|
+
*/
|
|
122
|
+
on<TRow = Record<string, unknown>>(type: 'postgres_changes', filter: PostgresChangesFilter & {
|
|
123
|
+
event: 'UPDATE';
|
|
124
|
+
}, callback: (payload: RealtimePostgresUpdatePayload<TRow>) => void): AuraChannelSubscription;
|
|
125
|
+
/**
|
|
126
|
+
* Enregistre un listener pour les événements PostgreSQL (CDC) avec filtrage DELETE.
|
|
127
|
+
* Typé spécifiquement avec `new: null` et `old: Partial<TRow> | null`.
|
|
128
|
+
*/
|
|
129
|
+
on<TRow = Record<string, unknown>>(type: 'postgres_changes', filter: PostgresChangesFilter & {
|
|
130
|
+
event: 'DELETE';
|
|
131
|
+
}, callback: (payload: RealtimePostgresDeletePayload<TRow>) => void): AuraChannelSubscription;
|
|
132
|
+
/**
|
|
133
|
+
* Enregistre un listener pour les événements PostgreSQL (CDC) (INSERT, UPDATE, DELETE ou '*').
|
|
134
|
+
* Typé comme union discriminée par `eventType`.
|
|
135
|
+
*/
|
|
136
|
+
on<TRow = Record<string, unknown>>(type: 'postgres_changes', filter: PostgresChangesFilter, callback: (payload: RealtimePostgresChangesPayload<TRow>) => void): AuraChannelSubscription;
|
|
137
|
+
/**
|
|
138
|
+
* Enregistre un listener broadcast avec typage générique du payload applicatif.
|
|
139
|
+
* Permet l'accès direct et typé à `({ payload }) => payload.x` ainsi qu'aux métadonnées.
|
|
106
140
|
*/
|
|
107
|
-
on(type: '
|
|
108
|
-
|
|
109
|
-
|
|
141
|
+
on<TPayload = Record<string, unknown>>(type: 'broadcast', filter: BroadcastFilter, callback: (payload: RealtimeBroadcastEvent<TPayload>) => void): AuraChannelSubscription;
|
|
142
|
+
/**
|
|
143
|
+
* Enregistre un listener pour la présence.
|
|
144
|
+
*/
|
|
145
|
+
on(type: 'presence', filter: PresenceFilter, callback: (payload: PresenceState | RealtimeEvent | Record<string, unknown>) => void): AuraChannelSubscription;
|
|
146
|
+
/**
|
|
147
|
+
* Supprime un ou plusieurs listeners du canal sans clore le canal ni couper le WebSocket.
|
|
148
|
+
*
|
|
149
|
+
* - `off(type, filter, callback)` : supprime le listener exact.
|
|
150
|
+
* - `off(type, filter)` : supprime les listeners correspondant à ce type et ce filtre.
|
|
151
|
+
* - `off(type)` : supprime tous les listeners du type spécifié.
|
|
152
|
+
* - `off()` : supprime tous les listeners locaux du canal.
|
|
153
|
+
*/
|
|
154
|
+
off(type?: RealtimeListenType, filter?: PostgresChangesFilter | BroadcastFilter | PresenceFilter, callback?: (payload: unknown) => void): this;
|
|
110
155
|
/**
|
|
111
156
|
* Subscribe to the channel. Sends the Subscribe message to the server
|
|
112
157
|
* with the full config (postgres_changes, broadcast, presence).
|
|
@@ -371,6 +416,13 @@ declare const HEARTBEAT_INTERVAL_MS = 25000;
|
|
|
371
416
|
* })
|
|
372
417
|
* ```
|
|
373
418
|
*/
|
|
419
|
+
interface HttpBroadcastOptions {
|
|
420
|
+
/**
|
|
421
|
+
* Identifiant UUID du projet pour qualifier le canal lorsque le SDK fonctionne sans session
|
|
422
|
+
* utilisateur (ex: côté serveur ou edge avec une clé API service_role).
|
|
423
|
+
*/
|
|
424
|
+
projectId?: string;
|
|
425
|
+
}
|
|
374
426
|
declare class RealtimeService {
|
|
375
427
|
private readonly client;
|
|
376
428
|
private _ws;
|
|
@@ -596,7 +648,20 @@ declare class RealtimeService {
|
|
|
596
648
|
* await aura.realtime.httpBroadcast(`${PROJECT_ID}:room-1`, 'cursor', { x: 10, y: 20 })
|
|
597
649
|
* ```
|
|
598
650
|
*/
|
|
599
|
-
|
|
651
|
+
/**
|
|
652
|
+
* Envoie un broadcast par HTTP (REST), sans connexion WebSocket ouverte
|
|
653
|
+
* (parité `@supabase/realtime-js`). Utile côté serveur/edge.
|
|
654
|
+
*
|
|
655
|
+
* POST `/v1/realtime/broadcast` avec le contrat du handler backend
|
|
656
|
+
* (`broadcast.rs`) : `{ channel, payload }`, `channel` préfixé par le projet,
|
|
657
|
+
* `event` replié dans `payload` (aligné sur le broadcast WS).
|
|
658
|
+
*
|
|
659
|
+
* Le canal peut être fourni :
|
|
660
|
+
* - sous forme courte (`'room-1'`) si une session utilisateur est présente,
|
|
661
|
+
* - sous forme courte avec l'option `{ projectId: '<uuid>' }`,
|
|
662
|
+
* - ou déjà qualifié (`'<project_id>:room-1'`).
|
|
663
|
+
*/
|
|
664
|
+
httpBroadcast(channel: string, event: string, payload?: Record<string, unknown>, options?: HttpBroadcastOptions): Promise<AuraResponse<null>>;
|
|
600
665
|
/**
|
|
601
666
|
* Schedule proactive token refresh at 80% of the JWT TTL.
|
|
602
667
|
* Prevents token expiration during an active WebSocket session.
|
|
@@ -628,4 +693,4 @@ declare class RealtimeService {
|
|
|
628
693
|
private _dispatch;
|
|
629
694
|
}
|
|
630
695
|
|
|
631
|
-
export { AuraChannel, type ConnectionState, DEFAULT_SUBSCRIBE_TIMEOUT_MS, HEARTBEAT_INTERVAL_MS, HeartbeatManager, RealtimeConnectionError, type RealtimeErrorPhase, RealtimeService };
|
|
696
|
+
export { AuraChannel, type AuraChannelSubscription, type ConnectionState, DEFAULT_SUBSCRIBE_TIMEOUT_MS, HEARTBEAT_INTERVAL_MS, HeartbeatManager, type HttpBroadcastOptions, RealtimeConnectionError, type RealtimeErrorPhase, RealtimeService };
|
package/dist/index.js
CHANGED
|
@@ -224,6 +224,7 @@ var AuraChannel = class {
|
|
|
224
224
|
name;
|
|
225
225
|
_projectPrefix;
|
|
226
226
|
_listeners = [];
|
|
227
|
+
_listenerIdCounter = 0;
|
|
227
228
|
_subscribed = false;
|
|
228
229
|
_sendFn = null;
|
|
229
230
|
_subscribeCallback = null;
|
|
@@ -285,8 +286,69 @@ var AuraChannel = class {
|
|
|
285
286
|
get fullName() {
|
|
286
287
|
return `${this._projectPrefix()}${this.name}`;
|
|
287
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* Signature d'implémentation de `.on()`.
|
|
291
|
+
* Retourne une fonction de désabonnement direct `() => void` qui proxy également l'instance du canal pour le chaînage fluide.
|
|
292
|
+
*/
|
|
288
293
|
on(type, filter, callback) {
|
|
289
|
-
|
|
294
|
+
const id = ++this._listenerIdCounter;
|
|
295
|
+
this._listeners.push({
|
|
296
|
+
id,
|
|
297
|
+
type,
|
|
298
|
+
filter,
|
|
299
|
+
callback
|
|
300
|
+
});
|
|
301
|
+
const unsubscribe = () => {
|
|
302
|
+
const idx = this._listeners.findIndex((l) => l.id === id);
|
|
303
|
+
if (idx !== -1) {
|
|
304
|
+
this._listeners.splice(idx, 1);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
return new Proxy(unsubscribe, {
|
|
308
|
+
get: (_target, prop) => {
|
|
309
|
+
const val = this[prop];
|
|
310
|
+
if (typeof val === "function") {
|
|
311
|
+
return (...args) => Reflect.apply(val, this, args);
|
|
312
|
+
}
|
|
313
|
+
return val;
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Supprime un ou plusieurs listeners du canal sans clore le canal ni couper le WebSocket.
|
|
319
|
+
*
|
|
320
|
+
* - `off(type, filter, callback)` : supprime le listener exact.
|
|
321
|
+
* - `off(type, filter)` : supprime les listeners correspondant à ce type et ce filtre.
|
|
322
|
+
* - `off(type)` : supprime tous les listeners du type spécifié.
|
|
323
|
+
* - `off()` : supprime tous les listeners locaux du canal.
|
|
324
|
+
*/
|
|
325
|
+
off(type, filter, callback) {
|
|
326
|
+
if (!type) {
|
|
327
|
+
this._listeners.length = 0;
|
|
328
|
+
return this;
|
|
329
|
+
}
|
|
330
|
+
const filtered = this._listeners.filter((l) => {
|
|
331
|
+
if (l.type !== type) return true;
|
|
332
|
+
if (filter) {
|
|
333
|
+
const filterMeta = filter;
|
|
334
|
+
const listenerFilter = l.filter;
|
|
335
|
+
if ("event" in filter && "event" in l.filter && listenerFilter.event !== filterMeta.event) {
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
if ("schema" in filter && listenerFilter.schema !== filterMeta.schema) {
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
if ("table" in filter && listenerFilter.table !== filterMeta.table) {
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (callback && l.callback !== callback) {
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
return false;
|
|
349
|
+
});
|
|
350
|
+
this._listeners.length = 0;
|
|
351
|
+
this._listeners.push(...filtered);
|
|
290
352
|
return this;
|
|
291
353
|
}
|
|
292
354
|
// ─── Subscribe ──────────────────────────────────────────────────────────
|
|
@@ -696,32 +758,57 @@ var AuraChannel = class {
|
|
|
696
758
|
}
|
|
697
759
|
}
|
|
698
760
|
const cdcPayload = payload && typeof payload === "object" ? payload : {};
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
761
|
+
const rawRecord = cdcPayload.record ?? cdcPayload.new ?? null;
|
|
762
|
+
const rawOldRecord = cdcPayload.old_record ?? cdcPayload.old ?? null;
|
|
763
|
+
const rawType = cdcPayload.type ?? cdcPayload.eventType;
|
|
764
|
+
const eventTypeString = typeof rawType === "string" ? rawType : typeof eventType === "string" ? eventType : "INSERT";
|
|
765
|
+
const cdcType = eventTypeString.toUpperCase();
|
|
766
|
+
const recordNew = cdcType === "DELETE" ? null : rawRecord;
|
|
767
|
+
const recordOld = cdcType === "INSERT" ? null : rawOldRecord;
|
|
768
|
+
const enrichedPayload = {
|
|
769
|
+
...cdcPayload,
|
|
770
|
+
record: rawRecord,
|
|
771
|
+
old_record: rawOldRecord,
|
|
772
|
+
new: recordNew,
|
|
773
|
+
old: recordOld,
|
|
774
|
+
eventType: cdcType,
|
|
775
|
+
schema: cdcPayload.schema ?? filter.schema ?? "public",
|
|
776
|
+
table: cdcPayload.table ?? filter.table ?? "",
|
|
777
|
+
commit_timestamp: cdcPayload.commit_timestamp ?? event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
778
|
+
};
|
|
779
|
+
const enrichedEvent = {
|
|
780
|
+
...event,
|
|
781
|
+
schema: cdcPayload.schema ?? filter.schema ?? "public",
|
|
782
|
+
table: cdcPayload.table ?? filter.table ?? "",
|
|
783
|
+
commit_timestamp: cdcPayload.commit_timestamp ?? event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
784
|
+
errors: cdcPayload.errors,
|
|
785
|
+
id: event.id,
|
|
786
|
+
topic: event.topic,
|
|
787
|
+
timestamp: event.timestamp,
|
|
788
|
+
eventType: cdcType,
|
|
789
|
+
event: cdcType === "INSERT" ? "insert" : cdcType === "UPDATE" ? "update" : "delete",
|
|
790
|
+
new: recordNew,
|
|
791
|
+
old: recordOld,
|
|
792
|
+
payload: enrichedPayload
|
|
793
|
+
};
|
|
715
794
|
listener.callback(enrichedEvent);
|
|
716
795
|
}
|
|
717
796
|
}
|
|
718
797
|
} else if (listener.type === "broadcast") {
|
|
719
798
|
if (eventType === "broadcast") {
|
|
720
799
|
const filter = listener.filter;
|
|
721
|
-
const broadcastPayload = event.payload && typeof event.payload === "object" ? event.payload :
|
|
722
|
-
const broadcastEvent = broadcastPayload
|
|
800
|
+
const broadcastPayload = event.payload && typeof event.payload === "object" ? event.payload : {};
|
|
801
|
+
const broadcastEvent = broadcastPayload.event ?? "broadcast";
|
|
723
802
|
if (!filter.event || filter.event === "*" || broadcastEvent === filter.event) {
|
|
724
|
-
|
|
803
|
+
const enrichedBroadcast = {
|
|
804
|
+
id: event.id,
|
|
805
|
+
topic: event.topic,
|
|
806
|
+
timestamp: event.timestamp,
|
|
807
|
+
allowed_sub_ids: event.allowed_sub_ids,
|
|
808
|
+
event: broadcastEvent,
|
|
809
|
+
payload: broadcastPayload
|
|
810
|
+
};
|
|
811
|
+
listener.callback(enrichedBroadcast);
|
|
725
812
|
}
|
|
726
813
|
}
|
|
727
814
|
} else if (listener.type === "presence") {
|
|
@@ -1370,8 +1457,25 @@ var RealtimeService = class _RealtimeService {
|
|
|
1370
1457
|
* await aura.realtime.httpBroadcast(`${PROJECT_ID}:room-1`, 'cursor', { x: 10, y: 20 })
|
|
1371
1458
|
* ```
|
|
1372
1459
|
*/
|
|
1373
|
-
|
|
1374
|
-
|
|
1460
|
+
/**
|
|
1461
|
+
* Envoie un broadcast par HTTP (REST), sans connexion WebSocket ouverte
|
|
1462
|
+
* (parité `@supabase/realtime-js`). Utile côté serveur/edge.
|
|
1463
|
+
*
|
|
1464
|
+
* POST `/v1/realtime/broadcast` avec le contrat du handler backend
|
|
1465
|
+
* (`broadcast.rs`) : `{ channel, payload }`, `channel` préfixé par le projet,
|
|
1466
|
+
* `event` replié dans `payload` (aligné sur le broadcast WS).
|
|
1467
|
+
*
|
|
1468
|
+
* Le canal peut être fourni :
|
|
1469
|
+
* - sous forme courte (`'room-1'`) si une session utilisateur est présente,
|
|
1470
|
+
* - sous forme courte avec l'option `{ projectId: '<uuid>' }`,
|
|
1471
|
+
* - ou déjà qualifié (`'<project_id>:room-1'`).
|
|
1472
|
+
*/
|
|
1473
|
+
httpBroadcast(channel, event, payload = {}, options) {
|
|
1474
|
+
const explicitProjectId = options?.projectId ?? this.client.projectId;
|
|
1475
|
+
let prefix = this._projectPrefix();
|
|
1476
|
+
if (!prefix && explicitProjectId) {
|
|
1477
|
+
prefix = explicitProjectId.endsWith(":") ? explicitProjectId : `${explicitProjectId}:`;
|
|
1478
|
+
}
|
|
1375
1479
|
let fullChannel;
|
|
1376
1480
|
if (prefix) {
|
|
1377
1481
|
fullChannel = channel.startsWith(prefix) ? channel : `${prefix}${channel}`;
|
|
@@ -1383,7 +1487,7 @@ var RealtimeService = class _RealtimeService {
|
|
|
1383
1487
|
error: new AuraError(0, {
|
|
1384
1488
|
error: {
|
|
1385
1489
|
code: "realtime_channel_non_qualifie",
|
|
1386
|
-
message: `httpBroadcast: le canal "${channel}" n'est pas qualifi\xE9 et aucune session n'est ouverte \u2014 le SDK n'a donc aucun \`project_id\` \xE0 lui pr\xE9fixer, alors que le serveur l'EXIGE (403 \xAB Le canal n'appartient pas \xE0 ce projet \xBB). Passez le canal sous la forme "<project_id>:` + channel + `" \u2014 c'est la forme attendue c\xF4t\xE9 serveur/edge, o\xF9 il n'y a pas de session utilisateur.`
|
|
1490
|
+
message: `httpBroadcast: le canal "${channel}" n'est pas qualifi\xE9 et aucune session n'est ouverte \u2014 le SDK n'a donc aucun \`project_id\` \xE0 lui pr\xE9fixer, alors que le serveur l'EXIGE (403 \xAB Le canal n'appartient pas \xE0 ce projet \xBB). Passez le canal sous la forme "<project_id>:` + channel + `" ou sp\xE9cifiez l'option { projectId: "<uuid>" } \u2014 c'est la forme attendue c\xF4t\xE9 serveur/edge, o\xF9 il n'y a pas de session utilisateur.`
|
|
1387
1491
|
}
|
|
1388
1492
|
})
|
|
1389
1493
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aurabase/realtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Service temps réel du SDK Aurabase (postgres_changes, broadcast, presence)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"realtime"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@aurabase/core": "0.
|
|
48
|
+
"@aurabase/core": "0.6.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/node": "20.14.15",
|