@mauroandre/velojs 0.0.22 → 0.0.24
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 +55 -0
- package/dist/app-context.d.ts +38 -0
- package/dist/app-context.js +43 -0
- package/dist/events.d.ts +4 -0
- package/dist/events.js +124 -105
- package/dist/server.js +139 -139
- package/dist/testing/conventions.d.ts +25 -0
- package/dist/testing/createTestApp.d.ts +23 -0
- package/dist/testing/index.d.ts +20 -0
- package/dist/testing/index.js +488 -0
- package/dist/testing/internal.d.ts +30 -0
- package/dist/testing/mockContext.d.ts +10 -0
- package/dist/testing/subscription.d.ts +11 -0
- package/dist/testing/types.d.ts +125 -0
- package/package.json +5 -1
package/dist/server.js
CHANGED
|
@@ -1,200 +1,200 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
import { Hono as
|
|
3
|
-
import { logger as
|
|
4
|
-
import { trimTrailingSlash as
|
|
5
|
-
import { render as
|
|
6
|
-
import { Router as
|
|
7
|
-
import { AsyncLocalStorage as
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const v =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import { jsx as p } from "preact/jsx-runtime";
|
|
2
|
+
import { Hono as b } from "hono";
|
|
3
|
+
import { logger as q } from "hono/logger";
|
|
4
|
+
import { trimTrailingSlash as T } from "hono/trailing-slash";
|
|
5
|
+
import { render as A } from "preact-render-to-string";
|
|
6
|
+
import { Router as R } from "wouter-preact";
|
|
7
|
+
import { AsyncLocalStorage as C } from "node:async_hooks";
|
|
8
|
+
import { getAppContext as g } from "./app-context.js";
|
|
9
|
+
import { flushPendingStreamRoutes as D, registerStreamHandler as I } from "./events.js";
|
|
10
|
+
const v = new C();
|
|
11
|
+
globalThis.__veloServerData = v;
|
|
12
|
+
let _ = null;
|
|
13
|
+
function W(e) {
|
|
14
|
+
if (_) {
|
|
15
|
+
e(_);
|
|
15
16
|
return;
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
+
g().serverCallbacks.push(e);
|
|
18
19
|
}
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
function y(e) {
|
|
21
|
+
_ = e;
|
|
22
|
+
const t = g();
|
|
23
|
+
for (const s of t.serverCallbacks) s(e);
|
|
24
|
+
t.serverCallbacks.length = 0;
|
|
23
25
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
S.push(t);
|
|
26
|
+
function J(e) {
|
|
27
|
+
g().pendingRoutes.push(e);
|
|
27
28
|
}
|
|
28
|
-
const N = (
|
|
29
|
-
if (
|
|
30
|
-
const
|
|
31
|
-
return
|
|
29
|
+
const N = (e, t, s) => {
|
|
30
|
+
if (e.req.query("_data") === "1") {
|
|
31
|
+
const a = globalThis.__veloBuildHash || void 0, r = s ? { ...s, __buildHash: a } : { __buildHash: a };
|
|
32
|
+
return e.json(r);
|
|
32
33
|
}
|
|
33
|
-
const
|
|
34
|
+
const n = e.req.path, o = v.run(
|
|
34
35
|
s ?? {},
|
|
35
|
-
() =>
|
|
36
|
+
() => A(/* @__PURE__ */ p(R, { ssrPath: n, children: t }))
|
|
36
37
|
);
|
|
37
|
-
if (
|
|
38
|
-
return
|
|
39
|
-
const
|
|
38
|
+
if (e.header("Cache-Control", "no-cache"), !s)
|
|
39
|
+
return e.html(o);
|
|
40
|
+
const c = `<script>window.__PAGE_DATA__=${JSON.stringify(
|
|
40
41
|
s
|
|
41
42
|
)}<\/script>`;
|
|
42
|
-
return
|
|
43
|
-
}, O = async (
|
|
44
|
-
const s =
|
|
45
|
-
|
|
46
|
-
if (!
|
|
47
|
-
const
|
|
48
|
-
return i ? { moduleId: i, loaderData:
|
|
43
|
+
return e.html(o.replace("</head>", `${c}</head>`));
|
|
44
|
+
}, O = async (e, t) => {
|
|
45
|
+
const s = t.req.param(), n = t.req.query(), o = { params: s, query: n, c: t }, c = await Promise.all(
|
|
46
|
+
e.map(async (r) => {
|
|
47
|
+
if (!r.loader) return null;
|
|
48
|
+
const d = await r.loader(o), i = r.metadata?.moduleId;
|
|
49
|
+
return i ? { moduleId: i, loaderData: d } : null;
|
|
49
50
|
})
|
|
50
|
-
),
|
|
51
|
+
), a = {
|
|
51
52
|
__params: s,
|
|
52
|
-
__query:
|
|
53
|
-
__pathname:
|
|
53
|
+
__query: n,
|
|
54
|
+
__pathname: t.req.path
|
|
54
55
|
};
|
|
55
|
-
for (const
|
|
56
|
-
|
|
56
|
+
for (const r of c)
|
|
57
|
+
r && (a[r.moduleId] = r.loaderData);
|
|
57
58
|
return {
|
|
58
|
-
components:
|
|
59
|
-
data:
|
|
59
|
+
components: e.map((r) => r.Component),
|
|
60
|
+
data: a
|
|
60
61
|
};
|
|
61
|
-
}, j = (
|
|
62
|
-
if (t.length === 0) return null;
|
|
63
|
-
const e = t.filter(Boolean);
|
|
62
|
+
}, j = (e) => {
|
|
64
63
|
if (e.length === 0) return null;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
const t = e.filter(Boolean);
|
|
65
|
+
if (t.length === 0) return null;
|
|
66
|
+
if (t.length === 1) {
|
|
67
|
+
const o = t[0];
|
|
68
|
+
return /* @__PURE__ */ p(o, {});
|
|
68
69
|
}
|
|
69
|
-
const s =
|
|
70
|
-
return
|
|
71
|
-
},
|
|
72
|
-
for (const
|
|
73
|
-
const
|
|
74
|
-
...
|
|
75
|
-
...
|
|
70
|
+
const s = t[t.length - 1];
|
|
71
|
+
return t.slice(0, -1).reduceRight((o, c) => /* @__PURE__ */ p(c, { children: o }), /* @__PURE__ */ p(s, {}));
|
|
72
|
+
}, w = (e, t, s = [], n = []) => {
|
|
73
|
+
for (const o of t) {
|
|
74
|
+
const c = [...s, o.module], a = [
|
|
75
|
+
...n,
|
|
76
|
+
...o.middlewares || []
|
|
76
77
|
];
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
if (o.children)
|
|
79
|
+
w(
|
|
80
|
+
e,
|
|
81
|
+
o.children,
|
|
82
|
+
c,
|
|
83
|
+
a
|
|
83
84
|
);
|
|
84
85
|
else {
|
|
85
|
-
const
|
|
86
|
-
if (!
|
|
86
|
+
const r = o.module.metadata?.fullPath;
|
|
87
|
+
if (!r) {
|
|
87
88
|
console.warn(
|
|
88
|
-
`Module ${
|
|
89
|
+
`Module ${o.module.metadata?.moduleId} has no fullPath`
|
|
89
90
|
);
|
|
90
91
|
continue;
|
|
91
92
|
}
|
|
92
|
-
const
|
|
93
|
-
const { components:
|
|
94
|
-
return N(i,
|
|
93
|
+
const d = async (i) => {
|
|
94
|
+
const { components: u, data: m } = await O(c, i), l = j(u);
|
|
95
|
+
return N(i, l, m);
|
|
95
96
|
};
|
|
96
|
-
|
|
97
|
+
a.length > 0 ? e.on(["GET"], [r], ...a, d) : e.on(["GET"], [r], d);
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
|
-
},
|
|
100
|
-
for (const
|
|
101
|
-
const
|
|
100
|
+
}, S = (e, t, s = []) => {
|
|
101
|
+
for (const n of t) {
|
|
102
|
+
const o = n.module.metadata?.moduleId, c = [
|
|
102
103
|
...s,
|
|
103
|
-
...
|
|
104
|
+
...n.middlewares || []
|
|
104
105
|
];
|
|
105
|
-
if (
|
|
106
|
-
const
|
|
107
|
-
(
|
|
106
|
+
if (o) {
|
|
107
|
+
const a = Object.keys(n.module).filter(
|
|
108
|
+
(r) => r.startsWith("action_")
|
|
108
109
|
);
|
|
109
|
-
for (const
|
|
110
|
-
const
|
|
110
|
+
for (const r of a) {
|
|
111
|
+
const d = r.replace("action_", ""), i = n.module[r];
|
|
111
112
|
if (typeof i == "function") {
|
|
112
|
-
const
|
|
113
|
+
const u = `/_action/${o}/${d}`, m = async (l) => {
|
|
113
114
|
let f = {};
|
|
114
115
|
try {
|
|
115
|
-
f = await
|
|
116
|
+
f = await l.req.json();
|
|
116
117
|
} catch {
|
|
117
118
|
}
|
|
118
|
-
const
|
|
119
|
+
const P = {
|
|
119
120
|
body: f,
|
|
120
|
-
params:
|
|
121
|
-
query:
|
|
122
|
-
c:
|
|
121
|
+
params: l.req.param(),
|
|
122
|
+
query: l.req.query(),
|
|
123
|
+
c: l
|
|
123
124
|
};
|
|
124
125
|
try {
|
|
125
|
-
const
|
|
126
|
-
return
|
|
127
|
-
} catch (
|
|
128
|
-
const
|
|
129
|
-
return
|
|
126
|
+
const h = await i(P);
|
|
127
|
+
return l.json(h ?? { ok: !0 });
|
|
128
|
+
} catch (h) {
|
|
129
|
+
const E = h instanceof Error ? h.message : "Action failed";
|
|
130
|
+
return l.json({ error: E }, 500);
|
|
130
131
|
}
|
|
131
132
|
};
|
|
132
|
-
|
|
133
|
+
c.length > 0 ? e.on(
|
|
133
134
|
["POST"],
|
|
134
|
-
[
|
|
135
|
-
...
|
|
136
|
-
|
|
137
|
-
) :
|
|
135
|
+
[u],
|
|
136
|
+
...c,
|
|
137
|
+
m
|
|
138
|
+
) : e.on(["POST"], [u], m);
|
|
138
139
|
}
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
|
-
|
|
142
|
+
n.children && S(e, n.children, c);
|
|
142
143
|
}
|
|
143
|
-
},
|
|
144
|
-
const
|
|
145
|
-
for (const
|
|
146
|
-
const
|
|
144
|
+
}, $ = async (e, t, s = []) => {
|
|
145
|
+
const n = (o, c) => {
|
|
146
|
+
for (const a of o) {
|
|
147
|
+
const r = a.module.metadata?.moduleId, d = [
|
|
147
148
|
...c,
|
|
148
|
-
...
|
|
149
|
+
...a.middlewares || []
|
|
149
150
|
];
|
|
150
|
-
if (
|
|
151
|
-
const
|
|
152
|
-
(
|
|
151
|
+
if (r) {
|
|
152
|
+
const i = Object.keys(a.module).filter(
|
|
153
|
+
(u) => u.startsWith("stream_")
|
|
153
154
|
);
|
|
154
|
-
for (const
|
|
155
|
-
const
|
|
156
|
-
if (
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
h,
|
|
155
|
+
for (const u of i) {
|
|
156
|
+
const m = u.replace("stream_", ""), l = a.module[u];
|
|
157
|
+
if (l?.__isVeloEventStream) {
|
|
158
|
+
const f = `/_event/${r}/${m}`;
|
|
159
|
+
l.__path = f, I(
|
|
160
|
+
e,
|
|
161
161
|
f,
|
|
162
|
-
|
|
162
|
+
l,
|
|
163
|
+
d
|
|
163
164
|
);
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
}
|
|
167
|
-
|
|
168
|
+
a.children && n(a.children, d);
|
|
168
169
|
}
|
|
169
170
|
};
|
|
170
|
-
n(
|
|
171
|
-
},
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
r && w(r);
|
|
171
|
+
n(t, s);
|
|
172
|
+
}, k = async (e) => {
|
|
173
|
+
const t = new b();
|
|
174
|
+
t.use(T()), process.env.NODE_ENV !== "production" && t.use("*", q());
|
|
175
|
+
const n = g().pendingRoutes.splice(0);
|
|
176
|
+
for (const o of n)
|
|
177
|
+
await o(t);
|
|
178
|
+
if (w(t, e), S(t, e), await $(t, e), D(t), process.env.NODE_ENV !== "production" && !_) {
|
|
179
|
+
const o = globalThis.__veloDevServer;
|
|
180
|
+
o && y(o);
|
|
181
181
|
}
|
|
182
|
-
return
|
|
183
|
-
},
|
|
184
|
-
const { routes:
|
|
182
|
+
return t;
|
|
183
|
+
}, z = async (e) => {
|
|
184
|
+
const { routes: t, port: s = Number(process.env.SERVER_PORT) || 3e3 } = e, n = await k(t);
|
|
185
185
|
if (process.env.NODE_ENV === "production" && !process.env.VELO_STATIC) {
|
|
186
|
-
const { serve:
|
|
187
|
-
(process.env.STATIC_BASE_URL || "").startsWith("http") ||
|
|
188
|
-
const i =
|
|
189
|
-
|
|
186
|
+
const { serve: o } = await import("@hono/node-server"), { serveStatic: c } = await import("@hono/node-server/serve-static"), { join: a } = await import("node:path"), r = a(process.cwd(), "dist/client");
|
|
187
|
+
(process.env.STATIC_BASE_URL || "").startsWith("http") || n.use("/*", c({ root: r })), console.log(`Server running on http://localhost:${s}`);
|
|
188
|
+
const i = o({ fetch: n.fetch, port: s });
|
|
189
|
+
y(i);
|
|
190
190
|
}
|
|
191
|
-
return
|
|
191
|
+
return n;
|
|
192
192
|
};
|
|
193
193
|
export {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
194
|
+
J as addRoutes,
|
|
195
|
+
k as createApp,
|
|
196
|
+
k as default,
|
|
197
|
+
W as onServer,
|
|
198
|
+
v as serverDataStorage,
|
|
199
|
+
z as startServer
|
|
200
200
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve URLs from VeloJS function references (action_*, loader, stream_*).
|
|
3
|
+
* Relies on metadata injected by the VeloJS Vite plugin at build time.
|
|
4
|
+
*/
|
|
5
|
+
import type { AppRoutes } from "../types.js";
|
|
6
|
+
interface ActionEntry {
|
|
7
|
+
name: string;
|
|
8
|
+
fn: Function;
|
|
9
|
+
moduleId: string;
|
|
10
|
+
url: string;
|
|
11
|
+
}
|
|
12
|
+
interface LoaderEntry {
|
|
13
|
+
fn: Function;
|
|
14
|
+
moduleId: string;
|
|
15
|
+
/** Multiple URL paths can share a loader if registered under different parents. */
|
|
16
|
+
paths: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface ConventionRegistry {
|
|
19
|
+
actions: Map<Function, ActionEntry>;
|
|
20
|
+
loaders: Map<Function, LoaderEntry>;
|
|
21
|
+
}
|
|
22
|
+
export declare function buildConventionRegistry(routes: AppRoutes): ConventionRegistry;
|
|
23
|
+
export declare function resolveActionUrl(registry: ConventionRegistry, fnOrUrl: Function | string): string;
|
|
24
|
+
export declare function resolveLoaderPath(registry: ConventionRegistry, fnOrUrl: Function | string, params?: Record<string, string>): string;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createTestApp — main entry point for the VeloJS testing toolkit.
|
|
3
|
+
*
|
|
4
|
+
* Builds an in-memory Hono app from your `routes` import, isolates side effects
|
|
5
|
+
* via AsyncLocalStorage so multiple test apps coexist, and exposes a
|
|
6
|
+
* FastAPI-TestClient-style API on top.
|
|
7
|
+
*/
|
|
8
|
+
import type { TestApp, CreateTestAppOptions } from "./types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Creates an isolated test app from your routes.
|
|
11
|
+
*
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { createTestApp } from "@mauroandre/velojs/testing";
|
|
14
|
+
* import { routes } from "../app/routes.js";
|
|
15
|
+
*
|
|
16
|
+
* const app = await createTestApp({
|
|
17
|
+
* routes,
|
|
18
|
+
* bootstrap: async () => { await connectDb(); },
|
|
19
|
+
* getSessionCookie: async ({ user }) => ({ session: await sign(user) }),
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function createTestApp(opts: CreateTestAppOptions): Promise<TestApp>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mauroandre/velojs/testing
|
|
3
|
+
*
|
|
4
|
+
* Backend testing toolkit for VeloJS apps. FastAPI-TestClient-style API:
|
|
5
|
+
* spin up the app in memory, fire HTTP requests against the registered handlers,
|
|
6
|
+
* subscribe to event streams, assert on the result. No socket, no browser.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { createTestApp } from "@mauroandre/velojs/testing";
|
|
11
|
+
* import { routes } from "../app/routes.js";
|
|
12
|
+
*
|
|
13
|
+
* const app = await createTestApp({ routes });
|
|
14
|
+
* const res = await app.get("/api/health");
|
|
15
|
+
* expect(res.status).toBe(200);
|
|
16
|
+
* await app.close();
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export { createTestApp } from "./createTestApp.js";
|
|
20
|
+
export type { TestApp, TestResponse, TestSubscription, CreateTestAppOptions, RequestOptions, BodyRequestOptions, LoaderRequestOptions, SubscribeOptions, NextOptions, MockContextOptions, Cookies, Headers, Query, Params, } from "./types.js";
|