Skip to content

Commit 746bd98

Browse files
committed
feat(nips): add NIP-02
1 parent 131e952 commit 746bd98

File tree

9 files changed

+103
-61
lines changed

9 files changed

+103
-61
lines changed

core/clients_test.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {
2-
EventKind,
32
EventMessage,
43
NostrEvent,
54
OkMessage,
@@ -58,7 +57,7 @@ describe("Client", () => {
5857
});
5958
it("should receive requests", async () => {
6059
subid = "test" as SubscriptionId;
61-
const req = { kinds: [EventKind[0]] };
60+
const req = { kinds: [0] };
6261
ws.dispatchEvent(
6362
new MessageEvent("message", {
6463
data: JSON.stringify(["REQ", subid, req]),

core/nips/01.ts

+43-41
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ export interface NostrEvent<K extends EventKind = EventKind> {
1313
pubkey: PublicKey;
1414
created_at: Timestamp;
1515
kind: K;
16-
tags: Tag[];
17-
content: Stringified<EventContent[K]>;
16+
tags: TagFor[K][];
17+
content: Stringified<EventContentFor[K]>;
1818
sig: Signature;
1919
}
2020

@@ -26,31 +26,34 @@ export type Timestamp = Brand<number, "EventTimeStamp">;
2626
// Tags
2727
// ----------------------
2828

29-
export type Tag<T extends TagName = TagName> = [T, ...string[]];
29+
export interface TagFor {
30+
0: AnyTag;
31+
1: AnyTag;
32+
2: AnyTag;
33+
}
34+
35+
export type AnyTag = string[];
3036

31-
export type TagName = Brand<string, "TagName">;
3237
export type IndexedEventTag = [IndexedEventTagName, ...string[]];
3338

34-
export type EventTag = ["e", EventId, RecmRelayUrl?];
35-
export type PubKeyTag = ["p", PublicKey, RecmRelayUrl?];
39+
export type EventTag = ["e", EventId, RelayUrl?];
40+
export type PubKeyTag = ["p", PublicKey, RelayUrl?];
3641
export type ParameterizedReplaceableEventTag = [
3742
"a",
3843
`${EventKind}:${PublicKey}:${TagValue<"d">}`,
39-
RecmRelayUrl?,
44+
RelayUrl?,
4045
];
4146
export type NonParameterizedReplaceableEventTag = [
4247
"a",
4348
`${EventKind}:${PublicKey}`,
44-
RecmRelayUrl?,
49+
RelayUrl?,
4550
];
4651

4752
// TODO: Use template literal
4853
export type IndexedEventTagName = Brand<string, "IndexedEventTagName">;
4954
// TODO: Use template literal
5055
export type TagValue<T extends string = string> = Brand<string, `${T}TagValue`>;
5156

52-
export type RecmRelayUrl = RelayUrl;
53-
5457
export type PrivateKey = Brand<string, "PrivateKey">;
5558
export type Signature = Brand<string, "EventSignature">;
5659

@@ -59,8 +62,8 @@ export type EventSerializePrecursor<K extends EventKind = EventKind> = [
5962
pubkey: PublicKey,
6063
created_at: Timestamp,
6164
kind: K,
62-
tags: Tag[],
63-
content: Stringified<EventContent[K]>,
65+
tags: TagFor[K][],
66+
content: Stringified<EventContentFor[K]>,
6467
];
6568

6669
// ----------------------
@@ -96,6 +99,7 @@ export type EventMessage<K extends EventKind = EventKind> = [
9699
];
97100
export type OkMessage<B extends boolean = boolean> = [
98101
"OK",
102+
99103
EventId,
100104
B,
101105
OkMessageBody<B>,
@@ -139,10 +143,17 @@ export type SubscriptionFilter<
139143
// Basic event kinds
140144
// ----------------------
141145

142-
export type EventKind<T extends number = number> = Brand<T, "EventKind">;
146+
export enum EventKind {
147+
Metadata = 0,
148+
TextNote = 1,
149+
/**
150+
* @deprecated
151+
*/
152+
RecommendRelay = 2,
153+
}
143154

144-
export type MetadataEvent = NostrEvent<EventKind<0>>;
145-
export type TextNoteEvent = NostrEvent<EventKind<1>>;
155+
export type MetadataEvent = NostrEvent<0>;
156+
export type TextNoteEvent = NostrEvent<1>;
146157

147158
// TODO: Use template literal for T
148159

@@ -168,44 +179,35 @@ export type ParameterizedReplaceableEventKind<T extends number = number> =
168179
"ParameterizedReplaceable"
169180
>;
170181

171-
export const EventKind = {
172-
0: 0 as EventKind<0>,
173-
Metadata: 0 as EventKind<0>,
174-
175-
1: 1 as EventKind<1>,
176-
TextNote: 1 as EventKind<1>,
177-
178-
$<T extends number>(kind: T): EventKind<T> {
179-
return kind as EventKind<T>;
180-
},
181-
182-
isRegularEventKind(
182+
// deno-lint-ignore no-namespace
183+
export namespace EventKind {
184+
export function isRegularEventKind(
183185
kind: EventKind,
184186
): kind is RegularEventKind {
185187
return 1000 <= kind && kind < 10000;
186-
},
187-
isReplaceableEventKind(
188+
}
189+
export function isReplaceableEventKind(
188190
kind: EventKind,
189191
): kind is ReplaceableEventKind {
190-
return (10000 <= kind && kind < 20000) || kind === 0 || kind === 3;
191-
},
192-
isEphemeralEventKind(
192+
return (10000 <= kind && kind < 20000) || kind === 0;
193+
}
194+
export function isEphemeralEventKind(
193195
kind: EventKind,
194196
): kind is EphemeralEventKind {
195197
return 20000 <= kind && kind < 30000;
196-
},
197-
isParameterizedReplaceableEventKind(
198+
}
199+
export function isParameterizedReplaceableEventKind(
198200
kind: EventKind,
199201
): kind is ParameterizedReplaceableEventKind {
200202
return 30000 <= kind && kind < 40000;
201-
},
202-
};
203+
}
204+
}
203205

204-
export type EventContent = [
205-
MetadataContent,
206-
string,
207-
RelayUrl,
208-
];
206+
export interface EventContentFor extends Record<EventKind, unknown> {
207+
0: MetadataContent;
208+
1: string;
209+
2: RelayUrl;
210+
}
209211

210212
export interface MetadataContent {
211213
name: string;

core/relays_test.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
EventId,
3-
EventKind,
43
MetadataEvent,
54
OkMessage,
65
PublishMessage,
@@ -93,7 +92,7 @@ describe("Relay", () => {
9392
assertEquals(relay.status, WebSocket.CLOSED);
9493
});
9594
it("should not connect when a subscription is created", () => {
96-
sub_1 = relay.subscribe({ kinds: [EventKind[1]] }, { id: "test-1" });
95+
sub_1 = relay.subscribe({ kinds: [1] }, { id: "test-1" });
9796
assert(sub_1 instanceof ReadableStream);
9897
assertEquals(relay.status, WebSocket.CLOSED);
9998
});
@@ -112,7 +111,7 @@ describe("Relay", () => {
112111
reader.releaseLock();
113112
});
114113
it("should be able to open multiple subscriptions", () => {
115-
sub_0 = relay.subscribe({ kinds: [EventKind[0]], limit: 1 }, {
114+
sub_0 = relay.subscribe({ kinds: [0], limit: 1 }, {
116115
id: "test-0",
117116
});
118117
assert(sub_0 instanceof ReadableStream);
@@ -150,7 +149,7 @@ describe("Relay", () => {
150149
ws.remote.addEventListener(
151150
"message",
152151
(ev: MessageEvent<string>) => {
153-
const [, event] = JSON.parse(ev.data) as PublishMessage<EventKind<1>>;
152+
const [, event] = JSON.parse(ev.data) as PublishMessage<1>;
154153
if (event.id === eid) {
155154
assertEquals(event.kind, 1);
156155
resolve(true);
@@ -172,7 +171,7 @@ describe("Relay", () => {
172171
ws.remote.addEventListener(
173172
"message",
174173
(ev: MessageEvent<string>) => {
175-
const [, event] = JSON.parse(ev.data) as PublishMessage<EventKind<1>>;
174+
const [, event] = JSON.parse(ev.data) as PublishMessage<1>;
176175
if (event.id === eid) {
177176
assertEquals(event.kind, 1);
178177
resolve(true);

lib/events.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import type {
22
ClientToRelayMessage,
3-
EventContent,
3+
EventContentFor,
44
EventKind,
55
PrivateKey,
66
Stringified,
7-
Tag,
7+
TagFor,
88
} from "../core/nips/01.ts";
99
import type { RelayLike } from "../core/relays.ts";
1010

1111
export { EventKind } from "../core/nips/01.ts";
1212

1313
export interface EventInit<K extends EventKind = EventKind> {
1414
kind: K;
15-
tags?: Tag[];
16-
content: EventContent[K] | Stringified<EventContent[K]>;
15+
tags?: TagFor[K][];
16+
content: EventContentFor[K] | Stringified<EventContentFor[K]>;
1717
}
1818

1919
import { Signer } from "./signs.ts";

lib/nips/02.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { NostrEvent } from "../../core/nips/01.ts";
2+
3+
declare module "../../core/nips/01.ts" {
4+
enum EventKind {
5+
ContactList = 3,
6+
}
7+
interface EventContentFor {
8+
3: "";
9+
}
10+
interface TagFor {
11+
3: ["p", PublicKey, RelayUrl, string];
12+
}
13+
}
14+
15+
export type ContactListEvent = NostrEvent<3>;

lib/nips/02_test.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { describe, it } from "../std/testing.ts";
2+
import { assert } from "../std/assert.ts";
3+
import { env } from "../env.ts";
4+
import { EventInit } from "../events.ts";
5+
6+
describe("EventInit<3>", () => {
7+
it("valid", () => {
8+
const init = {
9+
kind: 3,
10+
content: "",
11+
tags: [
12+
["p", env.PUBLIC_KEY, "wss://nos.lol", "string"],
13+
],
14+
} satisfies EventInit<3>;
15+
assert(init);
16+
});
17+
it('tag name should be "p"', () => {
18+
const init = {
19+
kind: 3,
20+
content: "",
21+
tags: [
22+
// @ts-expect-error: tag name should be "p"
23+
["e", env.PUBLIC_KEY, "wss://nos.lol", "string"],
24+
],
25+
} satisfies EventInit<3>;
26+
assert(init);
27+
});
28+
});

lib/notes.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { EventKind, NostrEvent, RelayUrl, TagName } from "../core/nips/01.ts";
1+
import { NostrEvent, RelayUrl } from "../core/nips/01.ts";
22
import type { Optional } from "../core/types.ts";
33
import { EventInit } from "./events.ts";
44

5-
export type TextNote = EventInit<EventKind<1>>;
5+
export type TextNote = EventInit<1>;
66

77
export type TextNoteInit = Optional<TextNote, "kind">;
88

@@ -25,10 +25,10 @@ export class TextNoteComposer extends TransformStream<TextNoteInit, TextNote> {
2525

2626
// deno-fmt-ignore
2727
const tags = (init.tags ?? []).concat(opts?.replyTo ? [
28-
["e" as TagName, opts.replyTo.id, relayRecommend ?? ""],
29-
["p" as TagName, opts.replyTo.pubkey, relayRecommend ?? ""],
28+
["e", opts.replyTo.id, relayRecommend ?? ""],
29+
["p", opts.replyTo.pubkey, relayRecommend ?? ""],
3030
] : []);
3131

32-
return { ...init, kind: EventKind.$(1), tags };
32+
return { ...init, kind: 1, tags };
3333
}
3434
}

lib/signs.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Brand } from "../core/types.ts";
22
import type {
3-
EventContent,
3+
EventContentFor,
44
EventId,
55
EventKind,
66
EventSerializePrecursor,
@@ -55,7 +55,7 @@ export class Signer extends TransformStream<EventInit, NostrEvent> {
5555
tags: [],
5656
...event,
5757
pubkey: PublicKey.from(this.nsec),
58-
content: JSON.stringify(event.content) as Stringified<EventContent[K]>,
58+
content: JSON.stringify(event.content) as Stringified<EventContentFor[K]>,
5959
} satisfies UnsignedEvent<K>;
6060

6161
const hash = sha256(this.#encoder.encode(serialize(unsigned)));

lib/signs_test.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { EventKind } from "../core/nips/01.ts";
21
import { describe, it } from "../lib/std/testing.ts";
32
import { assert, assertEquals } from "../lib/std/assert.ts";
43
import { Timestamp } from "../lib/times.ts";
@@ -25,7 +24,7 @@ describe("Signer/Verifier", () => {
2524
const event = {
2625
pubkey: PublicKey.from(nsec),
2726
created_at: Timestamp.now,
28-
kind: EventKind[1],
27+
kind: 1,
2928
tags: [],
3029
content: "lophus",
3130
};

0 commit comments

Comments
 (0)