@holo-js/flux-react 0.1.4 → 0.1.5

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.d.ts CHANGED
@@ -1,6 +1,26 @@
1
1
  import { FluxClient, FluxConnectionStatus, FluxListenerControls } from '@holo-js/flux';
2
- import { GeneratedBroadcastManifest, BroadcastPayloadFor } from '@holo-js/broadcast';
2
+ import { GeneratedBroadcastManifest, BroadcastPayloadFor, BroadcastJsonObject } from '@holo-js/broadcast';
3
3
 
4
+ type ManifestEventName<TManifest extends GeneratedBroadcastManifest> = TManifest['events'][number]['name'] & string;
5
+ type ManifestChannelPattern<TManifest extends GeneratedBroadcastManifest> = TManifest['channels'][number]['pattern'] & string;
6
+ type ManifestChannelEntryByPattern<TManifest extends GeneratedBroadcastManifest, TPattern extends string> = Extract<TManifest['channels'][number], {
7
+ pattern: TPattern;
8
+ }>;
9
+ type ManifestPresenceMember<TManifest extends GeneratedBroadcastManifest, TPattern extends string> = Extract<ManifestChannelEntryByPattern<TManifest, TPattern>, {
10
+ member: unknown;
11
+ }> extends {
12
+ member: infer TMember;
13
+ } ? TMember : BroadcastJsonObject;
14
+ type ManifestEventNamesForPattern<TManifest extends GeneratedBroadcastManifest, TPattern extends string> = TManifest['events'][number] extends infer TEvent ? TEvent extends {
15
+ readonly name: infer TName;
16
+ readonly channels: readonly {
17
+ readonly pattern: infer TEventPattern;
18
+ }[];
19
+ } ? TPattern extends TEventPattern & string ? TName & string : never : never : never;
20
+ type ManifestSubscriptionEventName<TManifest extends GeneratedBroadcastManifest, TChannel extends string> = string extends ManifestEventName<TManifest> ? string : TChannel extends ManifestChannelPattern<TManifest> ? ManifestEventNamesForPattern<TManifest, TChannel> : never;
21
+ type ManifestHookChannel<TManifest extends GeneratedBroadcastManifest> = string extends ManifestChannelPattern<TManifest> ? string : ManifestChannelPattern<TManifest>;
22
+ type ManifestHookEvent<TManifest extends GeneratedBroadcastManifest, TChannel extends string, TEvent extends string> = TEvent & ManifestSubscriptionEventName<TManifest, TChannel>;
23
+ type ManifestHookPresenceMember<TMember, TManifest extends GeneratedBroadcastManifest, TChannel extends string> = unknown extends TMember ? string extends ManifestChannelPattern<TManifest> ? BroadcastJsonObject : ManifestPresenceMember<TManifest, TChannel> : TMember;
4
24
  interface FluxHookOptions<TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest> {
5
25
  readonly client?: FluxClient<TManifest>;
6
26
  readonly onUnmount?: (cleanup: () => void) => void;
@@ -14,12 +34,12 @@ interface FluxPresenceHookCallbacks<TMember = unknown> {
14
34
  type FluxPresenceHookState<TMember = unknown> = FluxListenerControls & {
15
35
  readonly members: readonly TMember[];
16
36
  };
17
- declare function useFlux<TEvent extends string, TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest>(channel: string, events: TEvent | readonly TEvent[], callback: (payload: BroadcastPayloadFor<TEvent>) => void, options?: FluxHookOptions<TManifest>): FluxListenerControls;
18
- declare function useFluxPublic<TEvent extends string, TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest>(channel: string, events: TEvent | readonly TEvent[], callback: (payload: BroadcastPayloadFor<TEvent>) => void, options?: FluxHookOptions<TManifest>): FluxListenerControls;
19
- declare function useFluxPrivate<TEvent extends string, TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest>(channel: string, events: TEvent | readonly TEvent[], callback: (payload: BroadcastPayloadFor<TEvent>) => void, options?: FluxHookOptions<TManifest>): FluxListenerControls;
20
- declare function useFluxPresence<TMember = unknown, TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest>(channel: string, callbacks?: FluxPresenceHookCallbacks<TMember>, options?: FluxHookOptions<TManifest>): FluxPresenceHookState<TMember>;
21
- declare function useFluxNotification<TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest>(channel: string, callback: (payload: unknown) => void, options?: FluxHookOptions<TManifest>): FluxListenerControls;
22
- declare function useFluxModel<TEvent extends string, TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest>(channel: string, events: TEvent | readonly TEvent[], callback: (payload: BroadcastPayloadFor<TEvent>) => void, options?: FluxHookOptions<TManifest>): FluxListenerControls;
37
+ declare function useFlux<TEvent extends string, TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest, TChannel extends ManifestHookChannel<TManifest> = ManifestHookChannel<TManifest>>(channel: TChannel & ManifestHookChannel<NoInfer<TManifest>>, events: ManifestHookEvent<NoInfer<TManifest>, TChannel, TEvent> | readonly ManifestHookEvent<NoInfer<TManifest>, TChannel, TEvent>[], callback: (payload: BroadcastPayloadFor<TEvent>) => void, options?: FluxHookOptions<TManifest>): FluxListenerControls;
38
+ declare function useFluxPublic<TEvent extends string, TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest, TChannel extends ManifestHookChannel<TManifest> = ManifestHookChannel<TManifest>>(channel: TChannel & ManifestHookChannel<NoInfer<TManifest>>, events: ManifestHookEvent<NoInfer<TManifest>, TChannel, TEvent> | readonly ManifestHookEvent<NoInfer<TManifest>, TChannel, TEvent>[], callback: (payload: BroadcastPayloadFor<TEvent>) => void, options?: FluxHookOptions<TManifest>): FluxListenerControls;
39
+ declare function useFluxPrivate<TEvent extends string, TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest, TChannel extends ManifestHookChannel<TManifest> = ManifestHookChannel<TManifest>>(channel: TChannel & ManifestHookChannel<NoInfer<TManifest>>, events: ManifestHookEvent<NoInfer<TManifest>, TChannel, TEvent> | readonly ManifestHookEvent<NoInfer<TManifest>, TChannel, TEvent>[], callback: (payload: BroadcastPayloadFor<TEvent>) => void, options?: FluxHookOptions<TManifest>): FluxListenerControls;
40
+ declare function useFluxPresence<TMember = unknown, TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest, TChannel extends ManifestHookChannel<TManifest> = ManifestHookChannel<TManifest>>(channel: TChannel & ManifestHookChannel<NoInfer<TManifest>>, callbacks?: FluxPresenceHookCallbacks<ManifestHookPresenceMember<TMember, TManifest, TChannel>>, options?: FluxHookOptions<TManifest>): FluxPresenceHookState<ManifestHookPresenceMember<TMember, TManifest, TChannel>>;
41
+ declare function useFluxNotification<TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest, TChannel extends ManifestHookChannel<TManifest> = ManifestHookChannel<TManifest>>(channel: TChannel & ManifestHookChannel<NoInfer<TManifest>>, callback: (payload: unknown) => void, options?: FluxHookOptions<TManifest>): FluxListenerControls;
42
+ declare function useFluxModel<TEvent extends string, TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest, TChannel extends ManifestHookChannel<TManifest> = ManifestHookChannel<TManifest>>(channel: TChannel & ManifestHookChannel<NoInfer<TManifest>>, events: ManifestHookEvent<NoInfer<TManifest>, TChannel, TEvent> | readonly ManifestHookEvent<NoInfer<TManifest>, TChannel, TEvent>[], callback: (payload: BroadcastPayloadFor<TEvent>) => void, options?: FluxHookOptions<TManifest>): FluxListenerControls;
23
43
  declare function useFluxConnectionStatus<TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest>(options?: FluxConnectionStatusHookOptions<TManifest>): FluxConnectionStatus;
24
44
 
25
45
  export { type FluxConnectionStatusHookOptions, type FluxHookOptions, type FluxPresenceHookCallbacks, type FluxPresenceHookState, useFlux, useFluxConnectionStatus, useFluxModel, useFluxNotification, useFluxPresence, useFluxPrivate, useFluxPublic };
package/dist/index.mjs CHANGED
@@ -23,6 +23,20 @@ function useLatestRef(value) {
23
23
  function serializeEventDependency(events) {
24
24
  return Array.isArray(events) ? events.map(String).join("\0") : String(events);
25
25
  }
26
+ function memberKey(member) {
27
+ return JSON.stringify(member) ?? String(member);
28
+ }
29
+ function appendPresenceMember(members, member) {
30
+ return Object.freeze([...members, member]);
31
+ }
32
+ function removePresenceMember(members, member) {
33
+ const key = memberKey(member);
34
+ const index = members.findIndex((candidate) => Object.is(candidate, member) || memberKey(candidate) === key);
35
+ if (index < 0) {
36
+ return members;
37
+ }
38
+ return Object.freeze(members.filter((_, candidateIndex) => candidateIndex !== index));
39
+ }
26
40
  function useControls(createSubscription, onUnmount, dependencies = []) {
27
41
  const controlsRef = useRef(createNoopControls());
28
42
  const onUnmountRef = useLatestRef(onUnmount);
@@ -105,32 +119,49 @@ function useFluxPresence(channel, callbacks = {}, options = {}) {
105
119
  const onUnmountRef = useLatestRef(options.onUnmount);
106
120
  useEffect(() => {
107
121
  const subscription = client.presence(channel);
122
+ let active = true;
108
123
  const updateMembers = (members) => {
109
124
  membersRef.current = members;
110
125
  callbacksRef.current.onHere?.(membersRef.current);
111
126
  rerender();
112
127
  };
113
- updateMembers(subscription.members);
114
- const stop = subscription.__onPresenceChange?.(updateMembers);
115
128
  const cleanup = () => {
116
- stop?.();
129
+ active = false;
117
130
  subscription.leaveChannel();
118
131
  };
119
132
  controlsRef.current = Object.freeze({
120
133
  leave: () => {
134
+ active = false;
121
135
  subscription.leave();
122
136
  },
123
137
  leaveChannel: () => {
138
+ active = false;
124
139
  subscription.leaveChannel();
125
140
  },
126
141
  listen: () => {
142
+ active = true;
127
143
  subscription.listen();
144
+ updateMembers(subscription.members);
128
145
  return controlsRef.current;
129
146
  },
130
147
  stopListening: () => {
148
+ active = false;
131
149
  subscription.stopListening();
132
150
  }
133
151
  });
152
+ subscription.here((members) => {
153
+ if (active) {
154
+ updateMembers(members);
155
+ }
156
+ }).joining((member) => {
157
+ if (active) {
158
+ updateMembers(appendPresenceMember(membersRef.current, member));
159
+ }
160
+ }).leaving((member) => {
161
+ if (active) {
162
+ updateMembers(removePresenceMember(membersRef.current, member));
163
+ }
164
+ }).listen();
134
165
  onUnmountRef.current?.(cleanup);
135
166
  return cleanup;
136
167
  }, [channel, client, callbacksRef, onUnmountRef]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holo-js/flux-react",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Holo-JS Framework - React and Next hook skeletons for Flux",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -19,24 +19,26 @@
19
19
  "scripts": {
20
20
  "build": "tsup",
21
21
  "stub": "tsup",
22
- "typecheck": "tsc -p tsconfig.json --noEmit",
22
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.type-tests.json --noEmit",
23
23
  "test": "vitest --run"
24
24
  },
25
25
  "dependencies": {
26
- "@holo-js/broadcast": "^0.1.4",
27
- "@holo-js/flux": "^0.1.4"
26
+ "@holo-js/broadcast": "^0.1.5",
27
+ "@holo-js/flux": "^0.1.5"
28
28
  },
29
29
  "peerDependencies": {
30
- "react": "^18.3.1 || ^19.0.0"
30
+ "react": "^19.2.6"
31
31
  },
32
32
  "devDependencies": {
33
- "@types/react": "^18.3.12",
34
- "@types/react-test-renderer": "^18.3.1",
33
+ "@types/react": "^19.2.14",
34
+ "@types/react-dom": "^19.2.3",
35
+ "@types/react-test-renderer": "^19.1.0",
35
36
  "@types/node": "^22.10.2",
36
- "react": "^18.3.1",
37
- "react-test-renderer": "^18.3.1",
37
+ "react": "^19.2.6",
38
+ "react-dom": "^19.2.6",
39
+ "react-test-renderer": "^19.2.6",
38
40
  "tsup": "^8.3.5",
39
41
  "typescript": "^5.7.2",
40
- "vitest": "^2.1.8"
42
+ "vitest": "^4.1.5"
41
43
  }
42
44
  }