@ignex/nova 0.1.6 → 0.1.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ignex/nova",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "description": "TypeBox-driven FlatBuffer transport: Rust FFI serializer + Bun WebSocket + typed pub/sub API for server and FE.",
6
6
  "license": "MIT",
package/public/events.ts CHANGED
@@ -38,6 +38,7 @@ export {
38
38
  emitToGroup,
39
39
  emitToTopic,
40
40
  emitToUser,
41
+ emitToUserAnywhere,
41
42
  getEventsHub,
42
43
  isEventsBound,
43
44
  off,
@@ -146,7 +146,13 @@ export function createEmitter(opts: EmitterOptions): EmitEngine {
146
146
  if (cluster) {
147
147
  const msgId = crypto.randomUUID();
148
148
  const traceId = parentTraceId ?? "";
149
- if (opts.routeInstances !== undefined && (target.type === "client" || target.type === "user")) {
149
+ // `user anywhere: true` forces the full-mesh wildcard publish instead
150
+ // of presence routing — the user is reached on EVERY instance/service in
151
+ // the mesh, regardless of where presence thinks they are.
152
+ const routeable =
153
+ target.type === "client" ||
154
+ (target.type === "user" && target.anywhere !== true);
155
+ if (opts.routeInstances !== undefined && routeable) {
150
156
  // ROUTED targeted delivery: only the owning instances receive it
151
157
  const instances = opts.routeInstances(target);
152
158
  if (instances !== null) {
@@ -68,6 +68,18 @@ export function emitToUser<K extends EventName>(userId: string, name: K, payload
68
68
  requireHub().emitToUser(userId, name as never, payload as never);
69
69
  }
70
70
 
71
+ /**
72
+ * Deliver to the user on EVERY instance/service in the cluster mesh (full
73
+ * mesh, no presence routing) — reaches the user wherever they are connected.
74
+ */
75
+ export function emitToUserAnywhere<K extends EventName>(
76
+ userId: string,
77
+ name: K,
78
+ payload: Events[K],
79
+ ): void {
80
+ requireHub().emitToUserAnywhere(userId, name as never, payload as never);
81
+ }
82
+
71
83
  export function emitToClient<K extends EventName>(
72
84
  clientId: string,
73
85
  name: K,
@@ -38,6 +38,8 @@ export function createContextFactory<B extends Bindings>(deps: {
38
38
  emit: (name, payload, target) => hub.emit(name as never, payload as never, target),
39
39
  emitToGroup: (group, name, payload) => hub.emitToGroup(group, name as never, payload as never),
40
40
  emitToUser: (userId, name, payload) => hub.emitToUser(userId, name as never, payload as never),
41
+ emitToUserAnywhere: (userId, name, payload) =>
42
+ hub.emitToUserAnywhere(userId, name as never, payload as never),
41
43
  emitToClient: (clientId, name, payload) =>
42
44
  hub.emitToClient(clientId, name as never, payload as never),
43
45
  emitToTopic: (topic, name, payload) => hub.emitToTopic(topic, name as never, payload as never),
@@ -364,6 +364,14 @@ export function createEventsHub<B extends Bindings = DefaultBindings>(
364
364
  emitToUser(userId, name, payload) {
365
365
  api.emit(name, payload, { type: "user", userId });
366
366
  },
367
+ /**
368
+ * Deliver to the user on EVERY instance/service in the cluster mesh — an
369
+ * explicit full-mesh emit (no presence routing). Use when the user may be
370
+ * connected to any service sharing the cluster transport.
371
+ */
372
+ emitToUserAnywhere(userId, name, payload) {
373
+ api.emit(name, payload, { type: "user", userId, anywhere: true });
374
+ },
367
375
  emitToClient(clientId, name, payload) {
368
376
  api.emit(name, payload, { type: "client", clientId });
369
377
  },
@@ -23,6 +23,7 @@ export {
23
23
  emitToGroup,
24
24
  emitToTopic,
25
25
  emitToUser,
26
+ emitToUserAnywhere,
26
27
  getEventsHub,
27
28
  isEventsBound,
28
29
  off,
@@ -33,6 +33,8 @@ export interface EventContext<B extends Bindings = DefaultBindings> {
33
33
  emit<K extends EventNameOf<B>>(name: K, payload: EventsOf<B>[K], target?: EmitTarget): void;
34
34
  emitToGroup<K extends EventNameOf<B>>(group: string, name: K, payload: EventsOf<B>[K]): void;
35
35
  emitToUser<K extends EventNameOf<B>>(userId: string, name: K, payload: EventsOf<B>[K]): void;
36
+ /** Deliver to the user on every instance/service in the cluster mesh. */
37
+ emitToUserAnywhere<K extends EventNameOf<B>>(userId: string, name: K, payload: EventsOf<B>[K]): void;
36
38
  emitToClient<K extends EventNameOf<B>>(clientId: string, name: K, payload: EventsOf<B>[K]): void;
37
39
  emitToTopic<K extends EventNameOf<B>>(topic: string, name: K, payload: EventsOf<B>[K]): void;
38
40
  }
@@ -13,6 +13,9 @@
13
13
  * - `{ type: "topic", topic }` — subscribers of a topic (rooms + replay).
14
14
  * - `{ type: "group", group }` — members of a server-side group.
15
15
  * - `{ type: "user", userId }` — every socket acting on behalf of `userId`.
16
+ * - `{ type: "user", userId, anywhere: true }` — same, but ALWAYS fanned out
17
+ * to every instance/service in the cluster mesh (ignores presence routing), so
18
+ * the user is reached no matter which instance holds their socket.
16
19
  * - `{ type: "client", clientId }` — one specific connection.
17
20
  *
18
21
  * Local delivery is synchronous and allocation-free (the transport scratch +
@@ -23,7 +26,7 @@ export type EmitTarget =
23
26
  | { type: "broadcast" }
24
27
  | { type: "topic"; topic: string }
25
28
  | { type: "group"; group: string }
26
- | { type: "user"; userId: string }
29
+ | { type: "user"; userId: string; anywhere?: true }
27
30
  | { type: "client"; clientId: string };
28
31
 
29
32
  export type EmitTargetKind = EmitTarget["type"];
@@ -45,6 +45,11 @@ export interface EventsHub<B extends Bindings = DefaultBindings> {
45
45
  emitToTopic<K extends EventNameOf<B>>(topic: string, name: K, payload: EventsOf<B>[K]): void;
46
46
  emitToGroup<K extends EventNameOf<B>>(group: string, name: K, payload: EventsOf<B>[K]): void;
47
47
  emitToUser<K extends EventNameOf<B>>(userId: string, name: K, payload: EventsOf<B>[K]): void;
48
+ /**
49
+ * Deliver to the user on EVERY instance/service in the cluster mesh (full
50
+ * mesh, no presence routing) — reaches the user wherever they are.
51
+ */
52
+ emitToUserAnywhere<K extends EventNameOf<B>>(userId: string, name: K, payload: EventsOf<B>[K]): void;
48
53
  emitToClient<K extends EventNameOf<B>>(clientId: string, name: K, payload: EventsOf<B>[K]): void;
49
54
 
50
55
  // ── client records ("who is connected, on whose behalf") ──────────────