@fragno-dev/node 0.0.5

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.
@@ -0,0 +1,13 @@
1
+ $ tsdown
2
+ ℹ tsdown v0.15.5 powered by rolldown v1.0.0-beta.40
3
+ ℹ Using tsdown config: /home/runner/work/fragno/fragno/packages/fragno-node/tsdown.config.ts
4
+ ℹ entry: fragno-node.ts
5
+ ℹ target: node20.0.0
6
+ ℹ tsconfig: tsconfig.json
7
+ ℹ Build start
8
+ ℹ dist/fragno-node.js 1.37 kB │ gzip: 0.50 kB
9
+ ℹ dist/fragno-node.js.map 1.68 kB │ gzip: 0.60 kB
10
+ ℹ dist/fragno-node.d.ts.map 0.20 kB │ gzip: 0.14 kB
11
+ ℹ dist/fragno-node.d.ts 1.40 kB │ gzip: 0.50 kB
12
+ ℹ 4 files, total: 4.64 kB
13
+ ✔ Build complete in 10283ms
@@ -0,0 +1 @@
1
+ $ tsc --noEmit
package/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ # @fragno-dev/node
2
+
3
+ ## 0.0.5
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [b9450b1]
8
+ - @fragno-dev/core@0.0.5
9
+
10
+ ## 0.0.4
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies [1dae6f9]
15
+ - @fragno-dev/core@0.0.4
16
+
17
+ ## 0.0.3
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies [604caff]
22
+ - @fragno-dev/core@0.0.3
23
+
24
+ ## 0.0.2
25
+
26
+ ### Patch Changes
27
+
28
+ - Updated dependencies [2052919]
29
+ - @fragno-dev/core@0.0.2
@@ -0,0 +1,45 @@
1
+ import { RequestListener } from "node:http";
2
+
3
+ //#region fragno-node.d.ts
4
+
5
+ /**
6
+ * Creates a handler that can be used with `node:http` to serve backend requests based on a Fragno
7
+ * fragment (library).
8
+ *
9
+ * @example
10
+ * import { createServer } from "node:http";
11
+ * import { toNodeHandler } from "@fragno-dev/node";
12
+ * import { createExampleFragment } from "@fragno-dev/example-fragment";
13
+ *
14
+ * const fragment = createExampleFragment();
15
+ *
16
+ * const server = createServer(toNodeHandler(fragment.handler));
17
+ * server.listen(8080);
18
+ *
19
+ * @example
20
+ * import { createServer } from "node:http";
21
+ * import { toNodeHandler } from "@fragno-dev/node";
22
+ * import { createExampleFragment } from "@fragno-dev/example-fragment";
23
+ *
24
+ * const fragment = createExampleFragment();
25
+ *
26
+ * const server = createServer((req, res) => {
27
+ * if (req.url?.startsWith(fragment.mountRoute)) {
28
+ * const handler = toNodeHandler(fragment.handler);
29
+ * return handler(req, res);
30
+ * }
31
+ * // ... Your route handling
32
+ * });
33
+ * @example
34
+ * import express from "express";
35
+ * import { toNodeHandler } from "@fragno-dev/node";
36
+ *
37
+ * const app = express();
38
+ * app.all("/api/my-library/{*any}", toNodeHandler(fragment.handler));
39
+ *
40
+ * app.listen(8080);
41
+ */
42
+ declare function toNodeHandler(handler: (req: Request) => Promise<Response>): RequestListener;
43
+ //#endregion
44
+ export { toNodeHandler };
45
+ //# sourceMappingURL=fragno-node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fragno-node.d.ts","names":[],"sources":["../fragno-node.ts"],"sourcesContent":[],"mappings":";;;;;;AAwCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,aAAA,gBAA6B,YAAY,QAAQ,YAAY"}
@@ -0,0 +1,47 @@
1
+ import { createRequestListener } from "@remix-run/node-fetch-server";
2
+
3
+ //#region fragno-node.ts
4
+ /**
5
+ * Creates a handler that can be used with `node:http` to serve backend requests based on a Fragno
6
+ * fragment (library).
7
+ *
8
+ * @example
9
+ * import { createServer } from "node:http";
10
+ * import { toNodeHandler } from "@fragno-dev/node";
11
+ * import { createExampleFragment } from "@fragno-dev/example-fragment";
12
+ *
13
+ * const fragment = createExampleFragment();
14
+ *
15
+ * const server = createServer(toNodeHandler(fragment.handler));
16
+ * server.listen(8080);
17
+ *
18
+ * @example
19
+ * import { createServer } from "node:http";
20
+ * import { toNodeHandler } from "@fragno-dev/node";
21
+ * import { createExampleFragment } from "@fragno-dev/example-fragment";
22
+ *
23
+ * const fragment = createExampleFragment();
24
+ *
25
+ * const server = createServer((req, res) => {
26
+ * if (req.url?.startsWith(fragment.mountRoute)) {
27
+ * const handler = toNodeHandler(fragment.handler);
28
+ * return handler(req, res);
29
+ * }
30
+ * // ... Your route handling
31
+ * });
32
+ * @example
33
+ * import express from "express";
34
+ * import { toNodeHandler } from "@fragno-dev/node";
35
+ *
36
+ * const app = express();
37
+ * app.all("/api/my-library/{*any}", toNodeHandler(fragment.handler));
38
+ *
39
+ * app.listen(8080);
40
+ */
41
+ function toNodeHandler(handler) {
42
+ return createRequestListener(handler);
43
+ }
44
+
45
+ //#endregion
46
+ export { toNodeHandler };
47
+ //# sourceMappingURL=fragno-node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fragno-node.js","names":[],"sources":["../fragno-node.ts"],"sourcesContent":["import type { RequestListener } from \"node:http\";\nimport { createRequestListener } from \"@remix-run/node-fetch-server\";\n\n/**\n * Creates a handler that can be used with `node:http` to serve backend requests based on a Fragno\n * fragment (library).\n *\n * @example\n * import { createServer } from \"node:http\";\n * import { toNodeHandler } from \"@fragno-dev/node\";\n * import { createExampleFragment } from \"@fragno-dev/example-fragment\";\n *\n * const fragment = createExampleFragment();\n *\n * const server = createServer(toNodeHandler(fragment.handler));\n * server.listen(8080);\n *\n * @example\n * import { createServer } from \"node:http\";\n * import { toNodeHandler } from \"@fragno-dev/node\";\n * import { createExampleFragment } from \"@fragno-dev/example-fragment\";\n *\n * const fragment = createExampleFragment();\n *\n * const server = createServer((req, res) => {\n * if (req.url?.startsWith(fragment.mountRoute)) {\n * const handler = toNodeHandler(fragment.handler);\n * return handler(req, res);\n * }\n * // ... Your route handling\n * });\n * @example\n * import express from \"express\";\n * import { toNodeHandler } from \"@fragno-dev/node\";\n *\n * const app = express();\n * app.all(\"/api/my-library/{*any}\", toNodeHandler(fragment.handler));\n *\n * app.listen(8080);\n */\nexport function toNodeHandler(handler: (req: Request) => Promise<Response>): RequestListener {\n return createRequestListener(handler);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,SAAgB,cAAc,SAA+D;AAC3F,QAAO,sBAAsB,QAAQ"}
@@ -0,0 +1,77 @@
1
+ import express, { type Application } from "express";
2
+ import { test, expect, describe, beforeAll, afterAll } from "vitest";
3
+ import { z } from "zod";
4
+ import {
5
+ defineFragment,
6
+ defineRoute,
7
+ createFragment,
8
+ type FragnoInstantiatedFragment,
9
+ type FragnoPublicClientConfig,
10
+ } from "@fragno-dev/core";
11
+ import { toNodeHandler } from "./fragno-node";
12
+
13
+ describe("Fragno Express integration", () => {
14
+ const testFragmentDefinition = defineFragment("test-fragment");
15
+
16
+ const usersRoute = defineRoute({
17
+ method: "GET",
18
+ path: "/users",
19
+ outputSchema: z.array(z.object({ id: z.number(), name: z.string() })),
20
+ handler: async (_ctx, { json }) => json([{ id: 1, name: "John" }]),
21
+ });
22
+
23
+ const clientConfig: FragnoPublicClientConfig = {
24
+ baseUrl: "http://localhost",
25
+ };
26
+ let testFragment: FragnoInstantiatedFragment<[typeof usersRoute]>;
27
+ let app: Application;
28
+ let server: ReturnType<typeof app.listen>;
29
+ let port: number;
30
+
31
+ function createExpressServerForTest() {
32
+ testFragment = createFragment(testFragmentDefinition, {}, [usersRoute], clientConfig);
33
+ app = express();
34
+
35
+ app.all("/api/test-fragment/{*any}", toNodeHandler(testFragment.handler));
36
+
37
+ // Add JSON body parsing middleware
38
+ app.use(express.json());
39
+ app.get("/some-other-route", (_req, res) => {
40
+ res.writeHead(200, { "Content-Type": "application/json" });
41
+ res.end(JSON.stringify({ message: "Hello world" }));
42
+ });
43
+
44
+ server = app.listen(0);
45
+
46
+ const address = server.address();
47
+ if (!address || typeof address === "string") {
48
+ throw new Error("Address invalid");
49
+ }
50
+
51
+ port = address.port;
52
+ clientConfig.baseUrl = `http://localhost:${port}`;
53
+ }
54
+
55
+ beforeAll(() => {
56
+ createExpressServerForTest();
57
+ });
58
+
59
+ afterAll(() => {
60
+ server.close();
61
+ });
62
+
63
+ test("should fetch data from the GET /users route", async () => {
64
+ const response = await fetch(`${clientConfig.baseUrl}${testFragment.mountRoute}/users`);
65
+
66
+ expect(response.ok).toBe(true);
67
+
68
+ const data = await response.json();
69
+ expect(data).toEqual([{ id: 1, name: "John" }]);
70
+ });
71
+
72
+ test("should fall back to normal express routes for non-lib routes", async () => {
73
+ const response = await fetch(`${clientConfig.baseUrl}/some-other-route`);
74
+ const data = await response.json();
75
+ expect(data).toEqual({ message: "Hello world" });
76
+ });
77
+ });
@@ -0,0 +1,81 @@
1
+ import { createServer, type RequestListener, type Server } from "node:http";
2
+ import { test, expect, describe } from "vitest";
3
+ import { z } from "zod";
4
+ import {
5
+ defineFragment,
6
+ defineRoute,
7
+ createFragment,
8
+ type FragnoInstantiatedFragment,
9
+ type FragnoPublicClientConfig,
10
+ } from "@fragno-dev/core";
11
+ import { toNodeHandler } from "./fragno-node";
12
+
13
+ describe("Fragno Node.js integration", () => {
14
+ const testFragmentDefinition = defineFragment("test-fragment");
15
+
16
+ const usersRoute = defineRoute({
17
+ method: "GET",
18
+ path: "/users",
19
+ outputSchema: z.array(z.object({ id: z.number(), name: z.string() })),
20
+ handler: async (_ctx, { json }) => json([{ id: 1, name: "John" }]),
21
+ });
22
+
23
+ const clientConfig: FragnoPublicClientConfig = {
24
+ baseUrl: "http://localhost",
25
+ };
26
+ let testFragment: FragnoInstantiatedFragment<[typeof usersRoute]>;
27
+ let server: Server;
28
+ let port: number;
29
+
30
+ function createServerForTest(
31
+ listenerFactory: (fragment: FragnoInstantiatedFragment<[typeof usersRoute]>) => RequestListener,
32
+ ) {
33
+ testFragment = createFragment(testFragmentDefinition, {}, [usersRoute], clientConfig);
34
+ server = createServer(listenerFactory(testFragment));
35
+ server.listen(0);
36
+
37
+ const address = server.address();
38
+ if (!address || typeof address === "string") {
39
+ throw new Error("Address invalid");
40
+ }
41
+
42
+ port = address.port;
43
+ clientConfig.baseUrl = `http://localhost:${port}`;
44
+
45
+ return {
46
+ close: () => server.close(),
47
+ };
48
+ }
49
+
50
+ test("should fetch data from the GET /users route", async () => {
51
+ const { close } = createServerForTest((fragment) => toNodeHandler(fragment.handler));
52
+
53
+ const response = await fetch(`${clientConfig.baseUrl}${testFragment.mountRoute}/users`);
54
+
55
+ expect(response.ok).toBe(true);
56
+
57
+ const data = await response.json();
58
+ expect(data).toEqual([{ id: 1, name: "John" }]);
59
+
60
+ close();
61
+ });
62
+
63
+ test("should fall back to normal server for non-lib routes", async () => {
64
+ const { close } = createServerForTest((fragment) => (req, res) => {
65
+ if (req.url?.startsWith(fragment.mountRoute)) {
66
+ const handler = toNodeHandler(fragment.handler);
67
+ return handler(req, res);
68
+ } else {
69
+ res.writeHead(200, { "Content-Type": "application/json" });
70
+ res.end(JSON.stringify({ message: "It's working." }));
71
+ }
72
+ });
73
+
74
+ const response = await fetch(`${clientConfig.baseUrl}/`);
75
+ expect(response.ok).toBe(true);
76
+ const data = await response.json();
77
+ expect(data).toEqual({ message: "It's working." });
78
+
79
+ close();
80
+ });
81
+ });
package/fragno-node.ts ADDED
@@ -0,0 +1,43 @@
1
+ import type { RequestListener } from "node:http";
2
+ import { createRequestListener } from "@remix-run/node-fetch-server";
3
+
4
+ /**
5
+ * Creates a handler that can be used with `node:http` to serve backend requests based on a Fragno
6
+ * fragment (library).
7
+ *
8
+ * @example
9
+ * import { createServer } from "node:http";
10
+ * import { toNodeHandler } from "@fragno-dev/node";
11
+ * import { createExampleFragment } from "@fragno-dev/example-fragment";
12
+ *
13
+ * const fragment = createExampleFragment();
14
+ *
15
+ * const server = createServer(toNodeHandler(fragment.handler));
16
+ * server.listen(8080);
17
+ *
18
+ * @example
19
+ * import { createServer } from "node:http";
20
+ * import { toNodeHandler } from "@fragno-dev/node";
21
+ * import { createExampleFragment } from "@fragno-dev/example-fragment";
22
+ *
23
+ * const fragment = createExampleFragment();
24
+ *
25
+ * const server = createServer((req, res) => {
26
+ * if (req.url?.startsWith(fragment.mountRoute)) {
27
+ * const handler = toNodeHandler(fragment.handler);
28
+ * return handler(req, res);
29
+ * }
30
+ * // ... Your route handling
31
+ * });
32
+ * @example
33
+ * import express from "express";
34
+ * import { toNodeHandler } from "@fragno-dev/node";
35
+ *
36
+ * const app = express();
37
+ * app.all("/api/my-library/{*any}", toNodeHandler(fragment.handler));
38
+ *
39
+ * app.listen(8080);
40
+ */
41
+ export function toNodeHandler(handler: (req: Request) => Promise<Response>): RequestListener {
42
+ return createRequestListener(handler);
43
+ }
@@ -0,0 +1,94 @@
1
+ import express, { type Application } from "express";
2
+ import { test, expect, describe, beforeAll, afterAll, assert } from "vitest";
3
+ import { z } from "zod";
4
+ import {
5
+ defineFragment,
6
+ defineRoute,
7
+ createFragment,
8
+ type FragnoInstantiatedFragment,
9
+ type FragnoPublicClientConfig,
10
+ } from "@fragno-dev/core";
11
+ import { toNodeHandler } from "./fragno-node";
12
+
13
+ describe("Node.js Streaming", () => {
14
+ const testFragmentDefinition = defineFragment("test-fragment");
15
+
16
+ const streamRoute = defineRoute({
17
+ method: "GET",
18
+ path: "/stream",
19
+ outputSchema: z.array(z.object({ message: z.string() })),
20
+ handler: async (_ctx, { jsonStream }) => {
21
+ return jsonStream(async (stream) => {
22
+ await stream.write({ message: "Hello, " });
23
+ await stream.sleep(1);
24
+ await stream.write({ message: "World!" });
25
+ });
26
+ },
27
+ });
28
+
29
+ const clientConfig: FragnoPublicClientConfig = {
30
+ baseUrl: "http://localhost",
31
+ };
32
+ let testFragment: FragnoInstantiatedFragment<[typeof streamRoute]>;
33
+ let app: Application;
34
+ let server: ReturnType<typeof app.listen>;
35
+ let port: number;
36
+
37
+ function createExpressServerForTest() {
38
+ testFragment = createFragment(testFragmentDefinition, {}, [streamRoute], clientConfig);
39
+ app = express();
40
+
41
+ app.all("/api/test-fragment/{*any}", toNodeHandler(testFragment.handler));
42
+
43
+ // Add JSON body parsing middleware
44
+ app.use(express.json());
45
+ app.get("/some-other-route", (_req, res) => {
46
+ res.writeHead(200, { "Content-Type": "application/json" });
47
+ res.end(JSON.stringify({ message: "Hello world" }));
48
+ });
49
+
50
+ server = app.listen(0);
51
+
52
+ const address = server.address();
53
+ if (!address || typeof address === "string") {
54
+ throw new Error("Address invalid");
55
+ }
56
+
57
+ port = address.port;
58
+ clientConfig.baseUrl = `http://localhost:${port}`;
59
+ }
60
+
61
+ beforeAll(() => {
62
+ createExpressServerForTest();
63
+ });
64
+
65
+ afterAll(() => {
66
+ server.close();
67
+ });
68
+
69
+ test("should fetch data from the GET /stream route", async () => {
70
+ const response = await fetch(`${clientConfig.baseUrl}${testFragment.mountRoute}/stream`);
71
+
72
+ assert(response.body, "Response body is missing");
73
+
74
+ expect(response.headers.get("content-type")).toBe("application/x-ndjson; charset=utf-8");
75
+ expect(response.headers.get("cache-control")).toBe("no-cache");
76
+ expect(response.headers.get("transfer-encoding")).toBe("chunked");
77
+
78
+ const decoder = new TextDecoder();
79
+ let result = "";
80
+ for await (const chunk of response.body) {
81
+ const string = decoder.decode(chunk, { stream: true });
82
+ const lines = string.split("\n");
83
+ for (const line of lines) {
84
+ if (!line) {
85
+ continue;
86
+ }
87
+
88
+ const jsonObject = JSON.parse(line);
89
+ result += jsonObject.message;
90
+ }
91
+ }
92
+ expect(result).toBe("Hello, World!");
93
+ });
94
+ });
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@fragno-dev/node",
3
+ "version": "0.0.5",
4
+ "exports": {
5
+ ".": {
6
+ "bun": "./fragno-node.ts",
7
+ "development": "./fragno-node.ts",
8
+ "types": "./dist/fragno-node.d.ts",
9
+ "default": "./dist/fragno-node.js"
10
+ },
11
+ "./package.json": "./package.json"
12
+ },
13
+ "type": "module",
14
+ "scripts": {
15
+ "build": "tsdown",
16
+ "build:watch": "tsdown --watch",
17
+ "types:check": "tsc --noEmit"
18
+ },
19
+ "peerDependencies": {
20
+ "@fragno-dev/core": "0.0.5"
21
+ },
22
+ "engines": {
23
+ "node": "^20.0.0 || >=22.0.0"
24
+ },
25
+ "devDependencies": {
26
+ "@fragno-dev/core": "0.0.5",
27
+ "@fragno-private/typescript-config": "0.0.1",
28
+ "@types/node": "^22",
29
+ "@types/express": "^5.0.3",
30
+ "express": "^5.1.0",
31
+ "zod": "^4.0.5"
32
+ },
33
+ "dependencies": {
34
+ "@remix-run/node-fetch-server": "^0.8.0"
35
+ },
36
+ "main": "./dist/fragno-node.js",
37
+ "module": "./dist/fragno-node.js",
38
+ "types": "./dist/fragno-node.d.ts"
39
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "@fragno-private/typescript-config/tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": ".",
6
+ "composite": true
7
+ },
8
+ "include": ["*.ts"],
9
+ "exclude": ["node_modules", "dist"]
10
+ }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from "tsdown";
2
+
3
+ export default defineConfig({
4
+ entry: "./fragno-node.ts",
5
+ dts: true,
6
+ });
@@ -0,0 +1,3 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({});