@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 +5 -2
- package/dist/events.d.ts +16 -2
- package/dist/events.js +83 -80
- package/package.json +1 -1
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
|
-
| `
|
|
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
|
-
/**
|
|
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
|
|
1
|
+
const T = [];
|
|
2
2
|
function m(t) {
|
|
3
|
-
for (const r of
|
|
4
|
-
|
|
3
|
+
for (const r of T) r(t);
|
|
4
|
+
T.length = 0;
|
|
5
5
|
}
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
},
|
|
16
|
-
|
|
18
|
+
}, w = () => {
|
|
19
|
+
A > 0 || f && (f.abort(), f = null);
|
|
17
20
|
};
|
|
18
|
-
function
|
|
19
|
-
let
|
|
20
|
-
if (!!t.
|
|
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 "${
|
|
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 (
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
+
if (h?.snapshot) {
|
|
30
|
+
const b = o.get(s) ?? [];
|
|
31
|
+
b.push(l), o.set(s, b);
|
|
29
32
|
}
|
|
30
|
-
const
|
|
31
|
-
if (
|
|
32
|
-
for (const
|
|
33
|
+
const v = r.get(s);
|
|
34
|
+
if (v)
|
|
35
|
+
for (const b of v) b(l);
|
|
33
36
|
}
|
|
34
|
-
function
|
|
37
|
+
function i(e = "") {
|
|
35
38
|
if (a.has(e)) return;
|
|
36
39
|
a.add(e);
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
for (const l of
|
|
40
|
-
const
|
|
41
|
-
|
|
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
|
-
|
|
45
|
-
},
|
|
47
|
+
o.delete(e), u.delete(e), a.delete(e);
|
|
48
|
+
}, n)
|
|
46
49
|
);
|
|
47
50
|
}
|
|
48
|
-
const
|
|
49
|
-
emit:
|
|
50
|
-
close:
|
|
51
|
+
const S = {
|
|
52
|
+
emit: E,
|
|
53
|
+
close: i,
|
|
51
54
|
__isVeloEventStream: !0,
|
|
52
55
|
__config: t,
|
|
53
56
|
__listeners: r,
|
|
54
|
-
__buffers:
|
|
57
|
+
__buffers: o,
|
|
55
58
|
__path: t.path,
|
|
56
|
-
__onConnect(e,
|
|
57
|
-
let
|
|
58
|
-
|
|
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,
|
|
61
|
-
const
|
|
62
|
-
|
|
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 &&
|
|
66
|
-
|
|
67
|
-
}),
|
|
68
|
+
return t.path && T.push((e) => {
|
|
69
|
+
g(e, t.path, S, t.middlewares ?? []);
|
|
70
|
+
}), S;
|
|
68
71
|
}
|
|
69
|
-
function
|
|
70
|
-
return async (r, { abortSignal:
|
|
71
|
-
for (; !
|
|
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
|
|
79
|
-
|
|
81
|
+
const u = setTimeout(a, t.intervalMs);
|
|
82
|
+
o.addEventListener(
|
|
80
83
|
"abort",
|
|
81
84
|
() => {
|
|
82
|
-
clearTimeout(
|
|
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
|
|
93
|
+
function p(t) {
|
|
91
94
|
return t.req.query("channel") ?? "";
|
|
92
95
|
}
|
|
93
|
-
function
|
|
94
|
-
const
|
|
95
|
-
const { streamSSE:
|
|
96
|
-
return
|
|
97
|
-
const
|
|
98
|
-
if (
|
|
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(
|
|
101
|
-
}),
|
|
102
|
-
const
|
|
103
|
-
|
|
103
|
+
data: JSON.stringify(S)
|
|
104
|
+
}), o.__config.snapshot) {
|
|
105
|
+
const _ = await o.__config.snapshot(
|
|
106
|
+
d === "" ? void 0 : d
|
|
104
107
|
);
|
|
105
|
-
|
|
108
|
+
_ !== void 0 && await i.writeSSE({
|
|
106
109
|
event: "snapshot",
|
|
107
|
-
data: JSON.stringify(
|
|
110
|
+
data: JSON.stringify(_)
|
|
108
111
|
});
|
|
109
112
|
}
|
|
110
|
-
let
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
+
let e;
|
|
114
|
+
const c = new Promise((_) => {
|
|
115
|
+
e = _;
|
|
113
116
|
});
|
|
114
|
-
let
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
}),
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
}),
|
|
124
|
-
},
|
|
125
|
-
|
|
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,
|
|
130
|
+
}, E) : null, i.onAbort(l), await c;
|
|
128
131
|
});
|
|
129
132
|
};
|
|
130
|
-
a.length > 0 ? t.on(["GET"], [r], ...a,
|
|
133
|
+
a.length > 0 ? t.on(["GET"], [r], ...a, u) : t.on(["GET"], [r], u);
|
|
131
134
|
}
|
|
132
135
|
export {
|
|
133
|
-
|
|
136
|
+
N as createEventStream,
|
|
134
137
|
m as flushPendingStreamRoutes,
|
|
135
|
-
|
|
136
|
-
|
|
138
|
+
C as poll,
|
|
139
|
+
g as registerStreamHandler
|
|
137
140
|
};
|