@mauroandre/velojs 0.0.20 → 0.0.22
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 +57 -5
- package/dist/events.d.ts +73 -3
- package/dist/events.js +130 -87
- package/dist/index.d.ts +1 -1
- package/package.json +2 -2
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,10 +628,13 @@ 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 \| null \| Promise<...>` | Resolve channel ID. Sync or async. Return `null`/`undefined` to reject (403). Default: `?channel=...` |
|
|
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
|
-
| `source` | `(emit, { abortSignal }) => Promise<void>` |
|
|
635
|
+
| `source` | `(emit, { abortSignal }) => Promise<void>` | Stream-wide producer, only runs while subscribed |
|
|
636
|
+
| `perChannelSource` | `(channelKey, emit, { abortSignal }) => Promise<void>` | Per-channel producer. Mutually exclusive with `source` |
|
|
637
|
+
| `bufferSize` | `number` | Max entries kept in snapshot buffer per channel (FIFO). Default `Infinity` |
|
|
633
638
|
| `retainMs` | `number` | Buffer retention after `close()`. Default `300000` (5 min) |
|
|
634
639
|
| `heartbeatMs` | `number \| false` | Heartbeat interval. Default `20000` (20s). `false` to disable |
|
|
635
640
|
| `middlewares` | `MiddlewareHandler[]` | (Standalone) Hono middlewares for the SSE route |
|
|
@@ -695,6 +700,53 @@ For interval-based polling sources. Wraps your tick function in a loop that resp
|
|
|
695
700
|
poll({ intervalMs: 3000, tick: async (emit) => emit(await collect()) })
|
|
696
701
|
```
|
|
697
702
|
|
|
703
|
+
### Per-channel sources
|
|
704
|
+
|
|
705
|
+
Use `perChannelSource` for resources that scale per channel (SSH connections, DB cursors, pub/sub topics). Invoked once per channel on first subscriber, aborted when the last subscriber of that channel leaves.
|
|
706
|
+
|
|
707
|
+
```typescript
|
|
708
|
+
export const stream_logs = createEventStream<string>({
|
|
709
|
+
channel: (c) => `${c.req.param("worker")}:${c.req.param("container")}`,
|
|
710
|
+
bufferSize: 500,
|
|
711
|
+
perChannelSource: async (key, emit, { abortSignal }) => {
|
|
712
|
+
const conn = await ssh.connect(...);
|
|
713
|
+
const stream = conn.exec(`podman logs -f ${key.split(":")[1]}`);
|
|
714
|
+
stream.on("data", (d) => emit(d.toString(), { snapshot: true }));
|
|
715
|
+
abortSignal.addEventListener("abort", () => { stream.close(); conn.end(); });
|
|
716
|
+
},
|
|
717
|
+
});
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
Mutually exclusive with `source`.
|
|
721
|
+
|
|
722
|
+
### Async channel resolver (auth + ownership)
|
|
723
|
+
|
|
724
|
+
The `channel` resolver can be async and reject the connection by returning `null`/`undefined`:
|
|
725
|
+
|
|
726
|
+
```typescript
|
|
727
|
+
createEventStream({
|
|
728
|
+
channel: async (c) => {
|
|
729
|
+
const user = c.get("user");
|
|
730
|
+
const appId = c.req.query("channel");
|
|
731
|
+
const app = await getApp({ id: appId });
|
|
732
|
+
if (app?.owner !== user.id) return null; // → 403
|
|
733
|
+
return appId;
|
|
734
|
+
},
|
|
735
|
+
});
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
Combines auth and channel extraction in one place.
|
|
739
|
+
|
|
740
|
+
### Buffer size limits
|
|
741
|
+
|
|
742
|
+
Cap memory usage of long-running log streams via FIFO ring:
|
|
743
|
+
|
|
744
|
+
```typescript
|
|
745
|
+
createEventStream<string>({ bufferSize: 500 }); // keep last 500 entries per channel
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
Only affects emits with `{ snapshot: true }`.
|
|
749
|
+
|
|
698
750
|
---
|
|
699
751
|
|
|
700
752
|
## Hooks
|
|
@@ -1140,11 +1192,11 @@ Hooks (`useParams`, `useQuery`, `usePathname`, `Loader`, `useLoader`) access thi
|
|
|
1140
1192
|
|
|
1141
1193
|
| Import | Contents |
|
|
1142
1194
|
|--------|----------|
|
|
1143
|
-
| `@mauroandre/velojs` | Types (`AppRoutes`, `ActionArgs`, `LoaderArgs`, `Metadata`, `EventStream`, `EventStreamConfig`, `EmitFn`, `EmitOptions`, `SourceFn`), `Scripts`, `Link`, `createEventStream`, `poll`, `defineConfig` |
|
|
1195
|
+
| `@mauroandre/velojs` | Types (`AppRoutes`, `ActionArgs`, `LoaderArgs`, `Metadata`, `EventStream`, `EventStreamConfig`, `EmitFn`, `EmitOptions`, `SourceFn`, `PerChannelSourceFn`, `ChannelResolver`), `Scripts`, `Link`, `createEventStream`, `poll`, `defineConfig` |
|
|
1144
1196
|
| `@mauroandre/velojs/server` | `startServer`, `createApp`, `addRoutes`, `onServer`, `serverDataStorage` |
|
|
1145
1197
|
| `@mauroandre/velojs/client` | `startClient` |
|
|
1146
1198
|
| `@mauroandre/velojs/hooks` | `Loader`, `useLoader`, `useEventStream`, `useParams`, `useQuery`, `useNavigate`, `usePathname`, `touch` |
|
|
1147
|
-
| `@mauroandre/velojs/events` | `createEventStream`, `poll`, `EventStream`, `EventStreamConfig`, `EmitFn`, `EmitOptions`, `SourceFn` (also re-exported from root) |
|
|
1199
|
+
| `@mauroandre/velojs/events` | `createEventStream`, `poll`, `EventStream`, `EventStreamConfig`, `EmitFn`, `EmitOptions`, `SourceFn`, `PerChannelSourceFn`, `ChannelResolver` (also re-exported from root) |
|
|
1148
1200
|
| `@mauroandre/velojs/cookie` | `getCookie`, `setCookie`, `deleteCookie`, `getSignedCookie`, `setSignedCookie` |
|
|
1149
1201
|
| `@mauroandre/velojs/factory` | `createMiddleware`, `createFactory` |
|
|
1150
1202
|
| `@mauroandre/velojs/vite` | `veloPlugin` |
|
package/dist/events.d.ts
CHANGED
|
@@ -66,6 +66,36 @@ export interface EmitOptions {
|
|
|
66
66
|
export type SourceFn<TEvent> = (emit: EmitFn<TEvent>, ctx: {
|
|
67
67
|
abortSignal: AbortSignal;
|
|
68
68
|
}) => void | Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* A per-channel producer function. Invoked when the **first** subscriber connects
|
|
71
|
+
* to a specific channel. Aborted when the **last** subscriber of that channel disconnects.
|
|
72
|
+
*
|
|
73
|
+
* The provided `emit` function is bound to the current channel — call it with just
|
|
74
|
+
* the event value (no need to pass the channel ID again).
|
|
75
|
+
*
|
|
76
|
+
* Example (per-channel SSH log stream):
|
|
77
|
+
* ```typescript
|
|
78
|
+
* perChannelSource: async (channelKey, emit, { abortSignal }) => {
|
|
79
|
+
* const conn = await ssh.connect(...);
|
|
80
|
+
* const stream = conn.exec(`podman logs -f ${channelKey}`);
|
|
81
|
+
* stream.on("data", (d) => emit(d.toString(), { snapshot: true }));
|
|
82
|
+
* abortSignal.addEventListener("abort", () => { stream.close(); conn.end(); });
|
|
83
|
+
* }
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* Use this for resources that are **per-channel and expensive** (SSH connections,
|
|
87
|
+
* DB cursors, external pub/sub subscriptions). Mutually exclusive with `source`.
|
|
88
|
+
*/
|
|
89
|
+
export type PerChannelSourceFn<TEvent> = (channelKey: string, emit: (event: TEvent, options?: EmitOptions) => void, ctx: {
|
|
90
|
+
abortSignal: AbortSignal;
|
|
91
|
+
}) => void | Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Async-capable channel resolver. Returns a channel ID, or `null`/`undefined`
|
|
94
|
+
* to reject the connection (framework responds 403).
|
|
95
|
+
*
|
|
96
|
+
* Useful for combining channel extraction with authorization in one place.
|
|
97
|
+
*/
|
|
98
|
+
export type ChannelResolver = (c: Context) => string | null | undefined | Promise<string | null | undefined>;
|
|
69
99
|
export interface EventStreamConfig<TEvent, TSnapshot = TEvent> {
|
|
70
100
|
/**
|
|
71
101
|
* Explicit URL path for the SSE endpoint (standalone usage).
|
|
@@ -78,8 +108,22 @@ export interface EventStreamConfig<TEvent, TSnapshot = TEvent> {
|
|
|
78
108
|
* Subscribers only receive events emitted to their channel.
|
|
79
109
|
* When omitted, defaults to `c.req.query("channel") ?? ""` so client-side
|
|
80
110
|
* `useEventStream(stream, { channel })` works out of the box.
|
|
111
|
+
*
|
|
112
|
+
* Can be **async** — return a Promise to perform DB lookups, auth checks, etc.
|
|
113
|
+
* Return `null` or `undefined` to reject the connection (framework responds 403).
|
|
114
|
+
*
|
|
115
|
+
* Ignored when `broadcast: true` is set.
|
|
81
116
|
*/
|
|
82
|
-
channel?:
|
|
117
|
+
channel?: ChannelResolver;
|
|
118
|
+
/**
|
|
119
|
+
* If true, this stream has no channels — every emit goes to all subscribers.
|
|
120
|
+
* - `emit(value, options?)` (no channel arg)
|
|
121
|
+
* - `useEventStream(stream)` (no channel option)
|
|
122
|
+
* - Server uses a single broadcast bucket regardless of `?channel=` query.
|
|
123
|
+
*
|
|
124
|
+
* Default: false (channel-aware — `emit(channel, value, options?)`).
|
|
125
|
+
*/
|
|
126
|
+
broadcast?: boolean;
|
|
83
127
|
/**
|
|
84
128
|
* Returns the current state of a channel for snapshot-on-connect.
|
|
85
129
|
* Sent before any new events to bring the client up to date.
|
|
@@ -116,19 +160,45 @@ export interface EventStreamConfig<TEvent, TSnapshot = TEvent> {
|
|
|
116
160
|
* When the last subscriber disconnects, the AbortSignal fires.
|
|
117
161
|
*
|
|
118
162
|
* Use the `poll` helper for the common interval-based pattern.
|
|
163
|
+
*
|
|
164
|
+
* Mutually exclusive with `perChannelSource`.
|
|
119
165
|
*/
|
|
120
166
|
source?: SourceFn<TEvent>;
|
|
167
|
+
/**
|
|
168
|
+
* Per-channel producer. Invoked when the **first** subscriber of a channel connects.
|
|
169
|
+
* Aborted when the **last** subscriber of that channel disconnects.
|
|
170
|
+
*
|
|
171
|
+
* Use this for resources that scale per channel (SSH connections, DB cursors,
|
|
172
|
+
* external pub/sub subscriptions). The `emit` provided is bound to the current
|
|
173
|
+
* channel — call it with just the value.
|
|
174
|
+
*
|
|
175
|
+
* Mutually exclusive with `source`.
|
|
176
|
+
*/
|
|
177
|
+
perChannelSource?: PerChannelSourceFn<TEvent>;
|
|
121
178
|
/**
|
|
122
179
|
* Time (ms) the per-channel snapshot buffer is retained after `close()`.
|
|
123
180
|
* Reconnecting clients within this window still see the final state.
|
|
124
181
|
* Default: 5 * 60 * 1000 (5 minutes).
|
|
125
182
|
*/
|
|
126
183
|
retainMs?: number;
|
|
184
|
+
/**
|
|
185
|
+
* Maximum number of events kept in the per-channel snapshot buffer
|
|
186
|
+
* (only applies to `emit(..., { snapshot: true })`).
|
|
187
|
+
*
|
|
188
|
+
* When the buffer reaches this size, the oldest entries are dropped (FIFO ring).
|
|
189
|
+
* Use this to prevent memory growth in long-running log streams.
|
|
190
|
+
*
|
|
191
|
+
* Default: `Infinity` (no limit, current behavior).
|
|
192
|
+
*/
|
|
193
|
+
bufferSize?: number;
|
|
127
194
|
}
|
|
128
195
|
export interface EventStream<TEvent, TSnapshot = TEvent> {
|
|
129
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* Emit an event.
|
|
198
|
+
* - For channel-aware streams (default): `emit(channel, value, options?)`
|
|
199
|
+
* - For broadcast streams (`config.broadcast: true`): `emit(value, options?)`
|
|
200
|
+
*/
|
|
130
201
|
emit(event: TEvent, options?: EmitOptions): void;
|
|
131
|
-
/** Channel-targeted emit */
|
|
132
202
|
emit(channel: string, event: TEvent, options?: EmitOptions): void;
|
|
133
203
|
/**
|
|
134
204
|
* Closes the stream (or a specific channel).
|
package/dist/events.js
CHANGED
|
@@ -1,85 +1,112 @@
|
|
|
1
|
-
const
|
|
2
|
-
function
|
|
3
|
-
for (const
|
|
4
|
-
|
|
1
|
+
const C = [];
|
|
2
|
+
function R(t) {
|
|
3
|
+
for (const a of C) a(t);
|
|
4
|
+
C.length = 0;
|
|
5
5
|
}
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
function L(t = {}) {
|
|
7
|
+
if (t.broadcast && t.channel && console.warn(
|
|
8
|
+
"[velojs] createEventStream: `broadcast: true` ignores `channel`. Remove one of them — broadcast streams have no channels."
|
|
9
|
+
), t.source && t.perChannelSource)
|
|
10
|
+
throw new Error(
|
|
11
|
+
"[velojs] createEventStream: `source` and `perChannelSource` are mutually exclusive. Use `source` for stream-wide producers or `perChannelSource` for per-channel resources, not both."
|
|
12
|
+
);
|
|
13
|
+
const a = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map(), l = /* @__PURE__ */ new Set(), d = /* @__PURE__ */ new Map();
|
|
14
|
+
let u = null, p = 0;
|
|
15
|
+
const c = /* @__PURE__ */ new Map(), E = () => {
|
|
10
16
|
t.source && (u || (u = new AbortController(), Promise.resolve(
|
|
11
|
-
t.source(
|
|
17
|
+
t.source(_, { abortSignal: u.signal })
|
|
12
18
|
).catch((e) => {
|
|
13
19
|
console.error("[velojs] event stream source threw:", e);
|
|
14
20
|
})));
|
|
15
|
-
},
|
|
16
|
-
|
|
21
|
+
}, v = () => {
|
|
22
|
+
p > 0 || u && (u.abort(), u = null);
|
|
23
|
+
}, i = (e) => {
|
|
24
|
+
if (!t.perChannelSource || c.has(e)) return;
|
|
25
|
+
const o = new AbortController();
|
|
26
|
+
c.set(e, o);
|
|
27
|
+
const n = (r, S) => {
|
|
28
|
+
h.emit(e, r, S);
|
|
29
|
+
};
|
|
30
|
+
Promise.resolve(
|
|
31
|
+
t.perChannelSource(e, n, { abortSignal: o.signal })
|
|
32
|
+
).catch((r) => {
|
|
33
|
+
console.error(
|
|
34
|
+
`[velojs] perChannelSource threw for channel "${e}":`,
|
|
35
|
+
r
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
}, f = (e) => {
|
|
39
|
+
const o = c.get(e);
|
|
40
|
+
o && (o.abort(), c.delete(e));
|
|
17
41
|
};
|
|
18
|
-
function
|
|
19
|
-
let
|
|
20
|
-
if (!!t.
|
|
42
|
+
function _(e, o, n) {
|
|
43
|
+
let r, S, b;
|
|
44
|
+
if (!!t.broadcast ? (r = "", S = e, b = o) : (r = e, S = o, b = n), l.has(r)) {
|
|
21
45
|
console.warn(
|
|
22
|
-
`[velojs] emit() called on closed channel "${
|
|
46
|
+
`[velojs] emit() called on closed channel "${r}" — ignored. Reopen the channel by emitting to a fresh ID, or remove the close() call.`
|
|
23
47
|
);
|
|
24
48
|
return;
|
|
25
49
|
}
|
|
26
|
-
if (
|
|
27
|
-
const
|
|
28
|
-
|
|
50
|
+
if (b?.snapshot) {
|
|
51
|
+
const w = s.get(r) ?? [];
|
|
52
|
+
w.push(S);
|
|
53
|
+
const T = t.bufferSize ?? 1 / 0;
|
|
54
|
+
w.length > T && w.splice(0, w.length - T), s.set(r, w);
|
|
29
55
|
}
|
|
30
|
-
const
|
|
31
|
-
if (
|
|
32
|
-
for (const
|
|
56
|
+
const m = a.get(r);
|
|
57
|
+
if (m)
|
|
58
|
+
for (const w of m) w(S);
|
|
33
59
|
}
|
|
34
|
-
function
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
for (const
|
|
40
|
-
const
|
|
41
|
-
|
|
60
|
+
function A(e = "") {
|
|
61
|
+
if (l.has(e)) return;
|
|
62
|
+
l.add(e);
|
|
63
|
+
const o = a.get(e);
|
|
64
|
+
if (o)
|
|
65
|
+
for (const S of o) S.close();
|
|
66
|
+
const n = t.retainMs ?? 3e5, r = d.get(e);
|
|
67
|
+
r && clearTimeout(r), d.set(
|
|
42
68
|
e,
|
|
43
69
|
setTimeout(() => {
|
|
44
|
-
|
|
45
|
-
},
|
|
70
|
+
s.delete(e), d.delete(e), l.delete(e);
|
|
71
|
+
}, n)
|
|
46
72
|
);
|
|
47
73
|
}
|
|
48
|
-
const
|
|
49
|
-
emit:
|
|
50
|
-
close:
|
|
74
|
+
const h = {
|
|
75
|
+
emit: _,
|
|
76
|
+
close: A,
|
|
51
77
|
__isVeloEventStream: !0,
|
|
52
78
|
__config: t,
|
|
53
|
-
__listeners:
|
|
54
|
-
__buffers:
|
|
79
|
+
__listeners: a,
|
|
80
|
+
__buffers: s,
|
|
55
81
|
__path: t.path,
|
|
56
|
-
__onConnect(e,
|
|
57
|
-
let
|
|
58
|
-
|
|
82
|
+
__onConnect(e, o) {
|
|
83
|
+
let n = a.get(e);
|
|
84
|
+
const r = !n || n.size === 0;
|
|
85
|
+
n || (n = /* @__PURE__ */ new Set(), a.set(e, n)), n.add(o), p++, E(), r && i(e);
|
|
59
86
|
},
|
|
60
|
-
__onDisconnect(e,
|
|
61
|
-
const
|
|
62
|
-
|
|
87
|
+
__onDisconnect(e, o) {
|
|
88
|
+
const n = a.get(e);
|
|
89
|
+
n && (n.delete(o), n.size === 0 && (a.delete(e), f(e)), p--, v());
|
|
63
90
|
}
|
|
64
91
|
};
|
|
65
|
-
return t.path &&
|
|
66
|
-
|
|
67
|
-
}),
|
|
92
|
+
return t.path && C.push((e) => {
|
|
93
|
+
N(e, t.path, h, t.middlewares ?? []);
|
|
94
|
+
}), h;
|
|
68
95
|
}
|
|
69
|
-
function
|
|
70
|
-
return async (
|
|
71
|
-
for (; !
|
|
96
|
+
function M(t) {
|
|
97
|
+
return async (a, { abortSignal: s }) => {
|
|
98
|
+
for (; !s.aborted; ) {
|
|
72
99
|
try {
|
|
73
|
-
await t.tick(
|
|
74
|
-
} catch (
|
|
75
|
-
console.error("[velojs] poll tick threw:",
|
|
100
|
+
await t.tick(a);
|
|
101
|
+
} catch (l) {
|
|
102
|
+
console.error("[velojs] poll tick threw:", l);
|
|
76
103
|
}
|
|
77
|
-
await new Promise((
|
|
78
|
-
const
|
|
79
|
-
|
|
104
|
+
await new Promise((l) => {
|
|
105
|
+
const d = setTimeout(l, t.intervalMs);
|
|
106
|
+
s.addEventListener(
|
|
80
107
|
"abort",
|
|
81
108
|
() => {
|
|
82
|
-
clearTimeout(
|
|
109
|
+
clearTimeout(d), l();
|
|
83
110
|
},
|
|
84
111
|
{ once: !0 }
|
|
85
112
|
);
|
|
@@ -90,48 +117,64 @@ function N(t) {
|
|
|
90
117
|
function g(t) {
|
|
91
118
|
return t.req.query("channel") ?? "";
|
|
92
119
|
}
|
|
93
|
-
function
|
|
94
|
-
const
|
|
95
|
-
const { streamSSE:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
120
|
+
function N(t, a, s, l = []) {
|
|
121
|
+
const d = async (u) => {
|
|
122
|
+
const { streamSSE: p } = await import("hono/streaming");
|
|
123
|
+
let c;
|
|
124
|
+
if (s.__config.broadcast)
|
|
125
|
+
c = "";
|
|
126
|
+
else {
|
|
127
|
+
const i = s.__config.channel ?? g;
|
|
128
|
+
let f;
|
|
129
|
+
try {
|
|
130
|
+
f = await i(u);
|
|
131
|
+
} catch (_) {
|
|
132
|
+
return console.error("[velojs] channel resolver threw:", _), u.json({ error: "internal" }, 500);
|
|
133
|
+
}
|
|
134
|
+
if (f == null)
|
|
135
|
+
return u.json({ error: "forbidden" }, 403);
|
|
136
|
+
c = f;
|
|
137
|
+
}
|
|
138
|
+
const E = s.__config.heartbeatMs, v = E === !1 ? 0 : E ?? 2e4;
|
|
139
|
+
return p(u, async (i) => {
|
|
140
|
+
const f = s.__buffers.get(c);
|
|
141
|
+
if (f && f.length > 0 && await i.writeSSE({
|
|
99
142
|
event: "snapshot",
|
|
100
|
-
data: JSON.stringify(
|
|
101
|
-
}),
|
|
102
|
-
const
|
|
103
|
-
|
|
143
|
+
data: JSON.stringify(f)
|
|
144
|
+
}), s.__config.snapshot) {
|
|
145
|
+
const r = await s.__config.snapshot(
|
|
146
|
+
c === "" ? void 0 : c
|
|
104
147
|
);
|
|
105
|
-
|
|
148
|
+
r !== void 0 && await i.writeSSE({
|
|
106
149
|
event: "snapshot",
|
|
107
|
-
data: JSON.stringify(
|
|
150
|
+
data: JSON.stringify(r)
|
|
108
151
|
});
|
|
109
152
|
}
|
|
110
|
-
let
|
|
111
|
-
const
|
|
112
|
-
|
|
153
|
+
let _;
|
|
154
|
+
const A = new Promise((r) => {
|
|
155
|
+
_ = r;
|
|
113
156
|
});
|
|
114
|
-
let
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
}),
|
|
157
|
+
let h = !1, e = null;
|
|
158
|
+
const o = () => {
|
|
159
|
+
h || (h = !0, e && clearInterval(e), s.__onDisconnect(c, n), _());
|
|
160
|
+
}, n = ((r) => {
|
|
161
|
+
h || (i.writeSSE({ data: JSON.stringify(r) }).catch(() => {
|
|
162
|
+
}), s.__config.closeOn?.(r) && (i.close(), o()));
|
|
120
163
|
});
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}),
|
|
124
|
-
},
|
|
125
|
-
|
|
164
|
+
n.close = () => {
|
|
165
|
+
h || (i.writeSSE({ event: "close", data: "" }).catch(() => {
|
|
166
|
+
}), i.close(), o());
|
|
167
|
+
}, s.__onConnect(c, n), e = v > 0 ? setInterval(() => {
|
|
168
|
+
h || i.writeSSE({ event: "heartbeat", data: "" }).catch(() => {
|
|
126
169
|
});
|
|
127
|
-
},
|
|
170
|
+
}, v) : null, i.onAbort(o), await A;
|
|
128
171
|
});
|
|
129
172
|
};
|
|
130
|
-
|
|
173
|
+
l.length > 0 ? t.on(["GET"], [a], ...l, d) : t.on(["GET"], [a], d);
|
|
131
174
|
}
|
|
132
175
|
export {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
176
|
+
L as createEventStream,
|
|
177
|
+
R as flushPendingStreamRoutes,
|
|
178
|
+
M as poll,
|
|
179
|
+
N as registerStreamHandler
|
|
137
180
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export type { VeloConfig } from "./config.js";
|
|
|
3
3
|
export { defineConfig } from "./config.js";
|
|
4
4
|
export { Scripts, Link } from "./components.js";
|
|
5
5
|
export { createEventStream, poll } from "./events.js";
|
|
6
|
-
export type { EventStream, EventStreamConfig, EmitFn, EmitOptions, SourceFn, } from "./events.js";
|
|
6
|
+
export type { EventStream, EventStreamConfig, EmitFn, EmitOptions, SourceFn, PerChannelSourceFn, ChannelResolver, } from "./events.js";
|
|
7
7
|
export type { Context, MiddlewareHandler, Next } from "hono";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mauroandre/velojs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Fullstack web framework built on Hono + Preact with Vite-powered AST transforms",
|
|
6
6
|
"keywords": [
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
],
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "vite build --config vite.build.config.ts && tsc --declaration --emitDeclarationOnly --outDir dist --noEmit false --rootDir src",
|
|
33
|
-
"typecheck": "tsc --noEmit",
|
|
33
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
|
|
34
34
|
"test": "vitest run",
|
|
35
35
|
"test:watch": "vitest"
|
|
36
36
|
},
|