@ricsam/quickjs-test-utils 0.0.1 → 1.0.1

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.
Files changed (49) hide show
  1. package/README.md +97 -43
  2. package/dist/cjs/context.cjs +86 -0
  3. package/dist/cjs/context.cjs.map +10 -0
  4. package/dist/cjs/eval.cjs +69 -0
  5. package/dist/cjs/eval.cjs.map +10 -0
  6. package/dist/cjs/fetch-context.cjs +89 -0
  7. package/dist/cjs/fetch-context.cjs.map +10 -0
  8. package/dist/cjs/fs-context.cjs +54 -0
  9. package/dist/cjs/fs-context.cjs.map +10 -0
  10. package/dist/cjs/index.cjs +58 -0
  11. package/dist/cjs/index.cjs.map +10 -0
  12. package/dist/cjs/integration-server.cjs +137 -0
  13. package/dist/cjs/integration-server.cjs.map +10 -0
  14. package/dist/cjs/package.json +5 -0
  15. package/dist/cjs/quickjs-types.cjs +700 -0
  16. package/dist/cjs/quickjs-types.cjs.map +10 -0
  17. package/dist/cjs/runtime-context.cjs +55 -0
  18. package/dist/cjs/runtime-context.cjs.map +10 -0
  19. package/dist/cjs/typecheck.cjs +108 -0
  20. package/dist/cjs/typecheck.cjs.map +10 -0
  21. package/dist/mjs/context.mjs +61 -0
  22. package/dist/mjs/context.mjs.map +10 -0
  23. package/dist/mjs/eval.mjs +38 -0
  24. package/dist/mjs/eval.mjs.map +10 -0
  25. package/dist/mjs/fetch-context.mjs +61 -0
  26. package/dist/mjs/fetch-context.mjs.map +10 -0
  27. package/dist/mjs/fs-context.mjs +29 -0
  28. package/dist/mjs/fs-context.mjs.map +10 -0
  29. package/dist/mjs/index.mjs +45 -0
  30. package/dist/mjs/index.mjs.map +10 -0
  31. package/dist/mjs/integration-server.mjs +106 -0
  32. package/dist/mjs/integration-server.mjs.map +10 -0
  33. package/dist/mjs/package.json +5 -0
  34. package/dist/mjs/quickjs-types.mjs +669 -0
  35. package/dist/mjs/quickjs-types.mjs.map +10 -0
  36. package/dist/mjs/runtime-context.mjs +26 -0
  37. package/dist/mjs/runtime-context.mjs.map +10 -0
  38. package/dist/mjs/typecheck.mjs +77 -0
  39. package/dist/mjs/typecheck.mjs.map +10 -0
  40. package/dist/types/context.d.ts +35 -0
  41. package/dist/types/eval.d.ts +31 -0
  42. package/dist/types/fetch-context.d.ts +41 -0
  43. package/dist/types/fs-context.d.ts +12 -0
  44. package/dist/types/index.d.ts +6 -0
  45. package/dist/types/integration-server.d.ts +39 -0
  46. package/dist/types/quickjs-types.d.ts +42 -0
  47. package/dist/types/runtime-context.d.ts +9 -0
  48. package/dist/types/typecheck.d.ts +115 -0
  49. package/package.json +62 -6
@@ -0,0 +1,106 @@
1
+ // @bun
2
+ // packages/test-utils/src/integration-server.ts
3
+ import { getQuickJS } from "quickjs-emscripten";
4
+ import { setupFetch } from "@ricsam/quickjs-fetch";
5
+ import { setupCore } from "@ricsam/quickjs-core";
6
+ async function startIntegrationServer(quickJSCode, options) {
7
+ const QuickJS = await getQuickJS();
8
+ const runtime = QuickJS.newRuntime();
9
+ const context = runtime.newContext();
10
+ const coreHandle = setupCore(context);
11
+ const fetchHandle = setupFetch(context, {
12
+ coreHandle,
13
+ onFetch: options?.onFetch
14
+ });
15
+ const wsConnections = new Map;
16
+ fetchHandle.onWebSocketCommand((cmd) => {
17
+ const ws = wsConnections.get(cmd.connectionId);
18
+ if (!ws)
19
+ return;
20
+ if (cmd.type === "message") {
21
+ ws.send(cmd.data);
22
+ } else if (cmd.type === "close") {
23
+ ws.close(cmd.code, cmd.reason);
24
+ }
25
+ });
26
+ const result = context.evalCode(quickJSCode);
27
+ if (result.error) {
28
+ const error = context.dump(result.error);
29
+ result.error.dispose();
30
+ fetchHandle.dispose();
31
+ coreHandle.dispose();
32
+ context.dispose();
33
+ runtime.dispose();
34
+ throw new Error(`Failed to evaluate QuickJS code: ${JSON.stringify(error)}`);
35
+ }
36
+ result.value.dispose();
37
+ const server = Bun.serve({
38
+ port: 0,
39
+ async fetch(req, server2) {
40
+ try {
41
+ const response = await fetchHandle.dispatchRequest(req);
42
+ const upgrade = fetchHandle.getUpgradeRequest();
43
+ if (upgrade?.requested) {
44
+ const connectionId = crypto.randomUUID();
45
+ const success = server2.upgrade(req, {
46
+ data: { connectionId, userData: upgrade.data }
47
+ });
48
+ if (success) {
49
+ return;
50
+ }
51
+ return new Response("WebSocket upgrade failed", { status: 500 });
52
+ }
53
+ return response;
54
+ } catch (error) {
55
+ console.error("Integration test request failed:", error);
56
+ return new Response("Internal Server Error", { status: 500 });
57
+ }
58
+ },
59
+ websocket: {
60
+ open(ws) {
61
+ const { connectionId, userData } = ws.data;
62
+ wsConnections.set(connectionId, ws);
63
+ fetchHandle.dispatchWebSocketOpen(connectionId, userData);
64
+ },
65
+ message(ws, message) {
66
+ const msg = typeof message === "string" ? message : message.buffer;
67
+ fetchHandle.dispatchWebSocketMessage(ws.data.connectionId, msg);
68
+ },
69
+ close(ws, code, reason) {
70
+ fetchHandle.dispatchWebSocketClose(ws.data.connectionId, code, reason);
71
+ wsConnections.delete(ws.data.connectionId);
72
+ }
73
+ }
74
+ });
75
+ const port = server.port;
76
+ if (port === undefined) {
77
+ throw new Error("Server failed to get port");
78
+ }
79
+ const baseURL = `http://localhost:${port}`;
80
+ const wsURL = `ws://localhost:${port}`;
81
+ return {
82
+ baseURL,
83
+ wsURL,
84
+ port,
85
+ fetchHandle,
86
+ context,
87
+ runtime,
88
+ coreHandle,
89
+ async stop() {
90
+ for (const ws of wsConnections.values()) {
91
+ ws.close(1000, "Server stopping");
92
+ }
93
+ wsConnections.clear();
94
+ server.stop(true);
95
+ fetchHandle.dispose();
96
+ coreHandle.dispose();
97
+ context.dispose();
98
+ runtime.dispose();
99
+ }
100
+ };
101
+ }
102
+ export {
103
+ startIntegrationServer
104
+ };
105
+
106
+ //# debugId=1C6D168A9BCD814A64756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/integration-server.ts"],
4
+ "sourcesContent": [
5
+ "import { getQuickJS, type QuickJSContext, type QuickJSRuntime } from \"quickjs-emscripten\";\nimport { setupFetch, type FetchHandle, type WebSocketCommand, type SetupFetchOptions } from \"@ricsam/quickjs-fetch\";\nimport { setupCore, type CoreHandle } from \"@ricsam/quickjs-core\";\nimport type { Server, ServerWebSocket } from \"bun\";\n\ninterface WsData {\n connectionId: string;\n userData: unknown;\n}\n\nexport interface IntegrationTestServer {\n baseURL: string;\n wsURL: string;\n port: number;\n fetchHandle: FetchHandle;\n context: QuickJSContext;\n runtime: QuickJSRuntime;\n coreHandle: CoreHandle;\n stop(): Promise<void>;\n}\n\nexport interface StartIntegrationServerOptions {\n /** Handler for outbound fetch() calls from QuickJS */\n onFetch?: SetupFetchOptions[\"onFetch\"];\n}\n\n/**\n * Start a real HTTP server that dispatches requests to a QuickJS serve() handler.\n *\n * @param quickJSCode - JavaScript code to run in QuickJS, should call serve()\n * @param options - Optional configuration\n * @returns Server info including baseURL, wsURL, and stop function\n *\n * @example\n * const server = await startIntegrationServer(`\n * serve({\n * fetch(request) {\n * return new Response(\"Hello!\");\n * }\n * });\n * `);\n *\n * const response = await fetch(\\`\\${server.baseURL}/test\\`);\n * expect(await response.text()).toBe(\"Hello!\");\n *\n * await server.stop();\n */\nexport async function startIntegrationServer(\n quickJSCode: string,\n options?: StartIntegrationServerOptions\n): Promise<IntegrationTestServer> {\n const QuickJS = await getQuickJS();\n const runtime = QuickJS.newRuntime();\n const context = runtime.newContext();\n\n // Setup core and fetch APIs\n const coreHandle = setupCore(context);\n const fetchHandle = setupFetch(context, {\n coreHandle,\n onFetch: options?.onFetch,\n });\n\n // Track WebSocket connections\n const wsConnections = new Map<string, ServerWebSocket<WsData>>();\n\n // Handle outgoing WebSocket commands from QuickJS\n fetchHandle.onWebSocketCommand((cmd: WebSocketCommand) => {\n const ws = wsConnections.get(cmd.connectionId);\n if (!ws) return;\n\n if (cmd.type === \"message\") {\n ws.send(cmd.data);\n } else if (cmd.type === \"close\") {\n ws.close(cmd.code, cmd.reason);\n }\n });\n\n // Run the user's QuickJS code\n const result = context.evalCode(quickJSCode);\n if (result.error) {\n const error = context.dump(result.error);\n result.error.dispose();\n fetchHandle.dispose();\n coreHandle.dispose();\n context.dispose();\n runtime.dispose();\n throw new Error(`Failed to evaluate QuickJS code: ${JSON.stringify(error)}`);\n }\n result.value.dispose();\n\n // Start Bun server with port 0 for dynamic assignment\n const server: Server<WsData> = Bun.serve<WsData>({\n port: 0,\n async fetch(req, server) {\n try {\n // Dispatch to QuickJS handler\n const response = await fetchHandle.dispatchRequest(req);\n\n // Check if QuickJS requested a WebSocket upgrade\n const upgrade = fetchHandle.getUpgradeRequest();\n if (upgrade?.requested) {\n const connectionId = crypto.randomUUID();\n const success = server.upgrade(req, {\n data: { connectionId, userData: upgrade.data },\n });\n if (success) {\n return undefined;\n }\n return new Response(\"WebSocket upgrade failed\", { status: 500 });\n }\n\n return response;\n } catch (error) {\n console.error(\"Integration test request failed:\", error);\n return new Response(\"Internal Server Error\", { status: 500 });\n }\n },\n websocket: {\n open(ws) {\n const { connectionId, userData } = ws.data;\n wsConnections.set(connectionId, ws);\n fetchHandle.dispatchWebSocketOpen(connectionId, userData);\n },\n message(ws, message) {\n const msg = typeof message === \"string\" ? message : message.buffer;\n fetchHandle.dispatchWebSocketMessage(ws.data.connectionId, msg as string | ArrayBuffer);\n },\n close(ws, code, reason) {\n fetchHandle.dispatchWebSocketClose(ws.data.connectionId, code, reason);\n wsConnections.delete(ws.data.connectionId);\n },\n },\n });\n\n const port = server.port;\n if (port === undefined) {\n throw new Error(\"Server failed to get port\");\n }\n const baseURL = `http://localhost:${port}`;\n const wsURL = `ws://localhost:${port}`;\n\n return {\n baseURL,\n wsURL,\n port,\n fetchHandle,\n context,\n runtime,\n coreHandle,\n async stop() {\n // Close all WebSocket connections\n for (const ws of wsConnections.values()) {\n ws.close(1000, \"Server stopping\");\n }\n wsConnections.clear();\n\n // Stop the server\n server.stop(true);\n\n // Dispose QuickJS resources\n fetchHandle.dispose();\n coreHandle.dispose();\n context.dispose();\n runtime.dispose();\n },\n };\n}\n"
6
+ ],
7
+ "mappings": ";;AAAA;AACA;AACA;AA6CA,eAAsB,sBAAsB,CAC1C,aACA,SACgC;AAAA,EAChC,MAAM,UAAU,MAAM,WAAW;AAAA,EACjC,MAAM,UAAU,QAAQ,WAAW;AAAA,EACnC,MAAM,UAAU,QAAQ,WAAW;AAAA,EAGnC,MAAM,aAAa,UAAU,OAAO;AAAA,EACpC,MAAM,cAAc,WAAW,SAAS;AAAA,IACtC;AAAA,IACA,SAAS,SAAS;AAAA,EACpB,CAAC;AAAA,EAGD,MAAM,gBAAgB,IAAI;AAAA,EAG1B,YAAY,mBAAmB,CAAC,QAA0B;AAAA,IACxD,MAAM,KAAK,cAAc,IAAI,IAAI,YAAY;AAAA,IAC7C,IAAI,CAAC;AAAA,MAAI;AAAA,IAET,IAAI,IAAI,SAAS,WAAW;AAAA,MAC1B,GAAG,KAAK,IAAI,IAAI;AAAA,IAClB,EAAO,SAAI,IAAI,SAAS,SAAS;AAAA,MAC/B,GAAG,MAAM,IAAI,MAAM,IAAI,MAAM;AAAA,IAC/B;AAAA,GACD;AAAA,EAGD,MAAM,SAAS,QAAQ,SAAS,WAAW;AAAA,EAC3C,IAAI,OAAO,OAAO;AAAA,IAChB,MAAM,QAAQ,QAAQ,KAAK,OAAO,KAAK;AAAA,IACvC,OAAO,MAAM,QAAQ;AAAA,IACrB,YAAY,QAAQ;AAAA,IACpB,WAAW,QAAQ;AAAA,IACnB,QAAQ,QAAQ;AAAA,IAChB,QAAQ,QAAQ;AAAA,IAChB,MAAM,IAAI,MAAM,oCAAoC,KAAK,UAAU,KAAK,GAAG;AAAA,EAC7E;AAAA,EACA,OAAO,MAAM,QAAQ;AAAA,EAGrB,MAAM,SAAyB,IAAI,MAAc;AAAA,IAC/C,MAAM;AAAA,SACA,MAAK,CAAC,KAAK,SAAQ;AAAA,MACvB,IAAI;AAAA,QAEF,MAAM,WAAW,MAAM,YAAY,gBAAgB,GAAG;AAAA,QAGtD,MAAM,UAAU,YAAY,kBAAkB;AAAA,QAC9C,IAAI,SAAS,WAAW;AAAA,UACtB,MAAM,eAAe,OAAO,WAAW;AAAA,UACvC,MAAM,UAAU,QAAO,QAAQ,KAAK;AAAA,YAClC,MAAM,EAAE,cAAc,UAAU,QAAQ,KAAK;AAAA,UAC/C,CAAC;AAAA,UACD,IAAI,SAAS;AAAA,YACX;AAAA,UACF;AAAA,UACA,OAAO,IAAI,SAAS,4BAA4B,EAAE,QAAQ,IAAI,CAAC;AAAA,QACjE;AAAA,QAEA,OAAO;AAAA,QACP,OAAO,OAAO;AAAA,QACd,QAAQ,MAAM,oCAAoC,KAAK;AAAA,QACvD,OAAO,IAAI,SAAS,yBAAyB,EAAE,QAAQ,IAAI,CAAC;AAAA;AAAA;AAAA,IAGhE,WAAW;AAAA,MACT,IAAI,CAAC,IAAI;AAAA,QACP,QAAQ,cAAc,aAAa,GAAG;AAAA,QACtC,cAAc,IAAI,cAAc,EAAE;AAAA,QAClC,YAAY,sBAAsB,cAAc,QAAQ;AAAA;AAAA,MAE1D,OAAO,CAAC,IAAI,SAAS;AAAA,QACnB,MAAM,MAAM,OAAO,YAAY,WAAW,UAAU,QAAQ;AAAA,QAC5D,YAAY,yBAAyB,GAAG,KAAK,cAAc,GAA2B;AAAA;AAAA,MAExF,KAAK,CAAC,IAAI,MAAM,QAAQ;AAAA,QACtB,YAAY,uBAAuB,GAAG,KAAK,cAAc,MAAM,MAAM;AAAA,QACrE,cAAc,OAAO,GAAG,KAAK,YAAY;AAAA;AAAA,IAE7C;AAAA,EACF,CAAC;AAAA,EAED,MAAM,OAAO,OAAO;AAAA,EACpB,IAAI,SAAS,WAAW;AAAA,IACtB,MAAM,IAAI,MAAM,2BAA2B;AAAA,EAC7C;AAAA,EACA,MAAM,UAAU,oBAAoB;AAAA,EACpC,MAAM,QAAQ,kBAAkB;AAAA,EAEhC,OAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,SACM,KAAI,GAAG;AAAA,MAEX,WAAW,MAAM,cAAc,OAAO,GAAG;AAAA,QACvC,GAAG,MAAM,MAAM,iBAAiB;AAAA,MAClC;AAAA,MACA,cAAc,MAAM;AAAA,MAGpB,OAAO,KAAK,IAAI;AAAA,MAGhB,YAAY,QAAQ;AAAA,MACpB,WAAW,QAAQ;AAAA,MACnB,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA;AAAA,EAEpB;AAAA;",
8
+ "debugId": "1C6D168A9BCD814A64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@ricsam/quickjs-test-utils",
3
+ "version": "1.0.1",
4
+ "type": "module"
5
+ }