@@ -14,56 +14,170 @@ architecture is up to the task of handling proper extensible events.
1414## Usage: Parsing events
1515
1616``` 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" ,
1920 content: {
20- " msgtype" : " m.text" ,
21- " body" : " Hello world!"
21+ " org.matrix.msc1767.markup" : [
22+ { " body" : " this is my message text" },
23+ ],
2224 },
23- // and other fields
24- }) as MessageEvent ;
25+ // and other required fields
26+ });
2527
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 ) {
3029 console .log (parsed .text );
3130}
3231```
3332
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.
3635
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:
3838
3939``` 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
4342
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+ };
4647
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+ }
5077
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+ });
52118 }
53119}
120+ ```
54121
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+ }
59142}
143+ ```
144+
145+ and finally we can register it in a parser instance:
60146
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 ));
63150```
64151
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+
65175## Usage: Making events
66176
177+ <!-- ------------------------- -->
178+ *** TODO: This needs refactoring***
179+ <!-- ------------------------- -->
180+
67181Most event objects have a ` from ` static function which takes common details of an event
68182and returns an instance of that event for later serialization.
69183
0 commit comments