Skip to content

Commit 23c7344

Browse files
committed
build: create nips module
WIP: refactor: nip module refactor: thin core
1 parent db1ba3b commit 23c7344

40 files changed

+509
-588
lines changed

core/clients.ts

+16-44
Original file line numberDiff line numberDiff line change
@@ -4,69 +4,41 @@ import type {
44
RelayToClientMessage,
55
SubscriptionId,
66
} from "./protocol.ts";
7-
import {
8-
NostrNode,
9-
NostrNodeBase,
10-
NostrNodeConfig,
11-
NostrNodeModule,
12-
} from "./nodes.ts";
7+
import { Node, NodeConfig } from "./nodes.ts";
138

14-
// ----------------------
15-
// Interfaces
16-
// ----------------------
17-
18-
export type ClientConfig = NostrNodeConfig<
19-
RelayToClientMessage,
20-
ClientEventTypeRecord
21-
>;
9+
export type ClientConfig = NodeConfig;
2210
export type ClientOptions = Partial<ClientConfig>;
2311

12+
export interface ClientEventTypeRecord {
13+
message: ClientToRelayMessage;
14+
}
15+
2416
/**
2517
* A class that represents a remote Nostr client.
2618
*/
27-
export class Client extends NostrNodeBase<
19+
export class Client extends Node<
2820
RelayToClientMessage,
2921
ClientEventTypeRecord
30-
> implements NostrNode<RelayToClientMessage, ClientEventTypeRecord> {
31-
/**
32-
* The WebSocket connection to the client.
33-
*/
22+
> {
3423
declare ws: WebSocket;
24+
declare config: ClientConfig;
3525

36-
/**
37-
* Writable interface for the subscriptions.
38-
*/
26+
/** Writable interface for the subscriptions. */
3927
readonly subscriptions: Map<
4028
SubscriptionId,
4129
WritableStream<NostrEvent>
4230
> = new Map();
4331

44-
constructor(ws: WebSocket, opts?: ClientOptions) {
45-
super(ws, opts);
32+
constructor(ws: WebSocket, options?: ClientOptions) {
33+
super(ws, options);
34+
this.config = {
35+
...this.config,
36+
...options,
37+
};
4638
this.ws.addEventListener("message", (ev: MessageEvent<string>) => {
4739
const message = JSON.parse(ev.data) as ClientToRelayMessage;
4840
// TODO: Validate the message.
4941
this.dispatch("message", message);
5042
});
5143
}
5244
}
53-
54-
// ------------------------------
55-
// Events
56-
// ------------------------------
57-
58-
export interface ClientEventTypeRecord {
59-
"message": ClientToRelayMessage;
60-
}
61-
62-
export type ClientEventType = keyof ClientEventTypeRecord;
63-
64-
// ------------------------------
65-
// Modules
66-
// ------------------------------
67-
68-
export type ClientModule = NostrNodeModule<
69-
RelayToClientMessage,
70-
ClientEventTypeRecord,
71-
Client
72-
>;

core/deno.json

+6
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,11 @@
66
"./clients": "./clients.ts",
77
"./protocol": "./protocol.ts",
88
"./relays": "./relays.ts"
9+
},
10+
"imports": {
11+
"@lophus/lib": "jsr:@lophus/lib@0.0.14"
12+
},
13+
"publish": {
14+
"exclude": ["*_test.ts"]
915
}
1016
}

core/mod.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1+
export * from "./clients.ts";
12
export * from "./protocol.ts";
23
export * from "./relays.ts";
3-
export * from "./clients.ts";

core/nodes.ts

+57-116
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,35 @@
1-
import type { NostrMessage } from "./protocol.ts";
2-
import { WebSocketLike } from "./websockets.ts";
3-
4-
export interface NostrNodeConfig<
5-
W extends NostrMessage = NostrMessage,
6-
R extends EventTypeRecord = EventTypeRecord,
7-
> {
8-
modules: NostrNodeModule<W, R>[];
1+
import type { WebSocketLike } from "@lophus/lib/websockets";
2+
import type { InterNodeMessage } from "./protocol.ts";
3+
4+
export interface NodeConfig {
95
nbuffer: number;
106
}
117

12-
export type NostrNodeOptions<
13-
W extends NostrMessage = NostrMessage,
14-
R extends EventTypeRecord = EventTypeRecord,
15-
> = Partial<NostrNodeConfig<W, R>>;
16-
17-
/**
18-
* Common interface for relays and clients, which extends `EventTarget`.
19-
*/
20-
export interface NostrNode<
21-
W extends NostrMessage = NostrMessage,
22-
R extends EventTypeRecord = EventTypeRecord,
23-
> {
24-
readonly config: Readonly<NostrNodeConfig<W, R>>;
25-
readonly ws: WebSocketLike;
26-
readonly writable: WritableStream<W>;
27-
28-
status: WebSocketLike["readyState"];
29-
send(msg: W): void | Promise<void>;
30-
close(): Promise<void>;
31-
32-
install(mod: NostrNodeModule<W, R>): void;
33-
34-
addEventListener<T extends EventType<R>>(
35-
type: T,
36-
listener:
37-
| NostrNodeEventListenerOrEventListenerObject<W, R, T>
38-
| null,
39-
options?: AddEventListenerOptions,
40-
): void;
41-
42-
removeEventListener<T extends EventType<R>>(
43-
type: T,
44-
listener:
45-
| NostrNodeEventListenerOrEventListenerObject<W, R, T>
46-
| null,
47-
options?: boolean | EventListenerOptions,
48-
): void;
49-
50-
dispatchEvent<T extends EventType<R>>(event: NostrNodeEvent<R, T>): boolean;
51-
52-
/**
53-
* A convenience method to dispatch a `NostrNodeEvent` with the given `type`
54-
* and `data`.
55-
*/
56-
dispatch<T extends EventType<R>>(type: T, data: R[T]): void;
57-
58-
/**
59-
* A convenience method to add an event listener for the given `type` that
60-
* calls the given `listener` when the event is dispatched.
61-
*/
62-
on<T extends EventType<R>>(
63-
type: T,
64-
// deno-lint-ignore no-explicit-any
65-
listener: (data: R[T]) => any,
66-
): void;
67-
}
8+
export type NodeOptions = Partial<NodeConfig>;
689

6910
/**
7011
* Common base class for relays and clients.
7112
*/
72-
export class NostrNodeBase<
73-
W extends NostrMessage = NostrMessage,
74-
R extends EventTypeRecord = EventTypeRecord,
75-
> extends EventTarget implements NostrNode<W, R> {
76-
readonly writable: WritableStream<W>;
77-
readonly config: Readonly<NostrNodeConfig<W, R>>;
13+
export class Node<
14+
M extends InterNodeMessage = InterNodeMessage,
15+
R = AnyEventTypeRecord,
16+
> extends EventTarget {
17+
readonly writable: WritableStream<M>;
18+
readonly config: Readonly<NodeConfig>;
7819

7920
constructor(
8021
readonly ws: WebSocketLike,
81-
opts: NostrNodeOptions = {},
22+
options: NodeOptions = {},
8223
) {
8324
super();
8425
this.writable = new WritableStream({
8526
write: (msg) => this.ws.send(JSON.stringify(msg)),
8627
close: () => this.ws.close(),
8728
});
88-
this.config = { modules: [], nbuffer: 10, ...opts };
89-
this.config.modules.forEach((m) => this.install(m));
29+
this.config = { nbuffer: 10, ...options };
9030
}
9131

92-
send(msg: W): void | Promise<void> {
32+
send(msg: M): void | Promise<void> {
9333
return this.ws.send(JSON.stringify(msg));
9434
}
9535

@@ -107,19 +47,31 @@ export class NostrNodeBase<
10747
}
10848
}
10949

110-
install(mod: NostrNodeModule<W, R>): void {
111-
mod.install(this);
112-
}
50+
declare addEventListener: <T extends EventType<R>>(
51+
type: T,
52+
listener:
53+
| NodeEventListenerOrEventListenerObject<M, R, T>
54+
| null,
55+
options?: AddEventListenerOptions,
56+
) => void;
57+
58+
declare removeEventListener: <T extends EventType<R>>(
59+
type: T,
60+
listener:
61+
| NodeEventListenerOrEventListenerObject<M, R, T>
62+
| null,
63+
options?: boolean | EventListenerOptions,
64+
) => void;
11365

114-
declare addEventListener: NostrNode<W, R>["addEventListener"];
115-
declare removeEventListener: NostrNode<W, R>["removeEventListener"];
116-
declare dispatchEvent: NostrNode<W, R>["dispatchEvent"];
66+
declare dispatchEvent: <T extends EventType<R>>(
67+
event: NodeEvent<R, T>,
68+
) => boolean;
11769

11870
dispatch<T extends EventType<R>>(
11971
type: T,
12072
data: R[T],
12173
) {
122-
this.dispatchEvent(new NostrNodeEvent<R, T>(type, data));
74+
this.dispatchEvent(new NodeEvent<R, T>(type, data));
12375
}
12476

12577
on<T extends EventType<R>>(
@@ -131,56 +83,45 @@ export class NostrNodeBase<
13183
}
13284
}
13385

134-
// ------------------------------
135-
// Extensions
136-
// ------------------------------
137-
138-
export interface NostrNodeModule<
139-
W extends NostrMessage = NostrMessage,
140-
R extends EventTypeRecord = EventTypeRecord,
141-
N extends NostrNode<W, R> = NostrNode<W, R>,
142-
> {
143-
// deno-lint-ignore no-explicit-any
144-
install(node: N): any;
145-
}
146-
14786
// ------------------------------
14887
// Events
14988
// ------------------------------
15089

151-
// deno-lint-ignore no-empty-interface
152-
export interface EventTypeRecord {}
90+
// deno-lint-ignore no-explicit-any
91+
type AnyEventTypeRecord = any;
15392

154-
type EventType<R extends EventTypeRecord> = keyof R & string;
93+
type EventType<R = AnyEventTypeRecord> = keyof R & string;
15594

156-
export class NostrNodeEvent<
157-
R extends EventTypeRecord,
158-
T extends EventType<R>,
95+
export class NodeEvent<
96+
R = AnyEventTypeRecord,
97+
T extends EventType<R> = EventType<R>,
15998
> extends MessageEvent<R[T]> {
16099
declare type: T;
161100
constructor(type: T, data: R[T]) {
162101
super(type, { data });
163102
}
164103
}
165104

166-
type NostrNodeEventListenerOrEventListenerObject<
167-
W extends NostrMessage,
168-
R extends EventTypeRecord,
169-
T extends EventType<R>,
170-
> = NostrNodeEventListener<W, R, T> | NostrNodeEventListenerObject<W, R, T>;
171-
172-
type NostrNodeEventListener<
173-
W extends NostrMessage,
174-
R extends EventTypeRecord,
175-
T extends EventType<R>,
105+
type NodeEventListenerOrEventListenerObject<
106+
M extends InterNodeMessage,
107+
R = AnyEventTypeRecord,
108+
T extends EventType<R> = EventType<R>,
109+
> =
110+
| NodeEventListener<M, R, T>
111+
| NodeEventListenerObject<M, R, T>;
112+
113+
type NodeEventListener<
114+
W extends InterNodeMessage,
115+
R = AnyEventTypeRecord,
116+
T extends EventType<R> = EventType<R>,
176117
> // deno-lint-ignore no-explicit-any
177-
= (this: NostrNode<W, R>, ev: MessageEvent<R[T]>) => any;
118+
= (this: Node<W, R>, ev: MessageEvent<R[T]>) => any;
178119

179-
type NostrNodeEventListenerObject<
180-
W extends NostrMessage,
181-
R extends EventTypeRecord,
182-
T extends EventType<R>,
120+
type NodeEventListenerObject<
121+
W extends InterNodeMessage,
122+
R = AnyEventTypeRecord,
123+
T extends EventType<R> = EventType<R>,
183124
> = {
184125
// deno-lint-ignore no-explicit-any
185-
handleEvent(this: NostrNode<W, R>, ev: MessageEvent<R[T]>): any;
126+
handleEvent(this: Node<W, R>, ev: MessageEvent<R[T]>): any;
186127
};

core/nodes_test.ts

+17-23
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,27 @@
11
import { assertEquals } from "@std/assert";
2-
import { afterAll, beforeAll, describe, it } from "@std/testing/bdd";
2+
import { describe, it } from "@std/testing/bdd";
33
import { MockWebSocket } from "@lophus/lib/testing";
4-
import { NostrNode, NostrNodeBase } from "./nodes.ts";
4+
import { Node } from "./nodes.ts";
55

6-
describe("NostrNodeBase", () => {
7-
let node: NostrNode;
8-
let writer: WritableStreamDefaultWriter;
6+
describe("NostrNode", () => {
7+
describe("writable", () => {
8+
const node = new Node(new MockWebSocket());
9+
let writer: WritableStreamDefaultWriter;
910

10-
beforeAll(() => {
11-
node = new NostrNodeBase(new MockWebSocket());
12-
});
13-
14-
afterAll(async () => {
15-
await node.close().catch((err) => {
16-
if (err.message !== "Writable stream is closed or errored.") {
17-
throw err;
18-
}
11+
it("should open the WebSocket connection", async () => {
12+
writer = node.writable.getWriter();
13+
await writer.write(["NOTICE", "test"]);
14+
writer.releaseLock();
15+
assertEquals(node.status, WebSocket.OPEN);
1916
});
2017
});
2118

22-
it("should be connected to the WebSocket after a message is sent", async () => {
23-
writer = node.writable.getWriter();
24-
await writer.write(["NOTICE", "test"]);
25-
writer.releaseLock();
26-
assertEquals(node.status, WebSocket.OPEN);
27-
});
19+
describe("close", () => {
20+
const node = new Node(new MockWebSocket());
2821

29-
it("should close the WebSocket when the node is closed", async () => {
30-
await node.close();
31-
assertEquals(node.status, WebSocket.CLOSED);
22+
it("should close the WebSocket connection", async () => {
23+
await node.close();
24+
assertEquals(node.status, WebSocket.CLOSED);
25+
});
3226
});
3327
});

0 commit comments

Comments
 (0)