release: WRNexusJS 0.8.0
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-02 23:18:51 +05:30
parent 87507edf59
commit 586a6db8ff
625 changed files with 243608 additions and 11210 deletions
+48
View File
@@ -0,0 +1,48 @@
import { describe, expect, test } from "bun:test";
import {
createAcknowledgement,
createRealtimeHistory,
createRealtimeMessage,
realtimeSseResponse,
} from "../src/index.ts";
describe("realtime replay and acknowledgements", () => {
test("bounds room history and resumes after a client acknowledgement", () => {
const history = createRealtimeHistory({ limitPerRoom: 2 });
const entries = [1, 2, 3].map((value) =>
history.publish(
"chat",
createRealtimeMessage({ type: "message", room: "chat", data: { value } }),
),
);
expect(history.replay("chat").map((entry) => entry.message.data)).toEqual([
{ value: 2 },
{ value: 3 },
]);
history.acknowledge("chat", "client-1", entries[1]!.sequence);
expect(history.resume("chat", "client-1")).toHaveLength(1);
expect(history.snapshot()).toMatchObject({ rooms: 1, messages: 2, acknowledgements: 1 });
});
test("acknowledgements are monotonic and reject impossible values", () => {
const history = createRealtimeHistory();
const entry = history.publish("chat", createRealtimeMessage({ type: "message", data: {} }));
history.acknowledge("chat", "client", entry.sequence);
history.acknowledge("chat", "client", 0);
expect(history.acknowledged("chat", "client")).toBe(entry.sequence);
expect(() => history.acknowledge("chat", "client", entry.sequence + 1)).toThrow();
expect(createAcknowledgement("chat", entry.sequence, "client").type).toBe("ack");
});
test("encodes sequenced events as SSE", async () => {
const message = createRealtimeMessage({ type: "notice", room: "chat", data: { ok: true } });
const response = realtimeSseResponse(
new ReadableStream({
start(controller) {
controller.enqueue({ sequence: 7, message });
controller.close();
},
}),
);
expect(response.headers.get("content-type")).toContain("text/event-stream");
expect(await response.text()).toContain("id: 7\nevent: notice\ndata:");
});
});
+56
View File
@@ -0,0 +1,56 @@
import { expect, test } from "bun:test";
import {
createPresenceEvent,
createRealtimeMessage,
createTypingEvent,
parseRealtimeMessage,
roomMemberSummary,
roomQuery,
} from "../src/index.ts";
test("creates and parses typed realtime messages", () => {
const message = createRealtimeMessage({
id: "message-1",
type: "message",
room: "support/general",
data: { text: "Hi" },
sentAt: "2026-01-01T00:00:00.000Z",
});
expect(parseRealtimeMessage(JSON.stringify(message), { room: "support/general" })).toEqual(
message,
);
});
test("builds presence and typing events", () => {
expect(createPresenceEvent("join", { userId: "u1" }).type).toBe("presence");
expect(createTypingEvent("u1", true).data.typing).toBe(true);
});
test("summarizes room members and query parameters", () => {
expect(
roomMemberSummary([
{ userId: "1", status: "online" },
{ userId: "2", status: "away" },
]),
).toEqual({ total: 2, online: 1, away: 1, busy: 0 });
expect(roomQuery({ user: "u1", page: 2, active: true })).toBe("user=u1&page=2&active=true");
});
test("rejects oversized, unsafe, mismatched, and unknown messages", () => {
const message = createRealtimeMessage({
id: "message-2",
type: "message",
room: "support",
data: { text: "Hi" },
});
expect(() =>
parseRealtimeMessage(JSON.stringify(message), { allowedTypes: ["presence"] }),
).toThrow("not allowed");
expect(() => parseRealtimeMessage(JSON.stringify(message), { room: "other" })).toThrow(
"does not match",
);
expect(() => parseRealtimeMessage(JSON.stringify(message), { maxBytes: 16 })).toThrow(
"maximum size",
);
expect(() => roomQuery({ "bad key": "x" })).toThrow("query key");
});
+34
View File
@@ -0,0 +1,34 @@
import { expect, test } from "bun:test";
import { createFileStreamReceiver, databaseChangeFeed, frameFileStream } from "../src/index.ts";
test("database feeds publish only allowed safe table changes", async () => {
let handler: ((change: any) => void) | undefined;
const published: string[] = [];
databaseChangeFeed(
{
subscribe(next) {
handler = next;
return () => {};
},
},
(topic) => {
published.push(topic);
},
{ allowTables: ["users"] },
);
await handler!({ table: "users", operation: "update", occurredAt: 1 });
await handler!({ table: "secrets", operation: "update", occurredAt: 1 });
await handler!({ table: "users;drop", operation: "delete", occurredAt: 1 });
expect(published).toEqual(["db:users"]);
});
test("file streams reassemble out of order and enforce bounds", () => {
const bytes = new TextEncoder().encode("x".repeat(3000));
const frames = frameFileStream("upload-1", bytes, { chunkBytes: 1024 });
const receiver = createFileStreamReceiver({ maxBytes: 4000 });
expect(receiver.accept(frames[2]!)).toBeNull();
expect(receiver.accept(frames[0]!)).toBeNull();
expect(receiver.accept(frames[1]!)).toEqual(bytes);
expect(() => frameFileStream("bad/id", bytes)).toThrow("Invalid stream id");
expect(() => frameFileStream("large", bytes, { maxBytes: 10 })).toThrow("FILE-LIMIT");
});