@mauroandre/velojs 0.0.25 → 0.0.27
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 +121 -1
- package/dist/client.js +43 -16
- package/dist/events.d.ts +58 -3
- package/dist/events.js +174 -135
- package/dist/hooks.d.ts +56 -0
- package/dist/hooks.js +128 -68
- package/dist/index.d.ts +3 -2
- package/dist/server.js +180 -122
- package/dist/sockets.d.ts +94 -0
- package/dist/sockets.js +182 -0
- package/dist/static.js +9 -8
- package/dist/testing/index.js +453 -290
- package/dist/testing/socketSession.d.ts +13 -0
- package/dist/testing/types.d.ts +36 -0
- package/dist/types.d.ts +10 -1
- package/dist/vite.d.ts +18 -0
- package/dist/vite.js +339 -231
- package/package.json +6 -1
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VeloJS Sockets — declarative WebSocket convention.
|
|
3
|
+
*
|
|
4
|
+
* Dev pattern: export `socket_<name>` from a page/layout module.
|
|
5
|
+
*
|
|
6
|
+
* ```typescript
|
|
7
|
+
* // app/workers/WorkerTerminal.tsx
|
|
8
|
+
* export const socket_terminal = async ({
|
|
9
|
+
* incoming, send, keepOpen, close, abortSignal, c, params, query,
|
|
10
|
+
* }) => {
|
|
11
|
+
* const user = c.get("user");
|
|
12
|
+
* const session = await createTerminal(params.workerId);
|
|
13
|
+
* session.on("data", (d) => send({ type: "data", data: d.toString() }));
|
|
14
|
+
* abortSignal.addEventListener("abort", () => session.destroy());
|
|
15
|
+
* keepOpen();
|
|
16
|
+
* for await (const raw of incoming) {
|
|
17
|
+
* const msg = JSON.parse(typeof raw === "string" ? raw : "");
|
|
18
|
+
* if (msg.type === "resize") session.resize(msg.cols, msg.rows);
|
|
19
|
+
* else if (msg.type === "data") session.write(msg.data);
|
|
20
|
+
* }
|
|
21
|
+
* };
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* The plugin strips `socket_*` bodies from the client bundle and replaces them
|
|
25
|
+
* with a stub carrying `__path`. On the client, use `useSocket(socket_terminal)`.
|
|
26
|
+
*/
|
|
27
|
+
import type { Context, Hono, MiddlewareHandler } from "hono";
|
|
28
|
+
import type { RouteNode } from "./types.js";
|
|
29
|
+
export interface SocketHandlerArgs {
|
|
30
|
+
/** Raw message frames from the client. Text frames → `string`, binary → `Uint8Array`. */
|
|
31
|
+
incoming: AsyncIterable<string | Uint8Array>;
|
|
32
|
+
/** Send a message. Objects are auto-serialized with `JSON.stringify`. */
|
|
33
|
+
send: (msg: string | Uint8Array | object) => void;
|
|
34
|
+
/** Close the socket imperatively. Accepts an optional WS close code + reason. */
|
|
35
|
+
close: (code?: number, reason?: string) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Mark the socket as long-running. If the handler returns without calling
|
|
38
|
+
* `keepOpen()`, the framework closes the socket. When `keepOpen()` is set,
|
|
39
|
+
* the socket stays open until the abort signal fires.
|
|
40
|
+
*
|
|
41
|
+
* `for await (const msg of incoming)` naturally keeps the handler alive
|
|
42
|
+
* while messages flow — `keepOpen()` is redundant in that case but not
|
|
43
|
+
* harmful.
|
|
44
|
+
*/
|
|
45
|
+
keepOpen: () => void;
|
|
46
|
+
/** Fires when the client disconnects, the server aborts, or `app.close()` is called. */
|
|
47
|
+
abortSignal: AbortSignal;
|
|
48
|
+
/** Hono Context — use `c.get("user")` etc. for middleware-provided values. */
|
|
49
|
+
c: Context;
|
|
50
|
+
/** URL params (e.g. `/:workerId`). */
|
|
51
|
+
params: Record<string, string>;
|
|
52
|
+
/** Query-string values. */
|
|
53
|
+
query: Record<string, string>;
|
|
54
|
+
}
|
|
55
|
+
export type SocketHandler = (args: SocketHandlerArgs) => void | Promise<void>;
|
|
56
|
+
/** Client-side stub shape — what the Vite plugin replaces `socket_*` with on the client bundle. */
|
|
57
|
+
export interface SocketStub {
|
|
58
|
+
readonly __isVeloSocket: true;
|
|
59
|
+
readonly __path: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Wraps a raw `incoming` iterable so each frame is JSON-parsed. Invalid JSON
|
|
63
|
+
* frames are skipped with a console warning (not thrown). Use when all client
|
|
64
|
+
* messages are JSON objects.
|
|
65
|
+
*
|
|
66
|
+
* ```typescript
|
|
67
|
+
* for await (const msg of parseJson<TerminalMsg>(incoming)) {
|
|
68
|
+
* if (msg.type === "resize") session.resize(msg.cols, msg.rows);
|
|
69
|
+
* }
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare function parseJson<T = unknown>(incoming: AsyncIterable<string | Uint8Array>): AsyncIterable<T>;
|
|
73
|
+
interface RegisteredSocket {
|
|
74
|
+
path: string;
|
|
75
|
+
handler: SocketHandler;
|
|
76
|
+
middlewares: MiddlewareHandler[];
|
|
77
|
+
}
|
|
78
|
+
/** @internal Used by the testing toolkit so `app.close()` aborts in-memory sessions too. */
|
|
79
|
+
export declare function trackSocketSession(app: Hono, ctrl: AbortController): () => void;
|
|
80
|
+
/** @internal Abort every open socket session on `app`. Called by testing toolkit + disposers. */
|
|
81
|
+
export declare function abortAllSocketSessions(app: Hono): void;
|
|
82
|
+
/** @internal Returns registered sockets for `app` (used by the testing toolkit). */
|
|
83
|
+
export declare function getAppSockets(app: Hono): ReadonlyMap<string, RegisteredSocket>;
|
|
84
|
+
export declare function registerSocketHandler(app: Hono, path: string, handler: SocketHandler, middlewares?: MiddlewareHandler[]): Promise<void>;
|
|
85
|
+
/** Walks the route tree and registers every `socket_*` export. */
|
|
86
|
+
export declare function registerSocketRoutes(app: Hono, nodes: RouteNode[], parentMiddlewares?: MiddlewareHandler[]): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Inject the WebSocket adapter into an HTTP server. Call after `serve()`
|
|
89
|
+
* returns (prod) or with Vite's dev http server (dev).
|
|
90
|
+
*
|
|
91
|
+
* No-op when no sockets are registered on `app`.
|
|
92
|
+
*/
|
|
93
|
+
export declare function injectWebSocketServer(app: Hono, server: any): Promise<void>;
|
|
94
|
+
export {};
|
package/dist/sockets.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
async function* W(e) {
|
|
2
|
+
for await (const t of e) {
|
|
3
|
+
const n = typeof t == "string" ? t : new TextDecoder().decode(t);
|
|
4
|
+
try {
|
|
5
|
+
yield JSON.parse(n);
|
|
6
|
+
} catch {
|
|
7
|
+
console.warn("[velojs] parseJson: invalid JSON frame, skipped");
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const S = /* @__PURE__ */ new WeakMap(), g = /* @__PURE__ */ new WeakMap(), k = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
async function m(e) {
|
|
13
|
+
let t = S.get(e);
|
|
14
|
+
if (t) return t;
|
|
15
|
+
try {
|
|
16
|
+
return t = (await import("@hono/node-ws")).createNodeWebSocket({ app: e }), S.set(e, t), t;
|
|
17
|
+
} catch (n) {
|
|
18
|
+
return console.error("[velojs] failed to load @hono/node-ws:", n), null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function p(e, t) {
|
|
22
|
+
let n = k.get(e);
|
|
23
|
+
n || (n = /* @__PURE__ */ new Set(), k.set(e, n)), n.add(t);
|
|
24
|
+
}
|
|
25
|
+
function b(e, t) {
|
|
26
|
+
k.get(e)?.delete(t);
|
|
27
|
+
}
|
|
28
|
+
function _(e, t) {
|
|
29
|
+
return p(e, t), () => b(e, t);
|
|
30
|
+
}
|
|
31
|
+
function M(e) {
|
|
32
|
+
const t = k.get(e);
|
|
33
|
+
if (t) {
|
|
34
|
+
for (const n of t)
|
|
35
|
+
try {
|
|
36
|
+
n.abort();
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
t.clear();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function O(e) {
|
|
43
|
+
return g.get(e) ?? /* @__PURE__ */ new Map();
|
|
44
|
+
}
|
|
45
|
+
function j(e, t, n) {
|
|
46
|
+
const s = [];
|
|
47
|
+
let a = null, i = !1, c = !1, o = null;
|
|
48
|
+
const f = new AbortController();
|
|
49
|
+
p(e, f);
|
|
50
|
+
const u = {
|
|
51
|
+
async *[Symbol.asyncIterator]() {
|
|
52
|
+
for (; ; ) {
|
|
53
|
+
for (; s.length > 0; )
|
|
54
|
+
yield s.shift();
|
|
55
|
+
if (i) return;
|
|
56
|
+
await new Promise((l) => {
|
|
57
|
+
a = l;
|
|
58
|
+
}), a = null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}, d = (l) => {
|
|
62
|
+
if (!(!o || i))
|
|
63
|
+
try {
|
|
64
|
+
typeof l == "string" || l instanceof Uint8Array ? o.send(l) : o.send(JSON.stringify(l));
|
|
65
|
+
} catch (r) {
|
|
66
|
+
console.error("[velojs] socket send failed:", r);
|
|
67
|
+
}
|
|
68
|
+
}, h = (l, r) => {
|
|
69
|
+
if (o)
|
|
70
|
+
try {
|
|
71
|
+
o.close(l, r);
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
}, y = () => {
|
|
75
|
+
c = !0;
|
|
76
|
+
}, w = () => {
|
|
77
|
+
if (i || (i = !0, a?.()), !f.signal.aborted)
|
|
78
|
+
try {
|
|
79
|
+
f.abort();
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
b(e, f);
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
events: {
|
|
86
|
+
onOpen(l, r) {
|
|
87
|
+
o = r, Promise.resolve(
|
|
88
|
+
t({
|
|
89
|
+
incoming: u,
|
|
90
|
+
send: d,
|
|
91
|
+
close: h,
|
|
92
|
+
keepOpen: y,
|
|
93
|
+
abortSignal: f.signal,
|
|
94
|
+
c: n,
|
|
95
|
+
params: n.req.param(),
|
|
96
|
+
query: n.req.query()
|
|
97
|
+
})
|
|
98
|
+
).then(() => {
|
|
99
|
+
if (!c && o && !i)
|
|
100
|
+
try {
|
|
101
|
+
o.close();
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
}).catch((v) => {
|
|
105
|
+
console.error("[velojs] socket handler threw:", v);
|
|
106
|
+
try {
|
|
107
|
+
o?.close(1011, "handler error");
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
onMessage(l) {
|
|
113
|
+
if (i) return;
|
|
114
|
+
const r = l.data;
|
|
115
|
+
typeof r == "string" ? s.push(r) : r instanceof ArrayBuffer ? s.push(new Uint8Array(r)) : r && typeof r == "object" && "buffer" in r && r.buffer instanceof ArrayBuffer && s.push(new Uint8Array(r.buffer)), a?.();
|
|
116
|
+
},
|
|
117
|
+
onClose() {
|
|
118
|
+
w();
|
|
119
|
+
},
|
|
120
|
+
onError() {
|
|
121
|
+
w();
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
cleanup: w
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async function A(e, t, n, s = []) {
|
|
128
|
+
let a = g.get(e);
|
|
129
|
+
a || (a = /* @__PURE__ */ new Map(), g.set(e, a)), a.set(t, { path: t, handler: n, middlewares: s });
|
|
130
|
+
const i = await m(e);
|
|
131
|
+
if (!i) return;
|
|
132
|
+
const c = i.upgradeWebSocket((o) => j(e, n, o).events);
|
|
133
|
+
s.length > 0 ? e.on(["GET"], [t], ...s, c) : e.on(["GET"], [t], c);
|
|
134
|
+
}
|
|
135
|
+
async function q(e, t, n = []) {
|
|
136
|
+
const s = async (a, i) => {
|
|
137
|
+
for (const c of a) {
|
|
138
|
+
const o = c.module?.metadata?.moduleId, f = [...i, ...c.middlewares ?? []];
|
|
139
|
+
if (c.module && o)
|
|
140
|
+
for (const u of Object.keys(c.module)) {
|
|
141
|
+
if (!u.startsWith("socket_")) continue;
|
|
142
|
+
const d = c.module[u];
|
|
143
|
+
if (typeof d != "function") {
|
|
144
|
+
console.warn(
|
|
145
|
+
`[velojs] socket_* export ${o}/${u} is not a function; skipped`
|
|
146
|
+
);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const h = u.replace("socket_", ""), y = `/_socket/${o}/${h}`;
|
|
150
|
+
try {
|
|
151
|
+
d.__path = y;
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
d.__isVeloSocket = !0;
|
|
156
|
+
} catch {
|
|
157
|
+
}
|
|
158
|
+
await A(e, y, d, f);
|
|
159
|
+
}
|
|
160
|
+
c.children && await s(c.children, f);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
await s(t, n);
|
|
164
|
+
}
|
|
165
|
+
async function x(e, t) {
|
|
166
|
+
const n = S.get(e);
|
|
167
|
+
if (n)
|
|
168
|
+
try {
|
|
169
|
+
n.injectWebSocket(t);
|
|
170
|
+
} catch (s) {
|
|
171
|
+
console.error("[velojs] injectWebSocket failed:", s);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export {
|
|
175
|
+
M as abortAllSocketSessions,
|
|
176
|
+
O as getAppSockets,
|
|
177
|
+
x as injectWebSocketServer,
|
|
178
|
+
W as parseJson,
|
|
179
|
+
A as registerSocketHandler,
|
|
180
|
+
q as registerSocketRoutes,
|
|
181
|
+
_ as trackSocketSession
|
|
182
|
+
};
|
package/dist/static.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import i from "node:fs";
|
|
2
2
|
import a from "node:path";
|
|
3
|
-
async function
|
|
3
|
+
async function d(c, n = "") {
|
|
4
4
|
const e = [];
|
|
5
5
|
for (const t of c) {
|
|
6
|
-
const o = t.module
|
|
6
|
+
const o = t.module?.metadata?.fullPath ?? "";
|
|
7
7
|
if (t.children) {
|
|
8
|
-
const s = await
|
|
8
|
+
const s = await d(t.children, o);
|
|
9
9
|
e.push(...s);
|
|
10
|
-
} else {
|
|
10
|
+
} else if (t.module) {
|
|
11
11
|
const s = t.module.metadata?.fullPath;
|
|
12
12
|
if (!s) continue;
|
|
13
13
|
const r = Object.keys(t.module).filter((l) => l.startsWith("action_"));
|
|
@@ -17,9 +17,9 @@ async function h(c, n = "") {
|
|
|
17
17
|
const l = t.module.staticPaths;
|
|
18
18
|
if (typeof l == "function") {
|
|
19
19
|
const u = await l();
|
|
20
|
-
for (const
|
|
20
|
+
for (const h of u) {
|
|
21
21
|
let f = s;
|
|
22
|
-
for (const [p, m] of Object.entries(
|
|
22
|
+
for (const [p, m] of Object.entries(h))
|
|
23
23
|
f = f.replace(`:${p}`, m);
|
|
24
24
|
e.push({ fullPath: f, module: t.module });
|
|
25
25
|
}
|
|
@@ -29,7 +29,8 @@ async function h(c, n = "") {
|
|
|
29
29
|
);
|
|
30
30
|
} else
|
|
31
31
|
e.push({ fullPath: s, module: t.module });
|
|
32
|
-
}
|
|
32
|
+
} else
|
|
33
|
+
continue;
|
|
33
34
|
}
|
|
34
35
|
return e;
|
|
35
36
|
}
|
|
@@ -60,7 +61,7 @@ function w(c) {
|
|
|
60
61
|
async function j(c, n) {
|
|
61
62
|
const e = a.resolve(process.cwd(), "dist");
|
|
62
63
|
console.log("Generating static pages...");
|
|
63
|
-
const t = await
|
|
64
|
+
const t = await d(n);
|
|
64
65
|
if (t.length === 0) {
|
|
65
66
|
console.warn("No routes found for static generation.");
|
|
66
67
|
return;
|