@mauroandre/velojs 0.0.52 → 0.0.53
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 +23 -1
- package/agent/skills/velojs-testing/SKILL.md +35 -4
- package/dist/server.d.ts +15 -0
- package/dist/server.js +42 -37
- package/dist/testing/createTestApp.d.ts +8 -0
- package/dist/testing/index.js +432 -386
- package/dist/testing/types.d.ts +30 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1320,7 +1320,7 @@ Hooks (`useParams`, `useQuery`, `usePathname`, `Loader`, `useLoader`) access thi
|
|
|
1320
1320
|
|
|
1321
1321
|
## Testing
|
|
1322
1322
|
|
|
1323
|
-
VeloJS ships a backend testing toolkit at `@mauroandre/velojs/testing`. Spin up the app in memory, fire HTTP requests against the registered handlers, subscribe to event streams. No
|
|
1323
|
+
VeloJS ships a backend testing toolkit at `@mauroandre/velojs/testing`. Spin up the app in memory, fire HTTP requests against the registered handlers, subscribe to event streams. No browser, no fragile mocks of framework internals — and a real TCP port when an external actor has to reach the app.
|
|
1324
1324
|
|
|
1325
1325
|
```typescript
|
|
1326
1326
|
import { createTestApp } from "@mauroandre/velojs/testing";
|
|
@@ -1332,6 +1332,7 @@ const app = await createTestApp({
|
|
|
1332
1332
|
routes,
|
|
1333
1333
|
bootstrap: async () => { await connect(process.env.MONGO_URI!); },
|
|
1334
1334
|
getSessionCookie: async ({ user }) => ({ session: await sign(user) }),
|
|
1335
|
+
// port: 0, // optional — also serve this app over real TCP (see below)
|
|
1335
1336
|
});
|
|
1336
1337
|
|
|
1337
1338
|
// HTTP
|
|
@@ -1358,6 +1359,7 @@ await app.close();
|
|
|
1358
1359
|
| API | Purpose |
|
|
1359
1360
|
|-----|---------|
|
|
1360
1361
|
| `createTestApp(options)` | Build isolated app with bootstrap + auth callback |
|
|
1362
|
+
| `app.port` / `app.url` | Real port and base URL of the TCP listener — `undefined` in the default in-memory mode |
|
|
1361
1363
|
| `app.get/post/put/patch/delete` | HTTP requests (cookies, headers, query, JSON/FormData body) |
|
|
1362
1364
|
| `app.action(fn, opts)` | Invoke `action_*` by function reference |
|
|
1363
1365
|
| `app.loader(fn, opts)` | Invoke `loader` and unwrap response data |
|
|
@@ -1368,6 +1370,26 @@ await app.close();
|
|
|
1368
1370
|
| `app.reset()` | Clear stream buffers/listeners between tests |
|
|
1369
1371
|
| `app.close()` | Tear down everything (zero open handles guaranteed) |
|
|
1370
1372
|
|
|
1373
|
+
### External actors over TCP
|
|
1374
|
+
|
|
1375
|
+
In-memory requests never leave the process, so an actor that does — a worker in another VM POSTing a notify callback — needs a real socket. Pass `port` and the **same app instance** is served over TCP as well:
|
|
1376
|
+
|
|
1377
|
+
```typescript
|
|
1378
|
+
const app = await createTestApp({ routes, port: 0 }); // 0 → free port
|
|
1379
|
+
|
|
1380
|
+
// Hand the URL to the external client (worker, container, another test process)
|
|
1381
|
+
await provisioning.startWorker({ notifyUrl: `${app.url}/_action/Jobs/notify` });
|
|
1382
|
+
|
|
1383
|
+
// The worker POSTs over real HTTP; the in-memory subscription sees the event —
|
|
1384
|
+
// same app, same stream registry, no glue and no second instance
|
|
1385
|
+
const sub = await app.subscribe(stream_progress, { channel: jobId });
|
|
1386
|
+
const event = await sub.next({ timeoutMs: 5000 });
|
|
1387
|
+
|
|
1388
|
+
await app.close(); // closes the listener, dropping in-flight SSE/WebSocket connections
|
|
1389
|
+
```
|
|
1390
|
+
|
|
1391
|
+
Pages, endpoints, actions, `?_data=1` loaders, SSE and WebSocket all answer as in production; `app.close()` also terminates connections still open on the listener. `port` and `hostname` come from the options only — never from `process.env.PORT`/`HOST` (test determinism). Omitted `hostname` follows Node's default bind (all interfaces — the listener is reachable from the local network); `app.url` reports `http://localhost:<port>` unless an explicit hostname was given.
|
|
1392
|
+
|
|
1371
1393
|
See [Testing docs](https://github.com/mauro-andre/velojs/blob/dev/site/docs/17-testing.md) for the full guide with patterns, isolation, and FAQ.
|
|
1372
1394
|
|
|
1373
1395
|
---
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: velojs-testing
|
|
3
|
-
description: "Writing tests with `createTestApp` from `@mauroandre/velojs/testing`: HTTP requests, actions, loaders, auth, SSE subscriptions, sockets. Use when writing or fixing a test for a VeloJS app."
|
|
3
|
+
description: "Writing tests with `createTestApp` from `@mauroandre/velojs/testing`: HTTP requests, actions, loaders, auth, SSE subscriptions, sockets, and serving the app on a real TCP port for external actors. Use when writing or fixing a test for a VeloJS app."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Testing
|
|
7
7
|
|
|
8
|
-
VeloJS ships a backend testing toolkit at `@mauroandre/velojs/testing`. It spins up your app in memory, fires HTTP requests against the registered handlers, subscribes to event streams, and asserts on the result. No
|
|
8
|
+
VeloJS ships a backend testing toolkit at `@mauroandre/velojs/testing`. It spins up your app in memory, fires HTTP requests against the registered handlers, subscribes to event streams, and asserts on the result. No browser. No fragile mocks of the framework internals. And when an actor outside this process has to reach the app — a worker sending a notify callback — the same app instance is served over a real TCP port.
|
|
9
9
|
|
|
10
10
|
You test the whole stack — middleware, auth, routing, conventions, serialization — through the API your real users hit.
|
|
11
11
|
|
|
@@ -77,6 +77,7 @@ const app = await createTestApp({
|
|
|
77
77
|
routes,
|
|
78
78
|
bootstrap?: async () => { ... },
|
|
79
79
|
getSessionCookie?: async ({ user }) => ({ ... }),
|
|
80
|
+
port?: 0,
|
|
80
81
|
});
|
|
81
82
|
```
|
|
82
83
|
|
|
@@ -85,10 +86,12 @@ const app = await createTestApp({
|
|
|
85
86
|
| `routes` | Required. Your app's `routes` array — typically `import routes from "./app/routes.js"` (it is the **default** export of `routes.tsx`) |
|
|
86
87
|
| `bootstrap` | Runs once before the app is created. Use for DB connections, index creation, anything `app/server.tsx` does at startup. `addRoutes()` and `onServer()` calls here are **scoped to this app only** |
|
|
87
88
|
| `getSessionCookie` | Maps a user object to cookies the test client will attach. Required for `app.as(user)` and `app.sessionCookies(user)` |
|
|
89
|
+
| `port` | Optional. Serves the same app over real TCP on this port (`0` → free port, see [External actors over TCP](#external-actors-over-tcp)) |
|
|
90
|
+
| `hostname` | Interface to bind when `port` is set (e.g. `"127.0.0.1"`). Omitted → Node's default, all interfaces |
|
|
88
91
|
|
|
89
92
|
### app.close()
|
|
90
93
|
|
|
91
|
-
Tears down everything: heartbeats, retention timers, listeners, callbacks. After `close()`, Vitest must report **zero open handles**. Always call it in `afterAll`.
|
|
94
|
+
Tears down everything: heartbeats, retention timers, listeners, callbacks — and the TCP listener when `port` was passed, terminating the SSE/WebSocket connections still open on it. After `close()`, Vitest must report **zero open handles**. Always call it in `afterAll`.
|
|
92
95
|
|
|
93
96
|
### app.reset()
|
|
94
97
|
|
|
@@ -159,6 +162,34 @@ await app.get("/api/posts", { query: { tag: ["preact", "ssr"] } });
|
|
|
159
162
|
// → /api/posts?tag=preact&tag=ssr
|
|
160
163
|
```
|
|
161
164
|
|
|
165
|
+
## External actors over TCP
|
|
166
|
+
|
|
167
|
+
The in-memory client never leaves the process. When the actor under test *does* — a worker in another VM POSTing a notify callback to the control-plane — pass `port` and the **same instance** is served over real TCP:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
const app = await createTestApp({ routes, port: 0 }); // 0 → free port
|
|
171
|
+
|
|
172
|
+
// `app.url` / `app.port` are the real, usable address — hand it to the worker
|
|
173
|
+
await provisioning.startWorker({ notifyUrl: `${app.url}/_action/Jobs/notify` });
|
|
174
|
+
|
|
175
|
+
// The worker POSTs over real HTTP. It's the same instance behind app.get and
|
|
176
|
+
// app.subscribe, so a stream watched in-memory sees the event — no glue and no
|
|
177
|
+
// "test watches an empty stream because it hit another instance":
|
|
178
|
+
const sub = await app.subscribe(stream_progress, { channel: jobId });
|
|
179
|
+
const event = await sub.next({ timeoutMs: 5000 });
|
|
180
|
+
|
|
181
|
+
await app.close(); // listener down; in-flight SSE/WS connections dropped
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Everything answers as in production — pages, endpoints, `action_*`, `?_data=1` loaders, SSE (`stream_*`) and WebSocket (`socket_*`). `app.close()` refuses new connections and terminates the ones still open, so a suite that keeps the app alive across tests tears down promptly even with a worker mid-SSE.
|
|
185
|
+
|
|
186
|
+
Two rules keep tests deterministic:
|
|
187
|
+
|
|
188
|
+
- **Ports come from the options only.** `port` and `hostname` are never read from `process.env.PORT`/`HOST`.
|
|
189
|
+
- **The bind follows the option.** An explicit `hostname` (e.g. `"127.0.0.1"`) keeps the listener loopback; omitted, Node's default applies — all interfaces, reachable from the local network. `app.url` reports `http://localhost:<port>` unless an explicit hostname was given.
|
|
190
|
+
|
|
191
|
+
Without `port`, nothing changes: the app is purely in-memory, and `app.port`/`app.url` are `undefined`.
|
|
192
|
+
|
|
162
193
|
## Auth — getSessionCookie and .as
|
|
163
194
|
|
|
164
195
|
Most authenticated tests look like:
|
|
@@ -293,7 +324,7 @@ it("perChannelSource aborts when the last subscriber leaves", async () => {
|
|
|
293
324
|
|
|
294
325
|
## Sockets — `.socket(handler)`
|
|
295
326
|
|
|
296
|
-
`app.socket(handlerOrStubOrPath, opts)` opens an **in-memory** WebSocket session against a `socket_*` handler — no real TCP, no upgrade handshake. The handler runs directly with a mock Context.
|
|
327
|
+
`app.socket(handlerOrStubOrPath, opts)` opens an **in-memory** WebSocket session against a `socket_*` handler — no real TCP, no upgrade handshake. The handler runs directly with a mock Context. (For a real handshake, create the app with `port` and connect a client to `ws://...` — see [External actors over TCP](#external-actors-over-tcp).)
|
|
297
328
|
|
|
298
329
|
```typescript
|
|
299
330
|
import { socket_terminal } from "../app/workers/WorkerTerminal.js";
|
package/dist/server.d.ts
CHANGED
|
@@ -4,6 +4,21 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
4
4
|
export declare const serverDataStorage: AsyncLocalStorage<Record<string, unknown>>;
|
|
5
5
|
type ServerCallback = (server: import("http").Server) => void;
|
|
6
6
|
export declare function onServer(fn: ServerCallback): void;
|
|
7
|
+
/**
|
|
8
|
+
* Points the global `activeServer` at `server` and drains the callbacks
|
|
9
|
+
* registered in the current context. Exported for the testing toolkit, which
|
|
10
|
+
* serves a TestApp over TCP and must hand the real server to `onServer()`
|
|
11
|
+
* callbacks (same flush as `startServer`).
|
|
12
|
+
*/
|
|
13
|
+
export declare function flushServerCallbacks(server: import("http").Server): void;
|
|
14
|
+
/**
|
|
15
|
+
* Drops the global `activeServer` when it still points at `server`. A closed
|
|
16
|
+
* server must stop answering `onServer()`: a late registrant would receive a
|
|
17
|
+
* dead instance (address() === null) instead of queueing for the next one.
|
|
18
|
+
* `startServer` does the same through `server.once("close", …)`; the testing
|
|
19
|
+
* toolkit has no such event to lean on because it closes the server itself.
|
|
20
|
+
*/
|
|
21
|
+
export declare function clearActiveServer(server: import("http").Server): void;
|
|
7
22
|
export interface StartServerOptions {
|
|
8
23
|
routes: AppRoutes;
|
|
9
24
|
port?: number;
|
package/dist/server.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { jsx as
|
|
1
|
+
import { jsx as w } from "preact/jsx-runtime";
|
|
2
2
|
import { Hono as R } from "hono";
|
|
3
3
|
import { logger as T } from "hono/logger";
|
|
4
4
|
import { trimTrailingSlash as E } from "hono/trailing-slash";
|
|
5
5
|
import { render as C } from "preact-render-to-string";
|
|
6
|
-
import { Router as
|
|
7
|
-
import { AsyncLocalStorage as
|
|
6
|
+
import { Router as A } from "wouter-preact";
|
|
7
|
+
import { AsyncLocalStorage as $ } from "node:async_hooks";
|
|
8
8
|
import { getAppContext as _ } from "./app-context.js";
|
|
9
|
-
import { flushPendingStreamRoutes as
|
|
9
|
+
import { flushPendingStreamRoutes as k, registerStreamHandler as D } from "./events.js";
|
|
10
10
|
import { registerSocketRoutes as O, injectWebSocketServer as S } from "./sockets.js";
|
|
11
|
-
const P = new
|
|
11
|
+
const P = new $();
|
|
12
12
|
globalThis.__veloServerData = P;
|
|
13
13
|
let g = null;
|
|
14
|
-
function
|
|
14
|
+
function ee(e) {
|
|
15
15
|
if (g) {
|
|
16
16
|
e(g);
|
|
17
17
|
return;
|
|
@@ -24,23 +24,26 @@ function b(e) {
|
|
|
24
24
|
for (const s of t.serverCallbacks) s(e);
|
|
25
25
|
t.serverCallbacks.length = 0;
|
|
26
26
|
}
|
|
27
|
-
function
|
|
27
|
+
function I(e) {
|
|
28
|
+
g === e && (g = null);
|
|
29
|
+
}
|
|
30
|
+
function te(e) {
|
|
28
31
|
_().pendingRoutes.push(e);
|
|
29
32
|
}
|
|
30
|
-
const
|
|
33
|
+
const N = (e) => JSON.stringify(e).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029"), H = (e, t, s, a) => {
|
|
31
34
|
if (a && a !== 200 && e.status(a), e.req.query("_data") === "1") {
|
|
32
35
|
const c = globalThis.__veloBuildHash || void 0, r = s ? { ...s, __buildHash: c } : { __buildHash: c };
|
|
33
36
|
return e.json(r);
|
|
34
37
|
}
|
|
35
38
|
const o = e.req.path, n = P.run(
|
|
36
39
|
s ?? {},
|
|
37
|
-
() => C(/* @__PURE__ */
|
|
40
|
+
() => C(/* @__PURE__ */ w(A, { ssrPath: o, children: t }))
|
|
38
41
|
);
|
|
39
42
|
if (e.header("Cache-Control", "no-cache"), !s)
|
|
40
43
|
return e.html(n);
|
|
41
|
-
const i = `<script>window.__PAGE_DATA__=${
|
|
44
|
+
const i = `<script>window.__PAGE_DATA__=${N(s)}<\/script>`;
|
|
42
45
|
return e.html(n.replace("</head>", () => `${i}</head>`));
|
|
43
|
-
},
|
|
46
|
+
}, G = async (e, t) => {
|
|
44
47
|
const s = t.req.param(), a = t.req.query(), o = { params: s, query: a, c: t }, n = await Promise.all(
|
|
45
48
|
e.map(async (r) => {
|
|
46
49
|
if (!r.loader) return null;
|
|
@@ -66,16 +69,16 @@ const I = (e) => JSON.stringify(e).replace(/</g, "\\u003c").replace(/>/g, "\\u00
|
|
|
66
69
|
data: c,
|
|
67
70
|
shortCircuit: i
|
|
68
71
|
};
|
|
69
|
-
},
|
|
72
|
+
}, x = (e) => {
|
|
70
73
|
if (e.length === 0) return null;
|
|
71
74
|
const t = e.filter(Boolean);
|
|
72
75
|
if (t.length === 0) return null;
|
|
73
76
|
if (t.length === 1) {
|
|
74
77
|
const o = t[0];
|
|
75
|
-
return /* @__PURE__ */
|
|
78
|
+
return /* @__PURE__ */ w(o, {});
|
|
76
79
|
}
|
|
77
80
|
const s = t[t.length - 1];
|
|
78
|
-
return t.slice(0, -1).reduceRight((o, n) => /* @__PURE__ */
|
|
81
|
+
return t.slice(0, -1).reduceRight((o, n) => /* @__PURE__ */ w(n, { children: o }), /* @__PURE__ */ w(s, {}));
|
|
79
82
|
}, y = (e, t, s = [], a = []) => {
|
|
80
83
|
for (const o of t) {
|
|
81
84
|
if (o.handler && !o.module) {
|
|
@@ -107,13 +110,13 @@ const I = (e) => JSON.stringify(e).replace(/</g, "\\u003c").replace(/>/g, "\\u00
|
|
|
107
110
|
continue;
|
|
108
111
|
}
|
|
109
112
|
const l = async (d) => {
|
|
110
|
-
const { components: u, data: h, shortCircuit: m } = await
|
|
113
|
+
const { components: u, data: h, shortCircuit: m } = await G(n, d);
|
|
111
114
|
if (m) {
|
|
112
|
-
const
|
|
113
|
-
return d.req.query("_data") === "1" &&
|
|
115
|
+
const v = m.headers.get("Location");
|
|
116
|
+
return d.req.query("_data") === "1" && v && m.status >= 300 && m.status < 400 ? d.json({ __redirect: v }) : m;
|
|
114
117
|
}
|
|
115
|
-
const p =
|
|
116
|
-
return
|
|
118
|
+
const p = x(u);
|
|
119
|
+
return H(d, p, h, r);
|
|
117
120
|
};
|
|
118
121
|
c ? e.notFound(l) : i.length > 0 ? e.on(["GET"], [f], ...i, l) : e.on(["GET"], [f], l);
|
|
119
122
|
} else
|
|
@@ -148,8 +151,8 @@ const I = (e) => JSON.stringify(e).replace(/</g, "\\u003c").replace(/>/g, "\\u00
|
|
|
148
151
|
const p = await f(m);
|
|
149
152
|
return u.json(p ?? { ok: !0 });
|
|
150
153
|
} catch (p) {
|
|
151
|
-
const
|
|
152
|
-
return u.json({ error:
|
|
154
|
+
const v = p instanceof Error ? p.message : "Action failed";
|
|
155
|
+
return u.json({ error: v }, 500);
|
|
153
156
|
}
|
|
154
157
|
};
|
|
155
158
|
n.length > 0 ? e.on(
|
|
@@ -163,7 +166,7 @@ const I = (e) => JSON.stringify(e).replace(/</g, "\\u003c").replace(/>/g, "\\u00
|
|
|
163
166
|
}
|
|
164
167
|
a.children && q(e, a.children, n);
|
|
165
168
|
}
|
|
166
|
-
},
|
|
169
|
+
}, V = async (e, t, s = []) => {
|
|
167
170
|
const a = (o, n) => {
|
|
168
171
|
for (const i of o) {
|
|
169
172
|
const c = i.module?.metadata?.moduleId, r = [
|
|
@@ -191,7 +194,7 @@ const I = (e) => JSON.stringify(e).replace(/</g, "\\u003c").replace(/>/g, "\\u00
|
|
|
191
194
|
}
|
|
192
195
|
};
|
|
193
196
|
a(t, s);
|
|
194
|
-
},
|
|
197
|
+
}, W = (e, t) => t ? t.startsWith("/") ? e + t : e ? e + "/" + t : "/" + t : e || "", K = (e) => {
|
|
195
198
|
const t = /* @__PURE__ */ new Set(), s = (a) => {
|
|
196
199
|
for (const o of a) {
|
|
197
200
|
if (o.module && !o.children) {
|
|
@@ -204,7 +207,7 @@ const I = (e) => JSON.stringify(e).replace(/</g, "\\u003c").replace(/>/g, "\\u00
|
|
|
204
207
|
return s(e), t;
|
|
205
208
|
}, j = (e, t, s, a = "", o = []) => {
|
|
206
209
|
for (const n of t) {
|
|
207
|
-
const i =
|
|
210
|
+
const i = W(a, n.path), c = [
|
|
208
211
|
...o,
|
|
209
212
|
...n.middlewares || []
|
|
210
213
|
], r = !!n.handler, f = !!n.method;
|
|
@@ -236,21 +239,21 @@ const I = (e) => JSON.stringify(e).replace(/</g, "\\u003c").replace(/>/g, "\\u00
|
|
|
236
239
|
c
|
|
237
240
|
);
|
|
238
241
|
}
|
|
239
|
-
},
|
|
242
|
+
}, L = async (e) => {
|
|
240
243
|
const t = new R();
|
|
241
244
|
t.use(E()), process.env.NODE_ENV !== "production" && t.use("*", T());
|
|
242
245
|
const a = _().pendingRoutes.splice(0);
|
|
243
246
|
for (const n of a)
|
|
244
247
|
await n(t);
|
|
245
|
-
y(t, e), q(t, e), await
|
|
246
|
-
const o =
|
|
247
|
-
if (j(t, e, o), await O(t, e),
|
|
248
|
+
y(t, e), q(t, e), await V(t, e);
|
|
249
|
+
const o = K(e);
|
|
250
|
+
if (j(t, e, o), await O(t, e), k(t), process.env.NODE_ENV !== "production" && !g) {
|
|
248
251
|
const n = globalThis.__veloDevServer;
|
|
249
252
|
n && (b(n), await S(t, n));
|
|
250
253
|
}
|
|
251
254
|
return t;
|
|
252
|
-
},
|
|
253
|
-
const { routes: t } = e, s = Number(process.env.PORT) || e.port || 3e3, a = process.env.HOST || e.hostname || void 0, o = await
|
|
255
|
+
}, oe = async (e) => {
|
|
256
|
+
const { routes: t } = e, s = Number(process.env.PORT) || e.port || 3e3, a = process.env.HOST || e.hostname || void 0, o = await L(t);
|
|
254
257
|
if (process.env.NODE_ENV === "production" && !process.env.VELO_STATIC) {
|
|
255
258
|
const { serve: n } = await import("@hono/node-server"), { serveStatic: i } = await import("@hono/node-server/serve-static"), { join: c } = await import("node:path"), r = c(process.cwd(), "dist/client");
|
|
256
259
|
(process.env.STATIC_BASE_URL || "").startsWith("http") || o.use("/*", i({ root: r }));
|
|
@@ -263,17 +266,19 @@ const I = (e) => JSON.stringify(e).replace(/</g, "\\u003c").replace(/>/g, "\\u00
|
|
|
263
266
|
} else
|
|
264
267
|
console.log(`Server running on port ${s}`);
|
|
265
268
|
await S(o, l), b(l), l.once("close", () => {
|
|
266
|
-
|
|
269
|
+
I(l);
|
|
267
270
|
});
|
|
268
271
|
}
|
|
269
272
|
return o;
|
|
270
273
|
};
|
|
271
274
|
export {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
275
|
+
te as addRoutes,
|
|
276
|
+
I as clearActiveServer,
|
|
277
|
+
L as createApp,
|
|
278
|
+
L as default,
|
|
279
|
+
b as flushServerCallbacks,
|
|
280
|
+
N as jsonForScript,
|
|
281
|
+
ee as onServer,
|
|
277
282
|
P as serverDataStorage,
|
|
278
|
-
|
|
283
|
+
oe as startServer
|
|
279
284
|
};
|
|
@@ -19,5 +19,13 @@ import type { TestApp, CreateTestAppOptions } from "./types.js";
|
|
|
19
19
|
* getSessionCookie: async ({ user }) => ({ session: await sign(user) }),
|
|
20
20
|
* });
|
|
21
21
|
* ```
|
|
22
|
+
*
|
|
23
|
+
* Pass `port` to also serve the same app over real TCP — for external actors
|
|
24
|
+
* (a worker in another process) that cannot call `hono.fetch` in-memory:
|
|
25
|
+
*
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const app = await createTestApp({ routes, port: 0 });
|
|
28
|
+
* await handToWorker(`${app.url}/_action/Jobs/notify`);
|
|
29
|
+
* ```
|
|
22
30
|
*/
|
|
23
31
|
export declare function createTestApp(opts: CreateTestAppOptions): Promise<TestApp>;
|