@mauroandre/velojs 0.0.20 → 0.0.21

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
@@ -581,11 +581,12 @@ One line to declare, three verbs to use. Route at `/_event/admin/Provision/logs`
581
581
  export const stream_progress = createEventStream<DeployState>();
582
582
  ```
583
583
 
584
- **Standalone** — for cross-cutting streams (global metrics, notifications). Pass `path` and (if needed) `middlewares` explicitly.
584
+ **Standalone** — for cross-cutting streams (global metrics, notifications). Pass `path` and (if needed) `middlewares` explicitly. Use `broadcast: true` for streams without channels.
585
585
 
586
586
  ```typescript
587
587
  export const containerMetrics = createEventStream<Metric[]>({
588
588
  path: "/api/metrics/containers",
589
+ broadcast: true,
589
590
  middlewares: [authMiddleware],
590
591
  });
591
592
  ```
@@ -604,6 +605,7 @@ stream_logs.emit(sessionId, "Line", { snapshot: true });
604
605
  import { poll } from "@mauroandre/velojs";
605
606
 
606
607
  export const stream_metrics = createEventStream<Metric[]>({
608
+ broadcast: true,
607
609
  source: poll({
608
610
  intervalMs: 3000,
609
611
  tick: async (emit) => emit(await collectMetrics()),
@@ -626,7 +628,8 @@ export const stream_deploy = createEventStream<DeployState>({
626
628
  | Option | Type | Description |
627
629
  |--------|------|-------------|
628
630
  | `path` | `string` | (Standalone) Explicit URL path |
629
- | `channel` | `(c) => string` | Extract channel ID. Default: `?channel=...` query param |
631
+ | `broadcast` | `boolean` | If true, every emit goes to all subscribers (no channels). Default: false |
632
+ | `channel` | `(c) => string` | Extract channel ID. Default: `?channel=...` query param. Ignored when `broadcast: true` |
630
633
  | `snapshot` | `(channel) => TSnapshot` | Returns current state on connect (state-machine pattern) |
631
634
  | `closeOn` | `(event) => boolean` | Closes SSE when matching event is sent (declarative) |
632
635
  | `source` | `(emit, { abortSignal }) => Promise<void>` | Self-running producer, only runs while subscribed |
package/dist/events.d.ts CHANGED
@@ -78,8 +78,19 @@ export interface EventStreamConfig<TEvent, TSnapshot = TEvent> {
78
78
  * Subscribers only receive events emitted to their channel.
79
79
  * When omitted, defaults to `c.req.query("channel") ?? ""` so client-side
80
80
  * `useEventStream(stream, { channel })` works out of the box.
81
+ *
82
+ * Ignored when `broadcast: true` is set.
81
83
  */
82
84
  channel?: (c: Context) => string;
85
+ /**
86
+ * If true, this stream has no channels — every emit goes to all subscribers.
87
+ * - `emit(value, options?)` (no channel arg)
88
+ * - `useEventStream(stream)` (no channel option)
89
+ * - Server uses a single broadcast bucket regardless of `?channel=` query.
90
+ *
91
+ * Default: false (channel-aware — `emit(channel, value, options?)`).
92
+ */
93
+ broadcast?: boolean;
83
94
  /**
84
95
  * Returns the current state of a channel for snapshot-on-connect.
85
96
  * Sent before any new events to bring the client up to date.
@@ -126,9 +137,12 @@ export interface EventStreamConfig<TEvent, TSnapshot = TEvent> {
126
137
  retainMs?: number;
127
138
  }
128
139
  export interface EventStream<TEvent, TSnapshot = TEvent> {
129
- /** Broadcast emit (no channel) */
140
+ /**
141
+ * Emit an event.
142
+ * - For channel-aware streams (default): `emit(channel, value, options?)`
143
+ * - For broadcast streams (`config.broadcast: true`): `emit(value, options?)`
144
+ */
130
145
  emit(event: TEvent, options?: EmitOptions): void;
131
- /** Channel-targeted emit */
132
146
  emit(channel: string, event: TEvent, options?: EmitOptions): void;
133
147
  /**
134
148
  * Closes the stream (or a specific channel).
package/dist/events.js CHANGED
@@ -1,85 +1,88 @@
1
- const p = [];
1
+ const T = [];
2
2
  function m(t) {
3
- for (const r of p) r(t);
4
- p.length = 0;
3
+ for (const r of T) r(t);
4
+ T.length = 0;
5
5
  }
6
- function C(t = {}) {
7
- const r = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Set(), _ = /* @__PURE__ */ new Map();
8
- let u = null, S = 0;
9
- const b = () => {
10
- t.source && (u || (u = new AbortController(), Promise.resolve(
11
- t.source(A, { abortSignal: u.signal })
6
+ function N(t = {}) {
7
+ t.broadcast && t.channel && console.warn(
8
+ "[velojs] createEventStream: `broadcast: true` ignores `channel`. Remove one of them — broadcast streams have no channels."
9
+ );
10
+ const r = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Set(), u = /* @__PURE__ */ new Map();
11
+ let f = null, A = 0;
12
+ const d = () => {
13
+ t.source && (f || (f = new AbortController(), Promise.resolve(
14
+ t.source(E, { abortSignal: f.signal })
12
15
  ).catch((e) => {
13
16
  console.error("[velojs] event stream source threw:", e);
14
17
  })));
15
- }, h = () => {
16
- S > 0 || u && (u.abort(), u = null);
18
+ }, w = () => {
19
+ A > 0 || f && (f.abort(), f = null);
17
20
  };
18
- function A(e, i, s) {
19
- let o, l, d;
20
- if (!!t.channel ? (o = e, l = i, d = s) : (o = "", l = e, d = i), a.has(o)) {
21
+ function E(e, c, n) {
22
+ let s, l, h;
23
+ if (!!t.broadcast ? (s = "", l = e, h = c) : (s = e, l = c, h = n), a.has(s)) {
21
24
  console.warn(
22
- `[velojs] emit() called on closed channel "${o}" — ignored. Reopen the channel by emitting to a fresh ID, or remove the close() call.`
25
+ `[velojs] emit() called on closed channel "${s}" — ignored. Reopen the channel by emitting to a fresh ID, or remove the close() call.`
23
26
  );
24
27
  return;
25
28
  }
26
- if (d?.snapshot) {
27
- const T = n.get(o) ?? [];
28
- T.push(l), n.set(o, T);
29
+ if (h?.snapshot) {
30
+ const b = o.get(s) ?? [];
31
+ b.push(l), o.set(s, b);
29
32
  }
30
- const f = r.get(o);
31
- if (f)
32
- for (const T of f) T(l);
33
+ const v = r.get(s);
34
+ if (v)
35
+ for (const b of v) b(l);
33
36
  }
34
- function E(e = "") {
37
+ function i(e = "") {
35
38
  if (a.has(e)) return;
36
39
  a.add(e);
37
- const i = r.get(e);
38
- if (i)
39
- for (const l of i) l.close();
40
- const s = t.retainMs ?? 3e5, o = _.get(e);
41
- o && clearTimeout(o), _.set(
40
+ const c = r.get(e);
41
+ if (c)
42
+ for (const l of c) l.close();
43
+ const n = t.retainMs ?? 3e5, s = u.get(e);
44
+ s && clearTimeout(s), u.set(
42
45
  e,
43
46
  setTimeout(() => {
44
- n.delete(e), _.delete(e), a.delete(e);
45
- }, s)
47
+ o.delete(e), u.delete(e), a.delete(e);
48
+ }, n)
46
49
  );
47
50
  }
48
- const c = {
49
- emit: A,
50
- close: E,
51
+ const S = {
52
+ emit: E,
53
+ close: i,
51
54
  __isVeloEventStream: !0,
52
55
  __config: t,
53
56
  __listeners: r,
54
- __buffers: n,
57
+ __buffers: o,
55
58
  __path: t.path,
56
- __onConnect(e, i) {
57
- let s = r.get(e);
58
- s || (s = /* @__PURE__ */ new Set(), r.set(e, s)), s.add(i), S++, b();
59
+ __onConnect(e, c) {
60
+ let n = r.get(e);
61
+ n || (n = /* @__PURE__ */ new Set(), r.set(e, n)), n.add(c), A++, d();
59
62
  },
60
- __onDisconnect(e, i) {
61
- const s = r.get(e);
62
- s && (s.delete(i), s.size === 0 && r.delete(e), S--, h());
63
+ __onDisconnect(e, c) {
64
+ const n = r.get(e);
65
+ n && (n.delete(c), n.size === 0 && r.delete(e), A--, w());
63
66
  }
64
67
  };
65
- return t.path && p.push((e) => {
66
- v(e, t.path, c, t.middlewares ?? []);
67
- }), c;
68
+ return t.path && T.push((e) => {
69
+ g(e, t.path, S, t.middlewares ?? []);
70
+ }), S;
68
71
  }
69
- function N(t) {
70
- return async (r, { abortSignal: n }) => {
71
- for (; !n.aborted; ) {
72
+ function C(t) {
73
+ return async (r, { abortSignal: o }) => {
74
+ for (; !o.aborted; ) {
72
75
  try {
73
76
  await t.tick(r);
74
77
  } catch (a) {
75
78
  console.error("[velojs] poll tick threw:", a);
76
79
  }
77
80
  await new Promise((a) => {
78
- const _ = setTimeout(a, t.intervalMs);
79
- n.addEventListener(
81
+ const u = setTimeout(a, t.intervalMs);
82
+ o.addEventListener(
80
83
  "abort",
81
84
  () => {
82
- clearTimeout(_), a();
85
+ clearTimeout(u), a();
83
86
  },
84
87
  { once: !0 }
85
88
  );
@@ -87,51 +90,51 @@ function N(t) {
87
90
  }
88
91
  };
89
92
  }
90
- function g(t) {
93
+ function p(t) {
91
94
  return t.req.query("channel") ?? "";
92
95
  }
93
- function v(t, r, n, a = []) {
94
- const _ = async (u) => {
95
- const { streamSSE: S } = await import("hono/streaming"), h = (n.__config.channel ?? g)(u), A = n.__config.heartbeatMs, E = A === !1 ? 0 : A ?? 2e4;
96
- return S(u, async (c) => {
97
- const e = n.__buffers.get(h);
98
- if (e && e.length > 0 && await c.writeSSE({
96
+ function g(t, r, o, a = []) {
97
+ const u = async (f) => {
98
+ const { streamSSE: A } = await import("hono/streaming"), d = o.__config.broadcast ? "" : (o.__config.channel ?? p)(f), w = o.__config.heartbeatMs, E = w === !1 ? 0 : w ?? 2e4;
99
+ return A(f, async (i) => {
100
+ const S = o.__buffers.get(d);
101
+ if (S && S.length > 0 && await i.writeSSE({
99
102
  event: "snapshot",
100
- data: JSON.stringify(e)
101
- }), n.__config.snapshot) {
102
- const f = await n.__config.snapshot(
103
- h === "" ? void 0 : h
103
+ data: JSON.stringify(S)
104
+ }), o.__config.snapshot) {
105
+ const _ = await o.__config.snapshot(
106
+ d === "" ? void 0 : d
104
107
  );
105
- f !== void 0 && await c.writeSSE({
108
+ _ !== void 0 && await i.writeSSE({
106
109
  event: "snapshot",
107
- data: JSON.stringify(f)
110
+ data: JSON.stringify(_)
108
111
  });
109
112
  }
110
- let i;
111
- const s = new Promise((f) => {
112
- i = f;
113
+ let e;
114
+ const c = new Promise((_) => {
115
+ e = _;
113
116
  });
114
- let o = !1, l = null;
115
- const d = () => {
116
- o || (o = !0, l && clearInterval(l), n.__onDisconnect(h, w), i());
117
- }, w = ((f) => {
118
- o || (c.writeSSE({ data: JSON.stringify(f) }).catch(() => {
119
- }), n.__config.closeOn?.(f) && (c.close(), d()));
117
+ let n = !1, s = null;
118
+ const l = () => {
119
+ n || (n = !0, s && clearInterval(s), o.__onDisconnect(d, h), e());
120
+ }, h = ((_) => {
121
+ n || (i.writeSSE({ data: JSON.stringify(_) }).catch(() => {
122
+ }), o.__config.closeOn?.(_) && (i.close(), l()));
120
123
  });
121
- w.close = () => {
122
- o || (c.writeSSE({ event: "close", data: "" }).catch(() => {
123
- }), c.close(), d());
124
- }, n.__onConnect(h, w), l = E > 0 ? setInterval(() => {
125
- o || c.writeSSE({ event: "heartbeat", data: "" }).catch(() => {
124
+ h.close = () => {
125
+ n || (i.writeSSE({ event: "close", data: "" }).catch(() => {
126
+ }), i.close(), l());
127
+ }, o.__onConnect(d, h), s = E > 0 ? setInterval(() => {
128
+ n || i.writeSSE({ event: "heartbeat", data: "" }).catch(() => {
126
129
  });
127
- }, E) : null, c.onAbort(d), await s;
130
+ }, E) : null, i.onAbort(l), await c;
128
131
  });
129
132
  };
130
- a.length > 0 ? t.on(["GET"], [r], ...a, _) : t.on(["GET"], [r], _);
133
+ a.length > 0 ? t.on(["GET"], [r], ...a, u) : t.on(["GET"], [r], u);
131
134
  }
132
135
  export {
133
- C as createEventStream,
136
+ N as createEventStream,
134
137
  m as flushPendingStreamRoutes,
135
- N as poll,
136
- v as registerStreamHandler
138
+ C as poll,
139
+ g as registerStreamHandler
137
140
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mauroandre/velojs",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "type": "module",
5
5
  "description": "Fullstack web framework built on Hono + Preact with Vite-powered AST transforms",
6
6
  "keywords": [