ras-stack 0.24.1 → 0.25.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/README.md CHANGED
@@ -236,7 +236,7 @@ const subscription = useRealtimeSubscription({ client, channel, options, configu
236
236
  const clients = useRealtimePresence(subscription)
237
237
  ```
238
238
 
239
- The factory, subscription options, and configure callback should have stable identities and change only when their corresponding lifecycle should restart.
239
+ The client factory may return a client or a promise, so applications can fetch and validate an initial ticket before connecting. Pass `onError` as the third argument to handle asynchronous ticket failures. A client that resolves after unmount is disconnected without ever connecting. The factory, error handler, subscription options, and configure callback should have stable identities and change only when their corresponding lifecycle should restart.
240
240
 
241
241
  The client helpers return the underlying Centrifuge client and subscription. React ownership, channel conventions, ticket validation, event parsing, presence models, and query invalidation remain application code. `publish()` returns `false` when the publisher is closed, disabled, or at capacity. `close()` rejects new work and waits for accepted publications and their bounded retries to finish.
242
242
 
@@ -1,5 +1,8 @@
1
1
  import type { Centrifuge, ClientInfo, Subscription, SubscriptionOptions } from 'centrifuge';
2
- export declare function useConnectedRealtimeClient(create: () => Centrifuge, enabled?: boolean): Centrifuge | undefined;
2
+ export declare function useConnectedRealtimeClient(create: () => Centrifuge | Promise<Centrifuge>, enabled?: boolean, options?: {
3
+ configure?: (client: Centrifuge) => void | (() => void);
4
+ onError?: (error: unknown) => void;
5
+ }): Centrifuge | undefined;
3
6
  export type RealtimeSubscriptionHookOptions = {
4
7
  client?: Centrifuge;
5
8
  channel?: string;
@@ -1,16 +1,49 @@
1
1
  import { useEffect, useState } from 'react';
2
2
  import { connectRealtimeClient, openRealtimeSubscription, watchSubscriptionPresence } from './client.js';
3
- export function useConnectedRealtimeClient(create, enabled = true) {
3
+ export function useConnectedRealtimeClient(create, enabled = true, options = {}) {
4
4
  const [client, setClient] = useState();
5
+ const configure = options.configure;
6
+ const onError = options.onError;
5
7
  useEffect(() => {
6
8
  if (!enabled) {
7
9
  setClient(undefined);
8
10
  return undefined;
9
11
  }
10
- const next = create();
11
- setClient(next);
12
- return connectRealtimeClient(next);
13
- }, [create, enabled]);
12
+ let active = true;
13
+ let cleanup;
14
+ let disconnect;
15
+ const connect = (next) => {
16
+ if (!active) {
17
+ next.disconnect();
18
+ return;
19
+ }
20
+ try {
21
+ cleanup = configure?.(next) ?? undefined;
22
+ }
23
+ catch (error) {
24
+ next.disconnect();
25
+ onError?.(error);
26
+ return;
27
+ }
28
+ setClient(next);
29
+ disconnect = connectRealtimeClient(next);
30
+ };
31
+ try {
32
+ const created = create();
33
+ if (created instanceof Promise)
34
+ void created.then(connect, (error) => active && onError?.(error));
35
+ else
36
+ connect(created);
37
+ }
38
+ catch (error) {
39
+ onError?.(error);
40
+ }
41
+ return () => {
42
+ active = false;
43
+ cleanup?.();
44
+ disconnect?.();
45
+ };
46
+ }, [configure, create, enabled, onError]);
14
47
  return client;
15
48
  }
16
49
  export function useRealtimeSubscription(options) {
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","sourceRoot":"","sources":["../../src/realtime/react.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAC3C,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAA;AAExG,MAAM,UAAU,0BAA0B,CAAC,MAAwB,EAAE,OAAO,GAAG,IAAI;IACjF,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,EAAc,CAAA;IAClD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,SAAS,CAAC,SAAS,CAAC,CAAA;YACpB,OAAO,SAAS,CAAA;QAClB,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAA;QACrB,SAAS,CAAC,IAAI,CAAC,CAAA;QACf,OAAO,qBAAqB,CAAC,IAAI,CAAC,CAAA;IACpC,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACrB,OAAO,MAAM,CAAA;AACf,CAAC;AAUD,MAAM,UAAU,uBAAuB,CAAC,OAAwC;IAC9E,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,EAAgB,CAAA;IAChE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,EAAE,OAAO,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAA;IAC5F,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACpC,eAAe,CAAC,SAAS,CAAC,CAAA;YAC1B,OAAO,SAAS,CAAA;QAClB,CAAC;QACD,MAAM,MAAM,GAAG,wBAAwB,CAAC,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,SAAS,CAAC,CAAA;QACxF,eAAe,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;QACpC,OAAO,MAAM,CAAC,KAAK,CAAA;IACrB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAA;IAC9D,OAAO,YAAY,CAAA;AACrB,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,YAAsC,EAAE,OAAO,GAA2C,EAAE;IAC9H,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAA6B,EAAE,CAAC,CAAA;IACtE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAA;IAC/B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,UAAU,CAAC,EAAE,CAAC,CAAA;YACd,OAAO,SAAS,CAAA;QAClB,CAAC;QACD,IAAI,MAAM,GAAG,IAAI,CAAA;QACjB,MAAM,IAAI,GAAG,yBAAyB,CACpC,YAAY,EACZ,CAAC,IAAI,EAAE,EAAE;YACP,IAAI,MAAM;gBAAE,UAAU,CAAC,IAAI,CAAC,CAAA;QAC9B,CAAC,EACD,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAC3B,CAAA;QACD,OAAO,GAAG,EAAE;YACV,MAAM,GAAG,KAAK,CAAA;YACd,IAAI,EAAE,CAAA;QACR,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAA;IAC3B,OAAO,OAAO,CAAA;AAChB,CAAC","sourcesContent":["import type { Centrifuge, ClientInfo, Subscription, SubscriptionOptions } from 'centrifuge'\nimport { useEffect, useState } from 'react'\nimport { connectRealtimeClient, openRealtimeSubscription, watchSubscriptionPresence } from './client.js'\n\nexport function useConnectedRealtimeClient(create: () => Centrifuge, enabled = true) {\n const [client, setClient] = useState<Centrifuge>()\n useEffect(() => {\n if (!enabled) {\n setClient(undefined)\n return undefined\n }\n const next = create()\n setClient(next)\n return connectRealtimeClient(next)\n }, [create, enabled])\n return client\n}\n\nexport type RealtimeSubscriptionHookOptions = {\n client?: Centrifuge\n channel?: string\n enabled?: boolean\n options?: SubscriptionOptions\n configure?: (subscription: Subscription) => void | (() => void)\n}\n\nexport function useRealtimeSubscription(options: RealtimeSubscriptionHookOptions) {\n const [subscription, setSubscription] = useState<Subscription>()\n const { channel, client, configure, enabled = true, options: subscriptionOptions } = options\n useEffect(() => {\n if (!enabled || !client || !channel) {\n setSubscription(undefined)\n return undefined\n }\n const opened = openRealtimeSubscription(client, channel, subscriptionOptions, configure)\n setSubscription(opened.subscription)\n return opened.close\n }, [channel, client, configure, enabled, subscriptionOptions])\n return subscription\n}\n\nexport function useRealtimePresence(subscription: Subscription | undefined, options: { onError?: (error: unknown) => void } = {}) {\n const [clients, setClients] = useState<Record<string, ClientInfo>>({})\n const onError = options.onError\n useEffect(() => {\n if (!subscription) {\n setClients({})\n return undefined\n }\n let active = true\n const stop = watchSubscriptionPresence(\n subscription,\n (next) => {\n if (active) setClients(next)\n },\n onError ? { onError } : {},\n )\n return () => {\n active = false\n stop()\n }\n }, [onError, subscription])\n return clients\n}\n"]}
1
+ {"version":3,"file":"react.js","sourceRoot":"","sources":["../../src/realtime/react.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Centrifuge, ClientInfo, Subscription, SubscriptionOptions } from 'centrifuge'\nimport { useEffect, useState } from 'react'\nimport { connectRealtimeClient, openRealtimeSubscription, watchSubscriptionPresence } from './client.js'\n\nexport function useConnectedRealtimeClient(\n create: () => Centrifuge | Promise<Centrifuge>,\n enabled = true,\n options: { configure?: (client: Centrifuge) => void | (() => void); onError?: (error: unknown) => void } = {},\n) {\n const [client, setClient] = useState<Centrifuge>()\n const configure = options.configure\n const onError = options.onError\n useEffect(() => {\n if (!enabled) {\n setClient(undefined)\n return undefined\n }\n let active = true\n let cleanup: (() => void) | undefined\n let disconnect: (() => void) | undefined\n const connect = (next: Centrifuge) => {\n if (!active) {\n next.disconnect()\n return\n }\n try {\n cleanup = configure?.(next) ?? undefined\n } catch (error) {\n next.disconnect()\n onError?.(error)\n return\n }\n setClient(next)\n disconnect = connectRealtimeClient(next)\n }\n try {\n const created = create()\n if (created instanceof Promise) void created.then(connect, (error: unknown) => active && onError?.(error))\n else connect(created)\n } catch (error) {\n onError?.(error)\n }\n return () => {\n active = false\n cleanup?.()\n disconnect?.()\n }\n }, [configure, create, enabled, onError])\n return client\n}\n\nexport type RealtimeSubscriptionHookOptions = {\n client?: Centrifuge\n channel?: string\n enabled?: boolean\n options?: SubscriptionOptions\n configure?: (subscription: Subscription) => void | (() => void)\n}\n\nexport function useRealtimeSubscription(options: RealtimeSubscriptionHookOptions) {\n const [subscription, setSubscription] = useState<Subscription>()\n const { channel, client, configure, enabled = true, options: subscriptionOptions } = options\n useEffect(() => {\n if (!enabled || !client || !channel) {\n setSubscription(undefined)\n return undefined\n }\n const opened = openRealtimeSubscription(client, channel, subscriptionOptions, configure)\n setSubscription(opened.subscription)\n return opened.close\n }, [channel, client, configure, enabled, subscriptionOptions])\n return subscription\n}\n\nexport function useRealtimePresence(subscription: Subscription | undefined, options: { onError?: (error: unknown) => void } = {}) {\n const [clients, setClients] = useState<Record<string, ClientInfo>>({})\n const onError = options.onError\n useEffect(() => {\n if (!subscription) {\n setClients({})\n return undefined\n }\n let active = true\n const stop = watchSubscriptionPresence(\n subscription,\n (next) => {\n if (active) setClients(next)\n },\n onError ? { onError } : {},\n )\n return () => {\n active = false\n stop()\n }\n }, [onError, subscription])\n return clients\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ras-stack",
3
- "version": "0.24.1",
3
+ "version": "0.25.0",
4
4
  "description": "Composable full-stack primitives shared across Richard Solomou's applications.",
5
5
  "keywords": [
6
6
  "authentication",