@holo-js/flux 0.1.4 → 0.1.6
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 +15 -8
- package/dist/index.mjs +111 -4
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -13,11 +13,13 @@ type ManifestPresenceMember<TManifest extends GeneratedBroadcastManifest, TPatte
|
|
|
13
13
|
member: infer TMember;
|
|
14
14
|
} ? TMember : BroadcastJsonObject;
|
|
15
15
|
type ManifestWhisperName<TManifest extends GeneratedBroadcastManifest, TPattern extends string> = ManifestChannelEntryByPattern<TManifest, TPattern>['whispers'][number] & string;
|
|
16
|
-
type ManifestEventNamesForPattern<TManifest extends GeneratedBroadcastManifest, TPattern extends string> =
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
type ManifestEventNamesForPattern<TManifest extends GeneratedBroadcastManifest, TPattern extends string> = TManifest['events'][number] extends infer TEvent ? TEvent extends {
|
|
17
|
+
readonly name: infer TName;
|
|
18
|
+
readonly channels: readonly {
|
|
19
|
+
readonly pattern: infer TEventPattern;
|
|
19
20
|
}[];
|
|
20
|
-
}
|
|
21
|
+
} ? TPattern extends TEventPattern & string ? TName & string : never : never : never;
|
|
22
|
+
type ManifestSubscriptionEventName<TManifest extends GeneratedBroadcastManifest, TChannel extends string> = string extends ManifestEventName<TManifest> ? string : TChannel extends ManifestChannelPattern<TManifest> ? ManifestEventNamesForPattern<TManifest, TChannel> : ManifestEventName<TManifest>;
|
|
21
23
|
interface FluxClientOptions<TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest> {
|
|
22
24
|
readonly manifest?: TManifest;
|
|
23
25
|
readonly connection?: string;
|
|
@@ -33,6 +35,11 @@ interface FluxListenerControls {
|
|
|
33
35
|
interface FluxPresenceState<TMember = unknown> {
|
|
34
36
|
readonly members: readonly TMember[];
|
|
35
37
|
}
|
|
38
|
+
interface FluxPresenceListenerControls<TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest, TChannel extends string = string> {
|
|
39
|
+
here(callback: (members: readonly ManifestPresenceMember<TManifest, TChannel>[]) => void): FluxPresenceSubscription<TManifest, TChannel>;
|
|
40
|
+
joining(callback: (member: ManifestPresenceMember<TManifest, TChannel>) => void): FluxPresenceSubscription<TManifest, TChannel>;
|
|
41
|
+
leaving(callback: (member: ManifestPresenceMember<TManifest, TChannel>) => void): FluxPresenceSubscription<TManifest, TChannel>;
|
|
42
|
+
}
|
|
36
43
|
interface FluxConnectionControls {
|
|
37
44
|
connect(): Promise<void>;
|
|
38
45
|
disconnect(): Promise<void>;
|
|
@@ -60,12 +67,12 @@ interface FluxConnector {
|
|
|
60
67
|
interface FluxSubscription<TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest, TChannel extends string = string> extends FluxListenerControls {
|
|
61
68
|
readonly name: TChannel;
|
|
62
69
|
readonly type: FluxChannelKind;
|
|
63
|
-
listen<TEvent extends
|
|
70
|
+
listen<TEvent extends ManifestSubscriptionEventName<TManifest, TChannel>>(event?: TEvent | readonly TEvent[], callback?: (payload: BroadcastJsonObject) => void): FluxSubscription<TManifest, TChannel>;
|
|
64
71
|
notification(callback: (payload: BroadcastJsonObject) => void): FluxSubscription<TManifest, TChannel>;
|
|
65
72
|
listenForWhisper<TWhisper extends ManifestWhisperName<TManifest, TChannel>>(name: TWhisper, callback: (payload: BroadcastJsonObject) => void): FluxSubscription<TManifest, TChannel>;
|
|
66
73
|
whisper<TWhisper extends ManifestWhisperName<TManifest, TChannel>>(name: TWhisper, payload: BroadcastJsonObject): Promise<void>;
|
|
67
74
|
}
|
|
68
|
-
interface FluxPresenceSubscription<TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest, TChannel extends string = string> extends FluxSubscription<TManifest, TChannel>, FluxPresenceState<ManifestPresenceMember<TManifest, TChannel
|
|
75
|
+
interface FluxPresenceSubscription<TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest, TChannel extends string = string> extends FluxSubscription<TManifest, TChannel>, FluxPresenceState<ManifestPresenceMember<TManifest, TChannel>>, FluxPresenceListenerControls<TManifest, TChannel> {
|
|
69
76
|
}
|
|
70
77
|
interface FluxClient<TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest> extends FluxConnectionControls {
|
|
71
78
|
readonly options: Readonly<FluxClientOptions<TManifest>>;
|
|
@@ -94,7 +101,7 @@ declare function createSubscription<TManifest extends GeneratedBroadcastManifest
|
|
|
94
101
|
readonly __onPresenceChange: (callback: (members: readonly BroadcastJsonObject[]) => void) => () => void;
|
|
95
102
|
};
|
|
96
103
|
declare function createPresenceSubscription<TManifest extends GeneratedBroadcastManifest, TChannel extends string>(name: TChannel, connector: FluxConnector, registry: SubscriptionRegistry): FluxPresenceSubscription<TManifest, TChannel>;
|
|
97
|
-
declare function createFluxClient<TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest>(options?: FluxClientOptions<TManifest>): FluxClient<TManifest> & ConnectorDebugCarrier;
|
|
104
|
+
declare function createFluxClient<const TManifest extends GeneratedBroadcastManifest = GeneratedBroadcastManifest>(options?: FluxClientOptions<TManifest>): FluxClient<TManifest> & ConnectorDebugCarrier;
|
|
98
105
|
declare function configureFluxClient(options: FluxClientOptions | FluxClient): FluxClient;
|
|
99
106
|
declare function getFluxClient(): FluxClient;
|
|
100
107
|
declare function resetFluxClient(): void;
|
|
@@ -106,4 +113,4 @@ declare const fluxInternals: {
|
|
|
106
113
|
createSubscription: typeof createSubscription;
|
|
107
114
|
};
|
|
108
115
|
|
|
109
|
-
export { type FluxChannelKind, type FluxClient, type FluxClientOptions, type FluxConnectionControls, type FluxConnectionStatus, type FluxConnector, type FluxConnectorChannel, type FluxListenerControls, type FluxPresenceState, type FluxPresenceSubscription, type FluxSubscription, configureFluxClient, createFluxClient, flux as default, flux, fluxInternals, getFluxClient, resetFluxClient };
|
|
116
|
+
export { type FluxChannelKind, type FluxClient, type FluxClientOptions, type FluxConnectionControls, type FluxConnectionStatus, type FluxConnector, type FluxConnectorChannel, type FluxListenerControls, type FluxPresenceListenerControls, type FluxPresenceState, type FluxPresenceSubscription, type FluxSubscription, configureFluxClient, createFluxClient, flux as default, flux, fluxInternals, getFluxClient, resetFluxClient };
|
package/dist/index.mjs
CHANGED
|
@@ -9,6 +9,40 @@ function normalizeRequiredString(value, label) {
|
|
|
9
9
|
function toReadonlyArray(value) {
|
|
10
10
|
return Array.isArray(value) ? [...value] : [value];
|
|
11
11
|
}
|
|
12
|
+
function presenceMemberKey(member) {
|
|
13
|
+
return JSON.stringify(member) ?? String(member);
|
|
14
|
+
}
|
|
15
|
+
function presenceMemberDiff(previousMembers, nextMembers) {
|
|
16
|
+
const previousCounts = /* @__PURE__ */ new Map();
|
|
17
|
+
const nextCounts = /* @__PURE__ */ new Map();
|
|
18
|
+
for (const member of previousMembers) {
|
|
19
|
+
const key = presenceMemberKey(member);
|
|
20
|
+
previousCounts.set(key, (previousCounts.get(key) ?? 0) + 1);
|
|
21
|
+
}
|
|
22
|
+
for (const member of nextMembers) {
|
|
23
|
+
const key = presenceMemberKey(member);
|
|
24
|
+
nextCounts.set(key, (nextCounts.get(key) ?? 0) + 1);
|
|
25
|
+
}
|
|
26
|
+
const joining = nextMembers.filter((member) => {
|
|
27
|
+
const key = presenceMemberKey(member);
|
|
28
|
+
const previousCount = previousCounts.get(key) ?? 0;
|
|
29
|
+
if (previousCount > 0) {
|
|
30
|
+
previousCounts.set(key, previousCount - 1);
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
});
|
|
35
|
+
const leaving = previousMembers.filter((member) => {
|
|
36
|
+
const key = presenceMemberKey(member);
|
|
37
|
+
const nextCount = nextCounts.get(key) ?? 0;
|
|
38
|
+
if (nextCount > 0) {
|
|
39
|
+
nextCounts.set(key, nextCount - 1);
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
});
|
|
44
|
+
return { joining, leaving };
|
|
45
|
+
}
|
|
12
46
|
function addCallback(map, event, callback) {
|
|
13
47
|
const listeners = map.get(event) ?? /* @__PURE__ */ new Set();
|
|
14
48
|
listeners.add(callback);
|
|
@@ -256,8 +290,8 @@ function createSubscription(channelName, kind, connector, registry) {
|
|
|
256
290
|
registeredSubscriptions.delete(leaveChannel);
|
|
257
291
|
if (registeredSubscriptions.size === 0) {
|
|
258
292
|
registry.delete(registryKey);
|
|
293
|
+
connectorChannel.leave();
|
|
259
294
|
}
|
|
260
|
-
connectorChannel.leave();
|
|
261
295
|
};
|
|
262
296
|
const leaveRelated = () => {
|
|
263
297
|
for (const leave of [...registry.get(registryKey) ?? []]) {
|
|
@@ -296,19 +330,92 @@ function createSubscription(channelName, kind, connector, registry) {
|
|
|
296
330
|
return connectorChannel.members;
|
|
297
331
|
},
|
|
298
332
|
__onPresenceChange(callback) {
|
|
299
|
-
|
|
333
|
+
const stop = connectorChannel.onMembersChange(callback);
|
|
334
|
+
detachCallbacks.add(stop);
|
|
335
|
+
return () => {
|
|
336
|
+
detachCallbacks.delete(stop);
|
|
337
|
+
stop();
|
|
338
|
+
};
|
|
300
339
|
}
|
|
301
340
|
};
|
|
302
341
|
return Object.freeze(subscription);
|
|
303
342
|
}
|
|
304
343
|
function createPresenceSubscription(name, connector, registry) {
|
|
305
344
|
const base = createSubscription(name, "presence", connector, registry);
|
|
306
|
-
|
|
307
|
-
|
|
345
|
+
const joiningCallbacks = /* @__PURE__ */ new Set();
|
|
346
|
+
const leavingCallbacks = /* @__PURE__ */ new Set();
|
|
347
|
+
let active = true;
|
|
348
|
+
let previousMembers = base.__presenceMembers();
|
|
349
|
+
const stopPresenceChanges = base.__onPresenceChange((nextMembers) => {
|
|
350
|
+
if (!active) {
|
|
351
|
+
previousMembers = nextMembers;
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const diff = presenceMemberDiff(previousMembers, nextMembers);
|
|
355
|
+
previousMembers = nextMembers;
|
|
356
|
+
for (const member of diff.joining) {
|
|
357
|
+
for (const callback of joiningCallbacks) {
|
|
358
|
+
callback(member);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
for (const member of diff.leaving) {
|
|
362
|
+
for (const callback of leavingCallbacks) {
|
|
363
|
+
callback(member);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
const stopPresenceSubscription = () => {
|
|
368
|
+
active = false;
|
|
369
|
+
joiningCallbacks.clear();
|
|
370
|
+
leavingCallbacks.clear();
|
|
371
|
+
stopPresenceChanges();
|
|
372
|
+
};
|
|
373
|
+
const subscription = Object.freeze({
|
|
374
|
+
name: base.name,
|
|
375
|
+
type: base.type,
|
|
308
376
|
get members() {
|
|
309
377
|
return base.__presenceMembers();
|
|
378
|
+
},
|
|
379
|
+
here(callback) {
|
|
380
|
+
callback(subscription.members);
|
|
381
|
+
return subscription;
|
|
382
|
+
},
|
|
383
|
+
joining(callback) {
|
|
384
|
+
joiningCallbacks.add(callback);
|
|
385
|
+
return subscription;
|
|
386
|
+
},
|
|
387
|
+
leaving(callback) {
|
|
388
|
+
leavingCallbacks.add(callback);
|
|
389
|
+
return subscription;
|
|
390
|
+
},
|
|
391
|
+
leaveChannel() {
|
|
392
|
+
stopPresenceSubscription();
|
|
393
|
+
base.leaveChannel();
|
|
394
|
+
},
|
|
395
|
+
leave() {
|
|
396
|
+
stopPresenceSubscription();
|
|
397
|
+
base.leave();
|
|
398
|
+
},
|
|
399
|
+
stopListening() {
|
|
400
|
+
active = false;
|
|
401
|
+
base.stopListening();
|
|
402
|
+
},
|
|
403
|
+
listen(event, callback) {
|
|
404
|
+
active = true;
|
|
405
|
+
base.listen(event, callback);
|
|
406
|
+
return subscription;
|
|
407
|
+
},
|
|
408
|
+
notification(callback) {
|
|
409
|
+
return base.notification(callback);
|
|
410
|
+
},
|
|
411
|
+
listenForWhisper(name2, callback) {
|
|
412
|
+
return base.listenForWhisper(name2, callback);
|
|
413
|
+
},
|
|
414
|
+
async whisper(name2, payload) {
|
|
415
|
+
await base.whisper(name2, payload);
|
|
310
416
|
}
|
|
311
417
|
});
|
|
418
|
+
return subscription;
|
|
312
419
|
}
|
|
313
420
|
function createFluxClient(options = {}) {
|
|
314
421
|
const connector = options.connector ?? options.connectorFactory?.(options) ?? createUnavailableConnector();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holo-js/flux",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Holo-JS Framework - framework-agnostic realtime client skeleton for broadcast and presence",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,16 +19,16 @@
|
|
|
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.
|
|
26
|
+
"@holo-js/broadcast": "^0.1.6"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^22.10.2",
|
|
30
30
|
"tsup": "^8.3.5",
|
|
31
31
|
"typescript": "^5.7.2",
|
|
32
|
-
"vitest": "^
|
|
32
|
+
"vitest": "^4.1.5"
|
|
33
33
|
}
|
|
34
34
|
}
|