Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 51 additions & 3 deletions .github/workflows/verify.yml → .github/workflows/analyze.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Verify
name: Analyze

on:
push:
Expand Down Expand Up @@ -37,8 +37,8 @@ jobs:
- name: Build
run: bun run build

consumer:
name: Consumer install
consumer-basic:
name: Consumer (basic)
runs-on: ubuntu-latest
needs: verify
steps:
Expand Down Expand Up @@ -91,3 +91,51 @@ jobs:
grep -q "message received" server.log
grep -q "client disconnected" server.log
exit $CLIENT_EXIT

consumer-express:
name: Consumer (express)
runs-on: ubuntu-latest
needs: verify
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11

- name: Build package tarball
run: |
bun install --frozen-lockfile
bun run build
bun pm pack

- name: Install as a consumer
run: |
mkdir "$HOME/consumer" && cd "$HOME/consumer"
echo '{"name":"consumer","private":true}' > package.json
echo '{"compilerOptions":{"experimentalDecorators":true,"emitDecoratorMetadata":true}}' > tsconfig.json
TGZ=$(ls "$GITHUB_WORKSPACE"/inversify-socket-utils-*.tgz)
bun add "$TGZ" inversify@^8 reflect-metadata socket.io socket.io-client @inversifyjs/http-core @inversifyjs/http-express express
cp -r "$GITHUB_WORKSPACE/examples" .

- name: End-to-end check
run: |
cd "$HOME/consumer"
bun run examples/express/server.ts > server.log 2>&1 &
SERVER_PID=$!
for _ in $(seq 1 30); do
if grep -q "HTTP is listening" server.log 2>/dev/null; then break; fi
sleep 1
done
set +e
bun run examples/express/client.ts
CLIENT_EXIT=$?
set -e
sleep 1
cat server.log
kill $SERVER_PID
grep -q "client connected" server.log
grep -q "client disconnected" server.log
exit $CLIENT_EXIT
3 changes: 2 additions & 1 deletion .releaserc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"releaseRules": [
{ "type": "chore", "release": "patch" },
{ "type": "chore", "scope": "release", "release": false },
{ "type": "refactor", "release": "patch" }
{ "type": "refactor", "release": "patch" },
{ "type": "major", "release": "major" }
]
}
],
Expand Down
252 changes: 236 additions & 16 deletions bun.lock

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions examples/basic/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const io = new Server(app, {
const server = new InversifySocketServer(container, io);
server.build();

app.listen(3000);
const port = Number(process.env.PORT ?? 3000);

console.log("Server is listening on port 3000");
app.listen(port);

console.log(`Server is listening on port ${port}`);
75 changes: 75 additions & 0 deletions examples/express/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { io } from "socket.io-client";

const port = process.env.PORT ?? 3000;
const base = `http://127.0.0.1:${port}`;

const fail = (message: string): never => {
console.error(`[client] ${message}`);
process.exit(1);
};

const listResponse = await fetch(`${base}/api/users`);
const users = (await listResponse.json()) as Array<{ name: string }>;

console.log(`[http] GET /api/users -> ${users.map((user) => user.name).join(", ")}`);

if (!listResponse.ok || users.length === 0) {
fail("GET /api/users did not return any users");
}

const detailResponse = await fetch(`${base}/api/users/1`);
if (detailResponse.status !== 200) {
fail(`GET /api/users/1 -> ${detailResponse.status}`);
}

const missingResponse = await fetch(`${base}/api/users/999`);
if (missingResponse.status !== 404) {
fail(`GET /api/users/999 -> ${missingResponse.status}, expected 404`);
}

const client = io(base, {
transports: ["websocket"],
});

client.on("connect_error", (error) => fail(`connect error: ${error.message}`));

const connected = new Promise<{ totalUsers: number }>((resolve) =>
client.once("connected", resolve),
);
await new Promise<void>((resolve) => client.once("connect", resolve));

const hello = await connected;
console.log(`[ws] connected, server knows ${hello.totalUsers} user(s)`);

const reply = new Promise<string>((resolve) => client.once("message", resolve));
client.emit("message", { text: "hello" });

const answer = await reply;
console.log(`[ws] received: ${answer}`);
if (!answer.includes("hello")) {
fail(`unexpected echo: ${answer}`);
}

const created = new Promise<{ name: string }>((resolve) =>
client.once("user:created", resolve),
);

const createResponse = await fetch(`${base}/api/users`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ name: "Summer Smith", email: "summer@example.com" }),
});

const createdUser = (await createResponse.json()) as { name: string };

console.log(`[http] POST /api/users -> ${createdUser.name}`);

const broadcast = await created;
console.log(`[ws] broadcast received: user:created ${broadcast.name}`);
if (broadcast.name !== createdUser.name) {
fail("broadcast payload mismatch");
}

client.disconnect();

setTimeout(() => process.exit(0), 300);
37 changes: 37 additions & 0 deletions examples/express/controllers/message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import "reflect-metadata";

import { inject, injectable } from "inversify";
import type { Socket } from "socket.io";

import {
connectedSocket,
controller,
onConnect,
onDisconnect,
onMessage,
payload,
} from "inversify-socket-utils";

import { UserService } from "../services/user";

@injectable()
@controller("/")
export class MessageController {
constructor(@inject(UserService) private readonly users: UserService) {}

@onConnect("connection")
connection(@connectedSocket() socket: Socket): void {
console.log(`[ws] client connected: ${socket.id}`);
socket.emit("connected", { totalUsers: this.users.total() });
}

@onMessage("message")
message(@connectedSocket() socket: Socket, @payload() data: unknown): void {
socket.emit("message", `echo: ${JSON.stringify(data)}`);
}

@onDisconnect("disconnect")
disconnect(): void {
console.log("[ws] client disconnected");
}
}
44 changes: 44 additions & 0 deletions examples/express/controllers/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import "reflect-metadata";

import { inject } from "inversify";
import { Server } from "socket.io";
import {
Body,
Controller,
CreatedHttpResponse,
Get,
NotFoundHttpResponse,
Params,
Post,
} from "@inversifyjs/http-core";

import type { User } from "../services/user";
import { UserService } from "../services/user";

@Controller("/api/users")
export class UserController {
constructor(
@inject(UserService) private readonly users: UserService,
@inject(Server) private readonly io: Server,
) {}

@Get("/")
list(): User[] {
return this.users.list();
}

@Get("/:id")
detail(@Params({ name: "id" }) id: string): NotFoundHttpResponse | User {
const user = this.users.get(Number(id));
return user ?? new NotFoundHttpResponse();
}

@Post("/")
create(@Body() body: Omit<User, "id">): CreatedHttpResponse {
const user = this.users.create(body);

this.io.emit("user:created", user);

return new CreatedHttpResponse(user);
}
}
45 changes: 45 additions & 0 deletions examples/express/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import "reflect-metadata";

import { createServer } from "http";
import { Server } from "socket.io";
import { Container } from "inversify";
import { InversifyExpressHttpAdapter } from "@inversifyjs/http-express";
import { interfaces, InversifySocketServer, TYPE } from "inversify-socket-utils";

import { UserService } from "./services/user";
import { UserController } from "./controllers/user";
import { MessageController } from "./controllers/message";

const port = Number(process.env.PORT ?? 3000);
const container = new Container();

container.bind(UserService).toSelf().inSingletonScope();
container.bind(UserController).toSelf();

container
.bind<interfaces.Controller>(TYPE.Controller)
.to(MessageController);

const adapter = new InversifyExpressHttpAdapter(container, {
logger: true,
useJson: true,
});

const app = await adapter.build();

const httpServer = createServer(app);

const io = new Server(httpServer, {
cors: {
origin: "*",
},
});

container.bind(Server).toConstantValue(io);

new InversifySocketServer(container, io).build();

httpServer.listen(port, () => {
console.log(`HTTP is listening on http://127.0.0.1:${port}/api/users`);
console.log(`WS is listening on ws://127.0.0.1:${port}`);
});
37 changes: 37 additions & 0 deletions examples/express/services/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import "reflect-metadata";

import { injectable } from "inversify";

export interface User {
id: number;
name: string;
email: string;
}

@injectable()
export class UserService {
private readonly users: User[] = [
{ id: 1, name: "Rick Sanchez", email: "rick@example.com" },
{ id: 2, name: "Morty Smith", email: "morty@example.com" },
];

private nextId = 3;

list(): User[] {
return [...this.users];
}

get(id: number): User | undefined {
return this.users.find((user) => user.id === id);
}

create(data: Omit<User, "id">): User {
const user: User = { id: this.nextId++, ...data };
this.users.push(user);
return user;
}

total(): number {
return this.users.length;
}
}
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,27 @@
"clean": "rm -rf target",
"typecheck": "tsc -p tsconfig.json",
"semantic-release": "semantic-release",
"start": "bun run examples/basic/server.ts"
"start": "bun run examples/basic/server.ts",
"start:express": "bun run examples/express/server.ts"
},
"dependencies": {
"reflect-metadata": "^0.2.2"
},
"peerDependencies": {
"inversify": "^7.6.1",
"inversify": "^7.6.1 || ^8.2.3",
"socket.io": "^4.7.5"
},
"devDependencies": {
"@inversifyjs/http-core": "^5.6.1",
"@inversifyjs/http-express": "^5.6.1",
"@semantic-release/exec": "^7.1.0",
"@semantic-release/git": "^11.0.1",
"@types/bun": "^1.4.2",
"@types/express": "^5.0.6",
"@types/node": "^26.6.2",
"conventional-changelog-conventionalcommits": "^9.3.1",
"inversify": "^7.6.1",
"express": "^5.2.1",
"inversify": "^8.2.3",
"semantic-release": "^25.0.9",
"socket.io": "^4.7.5",
"socket.io-client": "^4.8.3",
Expand Down
Loading