@rebasepro/client 0.9.1-canary.fd3754b → 0.10.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/auth.d.ts +5 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.es.js +799 -95
- package/dist/index.es.js.map +1 -1
- package/dist/realtime-channel.d.ts +243 -0
- package/dist/transport.d.ts +34 -0
- package/dist/websocket.d.ts +96 -2
- package/package.json +11 -10
- package/src/auth.ts +32 -0
- package/src/collection.ts +16 -0
- package/src/index.ts +105 -2
- package/src/realtime-channel.test.ts +542 -0
- package/src/realtime-channel.ts +539 -0
- package/src/realtime-optout.test.ts +245 -0
- package/src/realtime-row-identity.test.ts +254 -0
- package/src/sdk_query_builder.ts +4 -1
- package/src/transport-baseurl.test.ts +53 -0
- package/src/transport.ts +59 -3
- package/src/websocket.ts +515 -88
- package/dist/collection.test.d.ts +0 -1
- package/dist/cron.test.d.ts +0 -1
- package/dist/data-proxy.test.d.ts +0 -1
- package/dist/index.umd.js +0 -2513
- package/dist/index.umd.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { createFunctionsClient } from "./functions";
|
|
|
10
10
|
import { createStorage } from "./storage";
|
|
11
11
|
import { ClientStorageSourceRegistry } from "./storage-registry";
|
|
12
12
|
import { RebaseWebSocketClient } from "./websocket";
|
|
13
|
+
import { RebaseRealtimeChannel, type ChannelOptions } from "./realtime-channel";
|
|
13
14
|
import {
|
|
14
15
|
DEFAULT_STORAGE_SOURCE_KEY,
|
|
15
16
|
InsertOf,
|
|
@@ -76,6 +77,16 @@ export type { FunctionInvokeOptions, FunctionsClient } from "./functions";
|
|
|
76
77
|
// but re-exported (see @internal on the class) because the `client-postgres`
|
|
77
78
|
// driver constructs it directly. Not a stable app-facing API.
|
|
78
79
|
export { RebaseWebSocketClient } from "./websocket";
|
|
80
|
+
export { RebaseRealtimeChannel } from "./realtime-channel";
|
|
81
|
+
export type {
|
|
82
|
+
PresenceState,
|
|
83
|
+
PresenceDiff,
|
|
84
|
+
BroadcastEvent,
|
|
85
|
+
ChannelTransport,
|
|
86
|
+
ChannelOptions,
|
|
87
|
+
ChannelHistoryEntry,
|
|
88
|
+
ChannelHistoryResult
|
|
89
|
+
} from "./realtime-channel";
|
|
79
90
|
|
|
80
91
|
export interface CreateRebaseClientOptions extends RebaseClientConfig {
|
|
81
92
|
auth?: CreateAuthOptions;
|
|
@@ -142,11 +153,38 @@ export type CreateRebaseClientResult<DB = Record<string, unknown>> = Omit<Rebase
|
|
|
142
153
|
apiKeys: ReturnType<typeof createApiKeys>;
|
|
143
154
|
functions: ReturnType<typeof createFunctionsClient>;
|
|
144
155
|
ws?: RebaseWebSocketClient;
|
|
156
|
+
/**
|
|
157
|
+
* Broadcast and presence channels.
|
|
158
|
+
*
|
|
159
|
+
* Was missing from this type while present on the returned object, which
|
|
160
|
+
* made `client.realtime.channel(...)` a type error and forced every adopter
|
|
161
|
+
* to cast around the feature before they could reach it.
|
|
162
|
+
*/
|
|
163
|
+
realtime: {
|
|
164
|
+
/**
|
|
165
|
+
* Join a broadcast/presence channel. Repeated calls with the same name
|
|
166
|
+
* return the same channel object. Throws only when the client was
|
|
167
|
+
* created with `realtime: false`.
|
|
168
|
+
*
|
|
169
|
+
* Pass `{ history: true }` to have the channel replay what it missed on
|
|
170
|
+
* join and on every reconnect, for channels the server retains.
|
|
171
|
+
*/
|
|
172
|
+
channel: (name: string, options?: ChannelOptions) => RebaseRealtimeChannel;
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* Release the realtime socket and its reconnect timer.
|
|
176
|
+
*
|
|
177
|
+
* An open socket keeps the Node event loop alive, so a script that does not
|
|
178
|
+
* call this will not exit on its own. Safe when realtime was never started
|
|
179
|
+
* (`realtime: false`), and safe to call twice.
|
|
180
|
+
*/
|
|
181
|
+
close: () => void;
|
|
145
182
|
storage: StorageSource;
|
|
146
183
|
storageRegistry: StorageSourceRegistry;
|
|
147
184
|
createStorageSource: (storageId: string) => StorageSource;
|
|
148
185
|
fetchStorageSources: () => Promise<StorageSourceDefinition[]>;
|
|
149
186
|
call: <T = unknown>(endpoint: string, payload?: unknown) => Promise<T>;
|
|
187
|
+
collection: <M extends Record<string, unknown> = Record<string, unknown>>(slug: string) => CollectionClient<M>;
|
|
150
188
|
data: TypedDataLayer<DB>;
|
|
151
189
|
};
|
|
152
190
|
|
|
@@ -238,9 +276,17 @@ export function createRebaseClient<DB = Record<string, unknown>>(options: Create
|
|
|
238
276
|
return storageSourcesPromise;
|
|
239
277
|
};
|
|
240
278
|
|
|
241
|
-
|
|
279
|
+
// Opting out has to happen before the URL is derived: `deriveWebSocketUrl`
|
|
280
|
+
// always produces one, so a truthy check alone can never leave the socket
|
|
281
|
+
// closed.
|
|
282
|
+
const realtimeEnabled = options.realtime !== false;
|
|
283
|
+
const resolvedWsUrl = realtimeEnabled
|
|
284
|
+
? (options.websocketUrl ?? deriveWebSocketUrl(options.baseUrl))
|
|
285
|
+
: undefined;
|
|
242
286
|
|
|
243
287
|
let ws: RebaseWebSocketClient | undefined;
|
|
288
|
+
/** One channel object per name — see `realtime.channel`. */
|
|
289
|
+
const realtimeChannels = new Map<string, RebaseRealtimeChannel>();
|
|
244
290
|
if (resolvedWsUrl) {
|
|
245
291
|
const wsOnUnauthorized = options.onUnauthorized || (async () => {
|
|
246
292
|
try {
|
|
@@ -268,9 +314,15 @@ export function createRebaseClient<DB = Record<string, unknown>>(options: Create
|
|
|
268
314
|
auth.onAuthStateChange((event, session) => {
|
|
269
315
|
if (!ws) return;
|
|
270
316
|
if (event === "SIGNED_OUT") {
|
|
317
|
+
// Not permanent: the client stays usable, and a later subscribe
|
|
318
|
+
// should reconnect anonymously.
|
|
271
319
|
ws.disconnect();
|
|
272
320
|
} else if (event === "SIGNED_IN" || event === "TOKEN_REFRESHED") {
|
|
273
|
-
|
|
321
|
+
// Only re-authenticate a socket that already exists. Signing in
|
|
322
|
+
// is not a request for realtime, and dialling here would undo
|
|
323
|
+
// lazy connect for every app with a login. A socket opened
|
|
324
|
+
// later authenticates itself from `getAuthToken` on open.
|
|
325
|
+
if (session?.accessToken && ws.hasSocket) {
|
|
274
326
|
ws.authenticate(session.accessToken).catch(console.warn);
|
|
275
327
|
}
|
|
276
328
|
}
|
|
@@ -408,6 +460,57 @@ export function createRebaseClient<DB = Record<string, unknown>>(options: Create
|
|
|
408
460
|
createStorageSource,
|
|
409
461
|
fetchStorageSources,
|
|
410
462
|
ws,
|
|
463
|
+
realtime: {
|
|
464
|
+
/**
|
|
465
|
+
* Join a broadcast/presence channel.
|
|
466
|
+
*
|
|
467
|
+
* Repeated calls with the same name return the same channel, so
|
|
468
|
+
* separate components can attach handlers without each opening its
|
|
469
|
+
* own membership — and `leave()` from one would otherwise silently
|
|
470
|
+
* cut off the others.
|
|
471
|
+
*/
|
|
472
|
+
channel: (name: string, options?: ChannelOptions): RebaseRealtimeChannel => {
|
|
473
|
+
// Only `realtime: false` gets here — a hard opt-out, so this
|
|
474
|
+
// stays an error. Being merely *unconnected* does not: the
|
|
475
|
+
// socket opens on the first channel operation, which is the
|
|
476
|
+
// whole point of asking for a channel before you use one.
|
|
477
|
+
if (!ws) {
|
|
478
|
+
throw new RebaseClientError(
|
|
479
|
+
"Realtime is disabled on this client (realtime: false), so channels are unavailable."
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
let existing = realtimeChannels.get(name);
|
|
483
|
+
if (!existing) {
|
|
484
|
+
existing = new RebaseRealtimeChannel(name, ws, options);
|
|
485
|
+
realtimeChannels.set(name, existing);
|
|
486
|
+
} else if (options?.history) {
|
|
487
|
+
// Same object by name, so options on a later call have no
|
|
488
|
+
// new channel to apply to. Asking for history upgrades the
|
|
489
|
+
// one that exists rather than being quietly ignored — but
|
|
490
|
+
// never the reverse, so a caller that omits the option
|
|
491
|
+
// cannot switch it off under one that asked for it.
|
|
492
|
+
existing.enableHistory();
|
|
493
|
+
}
|
|
494
|
+
return existing;
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
/**
|
|
498
|
+
* Release the realtime socket and its reconnect timer.
|
|
499
|
+
*
|
|
500
|
+
* Until this returns, the open socket keeps the Node event loop alive
|
|
501
|
+
* and the process will not exit on its own. Safe to call when realtime
|
|
502
|
+
* was never started, and safe to call twice.
|
|
503
|
+
*/
|
|
504
|
+
close: () => {
|
|
505
|
+
// Channels hold presence heartbeat timers, which would otherwise
|
|
506
|
+
// keep firing (and keep a Node process alive) after the socket
|
|
507
|
+
// they publish over is gone.
|
|
508
|
+
for (const channel of realtimeChannels.values()) void channel.leave();
|
|
509
|
+
realtimeChannels.clear();
|
|
510
|
+
// Permanent: nothing queued afterwards may redial and keep the
|
|
511
|
+
// event loop alive, which is the reason this method exists.
|
|
512
|
+
ws?.disconnect(true);
|
|
513
|
+
},
|
|
411
514
|
setToken: transport.setToken,
|
|
412
515
|
setAuthTokenGetter: transport.setAuthTokenGetter,
|
|
413
516
|
setOnUnauthorized: transport.setOnUnauthorized,
|