@@ -14,56 +14,170 @@ architecture is up to the task of handling proper extensible events.
14
14
## Usage: Parsing events
15
15
16
16
``` typescript
17
- const parsed = ExtensibleEvents .parse ({
18
- type: " m.room.message" ,
17
+ const parser = new EventParser ();
18
+ const parsed = parser .parse ({
19
+ type: " org.matrix.msc1767.message" ,
19
20
content: {
20
- " msgtype" : " m.text" ,
21
- " body" : " Hello world!"
21
+ " org.matrix.msc1767.markup" : [
22
+ { " body" : " this is my message text" },
23
+ ],
22
24
},
23
- // and other fields
24
- }) as MessageEvent ;
25
+ // and other required fields
26
+ });
25
27
26
- // Using instanceof can be unsafe in some cases, but casting the
27
- // response in TypeScript (as above) should be safe given this
28
- // if statement will block non-message types anyhow.
29
- if (parsed ?.isEquivalentTo (M_MESSAGE )) {
28
+ if (parsed instanceof MessageEvent ) {
30
29
console .log (parsed .text );
31
30
}
32
31
```
33
32
34
- * Note * : ` instanceof ` isn't considered safe for projects which might be running multiple copies
35
- of the library, such as in clients which have layers needing access to the events-sdk individually .
33
+ It is recommended to cache your ` EventParser ` instance for performance reasons, and for ease of use
34
+ when adding custom events.
36
35
37
- If you would like to register your own handling of events, use the following:
36
+ Registering your own events is easy, and we recommend creating your own block objects for handling the
37
+ contents of events:
38
38
39
39
``` typescript
40
- type MyContent = M_MESSAGE_EVENT_CONTENT & {
41
- field: string ;
42
- };
40
+ // There are a number of built-in block types for simple primitives
41
+ // BooleanBlock, IntegerBlock, StringBlock
43
42
44
- class MyEvent extends MessageEvent {
45
- public readonly field: string ;
43
+ // For object-based blocks, the following can be used:
44
+ type MyObjectBlockWireType = {
45
+ my_property: string ; // or whatever your block's properties are on the wire
46
+ };
46
47
47
- constructor (wireFormat : IPartialEvent <MyContent >) {
48
- // Parse the text bit of the event
49
- super (wireFormat );
48
+ class MyObjectBlock extends ObjectBlock <MyObjectBlockWireType > {
49
+ public static readonly schema: Schema = {
50
+ // This is a JSON Schema
51
+ type: " object" ,
52
+ properties: {
53
+ my_property: {
54
+ type: " string" ,
55
+ nullable: false ,
56
+ },
57
+ },
58
+ required: [" my_property" ],
59
+ errorMessage: {
60
+ properties: {
61
+ my_property: " my_property should be a non-null string and is required" ,
62
+ },
63
+ },
64
+ };
65
+
66
+ public static readonly validateFn = AjvContainer .ajv .compile (MyObjectBlock .schema );
67
+
68
+ public static readonly type = new UnstableValue (null , " org.example.my_custom_block" );
69
+
70
+ public constructor (raw : MyObjectBlockWireType ) {
71
+ super (MyObjectBlock .type .name , raw );
72
+ if (! MyObjectBlock .validateFn (raw )) {
73
+ throw new InvalidBlockError (this .name , MyObjectBlock .validateFn .errors );
74
+ }
75
+ }
76
+ }
50
77
51
- this .field = wireFormat .content ?.field ;
78
+ // For array-based blocks, we define the contents (items) slightly differently:
79
+ type MyArrayItemWireType = {
80
+ my_property: string ; // or whatever
81
+ }; // your item type can also be a primitive, like integers, booleans, and strings.
82
+
83
+ class MyArrayBlock extends ArrayBlock <MyArrayItemWireType > {
84
+ public static readonly schema = ArrayBlock .schema ;
85
+ public static readonly validateFn = ArrayBlock .validateFn ;
86
+
87
+ public static readonly itemSchema: Schema = {
88
+ // This is a JSON Schema
89
+ type: " object" ,
90
+ properties: {
91
+ my_property: {
92
+ type: " string" ,
93
+ nullable: false ,
94
+ },
95
+ },
96
+ required: [" my_property" ],
97
+ errorMessage: {
98
+ properties: {
99
+ my_property: " my_property should be a non-null string and is required" ,
100
+ },
101
+ },
102
+ };
103
+ public static readonly itemValidateFn = AjvContainer .ajv .compile (MyArrayBlock .itemSchema );
104
+
105
+ public static readonly type = new UnstableValue (null , " org.example.my_custom_block" );
106
+
107
+ public constructor (raw : MyArrayItemWireType []) {
108
+ super (MyArrayBlock .type .name , raw );
109
+ this .raw = raw .filter (x => {
110
+ const bool = MyArrayBlock .itemValidateFn (x );
111
+ if (! bool ) {
112
+ // Do something with the error. It might be valid to throw, as we do here, or
113
+ // use `.filter()`'s ability to exclude items from the final array.
114
+ throw new InvalidBlockError (this .name , MyArrayBlock .itemValidateFn .errors );
115
+ }
116
+ return bool ;
117
+ });
52
118
}
53
119
}
120
+ ```
54
121
55
- function parseMyEvent(wireEvent : IPartialEvent <MyContent >): Optional <MyEvent > {
56
- // If you need to convert a legacy format, this is where you'd do it. Your
57
- // event class should be able to be instatiated outside of this parse function.
58
- return new MyEvent (wireEvent );
122
+ Then, we can define a custom event:
123
+
124
+ ``` typescript
125
+ type MyWireContent = EitherAnd <
126
+ { [MyObjectBlock .type .name ]: MyObjectBlockWireType },
127
+ { [MyObjectBlock .type .altName ]: MyObjectBlockWireType }
128
+ >;
129
+
130
+ class MyCustomEvent extends RoomEvent <MyWireContent > {
131
+ public static readonly contentSchema: Schema = AjvContainer .eitherAnd (MyObjectBlock .type , MyObjectBlock .schema );
132
+ public static readonly contentValidateFn = AjvContainer .ajv .compile (MyCustomEvent .contentSchema );
133
+
134
+ public static readonly type = new UnstableValue (null , " org.example.my_custom_event" );
135
+
136
+ public constructor (raw : WireEvent .RoomEvent <MyWireContent >) {
137
+ super (MyCustomEvent .type .name , raw , false ); // see docs
138
+ if (! MyCustomEvent .contentValidateFn (this .content )) {
139
+ throw new InvalidEventError (this .name , MyCustomEvent .contentValidateFn .errors );
140
+ }
141
+ }
59
142
}
143
+ ```
144
+
145
+ and finally we can register it in a parser instance:
60
146
61
- ExtensibleEvents .registerInterpreter (" org.example.my_event_type" , parseMyEvent );
62
- ExtensibleEvents .unknownInterpretOrder .push (" org.example.my_event_type" );
147
+ ``` typescript
148
+ const parser = new EventParser ();
149
+ parser .addKnownType (MyCustomEvent .type , x => new MyCustomEvent (x ));
63
150
```
64
151
152
+ If you'd also like to register an "unknown event type" handler, that can be done like so:
153
+
154
+ ``` typescript
155
+ const myParser: UnknownEventParser <MyWireContent > = x => {
156
+ const possibleBlock = MyObjectBlock .type .findIn (x .content );
157
+ if (!! possibleBlock ) {
158
+ const block = new MyObjectBlock (possibleBlock as MyObjectBlockWireType );
159
+ return new MyCustomEvent ({
160
+ ... x ,
161
+ type: MyCustomEvent .type .name , // required - override the event type
162
+ content: {
163
+ [MyObjectBlock .name ]: block .raw ,
164
+ }, // technically optional, but good practice: clean up the event's content for handling.
165
+ });
166
+ }
167
+ return undefined ; // else, we don't care about it
168
+ };
169
+ parser .setUnknownParsers ([myParser , ... parser .defaultUnknownEventParsers ]);
170
+ ```
171
+
172
+ Putting your parser at the start of the array will ensure it gets called first. Including the default parsers
173
+ is also optional, though recommended.
174
+
65
175
## Usage: Making events
66
176
177
+ <!-- ------------------------- -->
178
+ *** TODO: This needs refactoring***
179
+ <!-- ------------------------- -->
180
+
67
181
Most event objects have a ` from ` static function which takes common details of an event
68
182
and returns an instance of that event for later serialization.
69
183
0 commit comments